[TKR-275] Add Geist on Fantom (#398)

* Add Geist on Fantom

* nvm there is not subgraph for it

* finish giest utils

* Address pr comments

* lowercase gtoken addresses

* return undefined instead of error for unsupported pairs

* another lower case

* Update fantom fillQuoteTransformer address

* more const clean up
This commit is contained in:
Cece Z
2022-03-01 22:34:30 -08:00
committed by GitHub
parent 6073607d3e
commit ae2fe55efa
10 changed files with 207 additions and 146 deletions

View File

@@ -1,4 +1,14 @@
[
{
"version": "16.50.0",
"changes": [
{
"note": "Adding support for Geist on `Fantom`",
"pr": 398
}
]
},
{
"version": "16.49.9",
"changes": [

View File

@@ -0,0 +1,57 @@
import { BigNumber } from '@0x/utils';
import { ZERO_AMOUNT } from '../constants';
export interface GeistInfo {
lendingPool: string;
gToken: string;
underlyingToken: string;
}
// tslint:disable-next-line:no-unnecessary-class
export class GeistSampler {
public static sampleSellsFromGeist(
geistInfo: GeistInfo,
takerToken: string,
makerToken: string,
takerTokenAmounts: BigNumber[],
): BigNumber[] {
// Deposit/Withdrawal underlying <-> gToken is always 1:1
if (
(takerToken.toLowerCase() === geistInfo.gToken.toLowerCase() &&
makerToken.toLowerCase() === geistInfo.underlyingToken.toLowerCase()) ||
(takerToken.toLowerCase() === geistInfo.underlyingToken.toLowerCase() &&
makerToken.toLowerCase() === geistInfo.gToken.toLowerCase())
) {
return takerTokenAmounts;
}
// Not matching the reserve return 0 results
const numSamples = takerTokenAmounts.length;
const makerTokenAmounts = new Array(numSamples);
makerTokenAmounts.fill(ZERO_AMOUNT);
return makerTokenAmounts;
}
public static sampleBuysFromGeist(
geistInfo: GeistInfo,
takerToken: string,
makerToken: string,
makerTokenAmounts: BigNumber[],
): BigNumber[] {
// Deposit/Withdrawal underlying <-> gToken is always 1:1
if (
(takerToken.toLowerCase() === geistInfo.gToken.toLowerCase() &&
makerToken.toLowerCase() === geistInfo.underlyingToken.toLowerCase()) ||
(takerToken.toLowerCase() === geistInfo.underlyingToken.toLowerCase() &&
makerToken.toLowerCase() === geistInfo.gToken.toLowerCase())
) {
return makerTokenAmounts;
}
// Not matching the reserve return 0 results
const numSamples = makerTokenAmounts.length;
const takerTokenAmounts = new Array(numSamples);
takerTokenAmounts.fill(ZERO_AMOUNT);
return takerTokenAmounts;
}
}

View File

@@ -17,6 +17,7 @@ import {
ERC20BridgeSource,
FeeSchedule,
FillData,
GeistFillData,
GetMarketOrdersOpts,
KyberSamplerOpts,
LidoInfo,
@@ -187,6 +188,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.Beethovenx,
ERC20BridgeSource.Curve,
ERC20BridgeSource.CurveV2,
ERC20BridgeSource.Geist,
ERC20BridgeSource.JetSwap,
ERC20BridgeSource.MorpheusSwap,
ERC20BridgeSource.SpiritSwap,
@@ -331,6 +333,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.Beethovenx,
ERC20BridgeSource.Curve,
ERC20BridgeSource.CurveV2,
ERC20BridgeSource.Geist,
ERC20BridgeSource.JetSwap,
ERC20BridgeSource.MorpheusSwap,
ERC20BridgeSource.SpiritSwap,
@@ -578,6 +581,7 @@ export const FANTOM_TOKENS = {
DAI: '0x8d11ec38a3eb5e956b052f67da8bdc9bef8abf3e',
fUSDT: '0x049d68029688eabf473097a2fc38ef61633a3c7a',
WBTC: '0x321162cd933e2be498cd2267a90534a804051b11',
WCRV: '0x1e4f97b9f9f913c46f1632781732927b9019c68b',
renBTC: '0xdbf31df14b66535af65aac99c32e9ea844e14501',
MIM: '0x82f0b8b456c1a451378467398982d4834b6829c1',
nUSD: '0xed2a7edd7413021d440b09d654f3b87712abab66',
@@ -586,6 +590,15 @@ export const FANTOM_TOKENS = {
gUSDC: '0xe578c856933d8e1082740bf7661e379aa2a30b26',
gDAI: '0x07e6332dd090d287d3489245038daf987955dcfb',
FRAX: '0xdc301622e621166bd8e82f2ca0a26c13ad0be355',
gFTM: '0x39b3bd37208cbade74d0fcbdbb12d606295b430a',
gETH: '0x25c130b2624cf12a4ea30143ef50c5d68cefa22f',
gWBTC: '0x38aca5484b8603373acc6961ecd57a6a594510a3',
gCRV: '0x690754a168b022331caa2467207c61919b3f8a98',
gMIM: '0xc664fc7b8487a3e10824cda768c1d239f2403bbe',
};
export const GEIST_FANTOM_POOLS = {
lendingPool: '0x9fad24f572045c7869117160a571b2e50b10d068',
};
export const OPTIMISM_TOKENS = {
@@ -1996,6 +2009,13 @@ export const COMPONENT_POOLS_BY_CHAIN_ID = valueByChainId(
},
);
export const GEIST_INFO_ADDRESS_BY_CHAIN_ID = valueByChainId<string>(
{
[ChainId.Fantom]: '0xd8321aa83fb0a4ecd6348d4577431310a6e0814d',
},
NULL_ADDRESS,
);
export const BALANCER_V2_VAULT_ADDRESS_BY_CHAIN = valueByChainId<string>(
{
[ChainId.Mainnet]: '0xba12222222228d8ba445958a75a0704d566bf2c8',
@@ -2362,6 +2382,10 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
// NOTE: The Aave deposit method is more expensive than the withdraw
return aaveFillData.takerToken === aaveFillData.underlyingToken ? 400e3 : 300e3;
},
[ERC20BridgeSource.Geist]: (fillData?: FillData) => {
const geistFillData = fillData as GeistFillData;
return geistFillData.takerToken === geistFillData.underlyingToken ? 400e3 : 300e3;
},
[ERC20BridgeSource.Compound]: (fillData?: FillData) => {
// NOTE: cETH is handled differently than other cTokens
const wethAddress = NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet];

View File

@@ -0,0 +1,36 @@
import { FANTOM_TOKENS, GEIST_FANTOM_POOLS } from './constants';
import { GeistInfo } from './types';
const gTokenToUnderlyingToken = new Map<string, string>([
[FANTOM_TOKENS.gFTM, FANTOM_TOKENS.WFTM],
[FANTOM_TOKENS.gfUSDT, FANTOM_TOKENS.fUSDT],
[FANTOM_TOKENS.gDAI, FANTOM_TOKENS.DAI],
[FANTOM_TOKENS.gUSDC, FANTOM_TOKENS.USDC],
[FANTOM_TOKENS.gETH, FANTOM_TOKENS.WETH],
[FANTOM_TOKENS.gWBTC, FANTOM_TOKENS.WBTC],
[FANTOM_TOKENS.gCRV, FANTOM_TOKENS.WCRV],
[FANTOM_TOKENS.gMIM, FANTOM_TOKENS.MIM],
]);
export function getGeistInfoForPair(
takerToken: string,
makerToken: string,
): GeistInfo | undefined {
let gToken;
let underlyingToken;
if (gTokenToUnderlyingToken.get(takerToken) === makerToken) {
gToken = takerToken;
underlyingToken = makerToken;
} else if (gTokenToUnderlyingToken.get(makerToken) === takerToken) {
gToken = makerToken;
underlyingToken = takerToken;
} else {
return undefined;
}
return {
lendingPool: GEIST_FANTOM_POOLS.lendingPool,
gToken,
underlyingToken,
};
}

View File

@@ -18,6 +18,7 @@ import {
ERC20BridgeSource,
FillData,
FinalUniswapV3FillData,
GeistFillData,
GenericRouterFillData,
KyberDmmFillData,
KyberFillData,
@@ -202,6 +203,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
return encodeBridgeSourceId(BridgeProtocol.AaveV2, 'AaveV2');
case ERC20BridgeSource.Compound:
return encodeBridgeSourceId(BridgeProtocol.Compound, 'Compound');
case ERC20BridgeSource.Geist:
return encodeBridgeSourceId(BridgeProtocol.AaveV2, 'Geist');
default:
throw new Error(AggregationError.NoBridgeForSource);
}
@@ -356,6 +359,10 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
const compoundFillData = (order as OptimizedMarketBridgeOrder<CompoundFillData>).fillData;
bridgeData = encoder.encode([compoundFillData.cToken]);
break;
case ERC20BridgeSource.Geist:
const geistFillData = (order as OptimizedMarketBridgeOrder<GeistFillData>).fillData;
bridgeData = encoder.encode([geistFillData.lendingPool, geistFillData.gToken]);
break;
default:
throw new Error(AggregationError.NoBridgeForSource);
@@ -525,6 +532,7 @@ export const BRIDGE_ENCODERS: {
[ERC20BridgeSource.Lido]: AbiEncoder.create('(address)'),
[ERC20BridgeSource.AaveV2]: AbiEncoder.create('(address,address)'),
[ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'),
[ERC20BridgeSource.Geist]: AbiEncoder.create('(address,address)'),
};
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {

View File

@@ -4,6 +4,7 @@ import { BigNumber, logUtils } from '@0x/utils';
import * as _ from 'lodash';
import { AaveV2Sampler } from '../../noop_samplers/AaveV2Sampler';
import { GeistSampler } from '../../noop_samplers/GeistSampler';
import { SamplerCallResult, SignedNativeOrder } from '../../types';
import { ERC20BridgeSamplerContract } from '../../wrappers';
@@ -46,6 +47,7 @@ import {
UNISWAPV3_CONFIG_BY_CHAIN_ID,
ZERO_AMOUNT,
} from './constants';
import { getGeistInfoForPair } from './geist_utils';
import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
import { getIntermediateTokens } from './multihop_utils';
import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache';
@@ -66,6 +68,8 @@ import {
DexSample,
DODOFillData,
ERC20BridgeSource,
GeistFillData,
GeistInfo,
GenericRouterFillData,
HopInfo,
KyberDmmFillData,
@@ -1151,6 +1155,34 @@ export class SamplerOperations {
});
}
// tslint:disable-next-line:prefer-function-over-method
public getGeistSellQuotes(
geistInfo: GeistInfo,
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<GeistFillData> {
return new SamplerNoOperation({
source: ERC20BridgeSource.Geist,
fillData: { ...geistInfo, takerToken },
callback: () => GeistSampler.sampleSellsFromGeist(geistInfo, takerToken, makerToken, takerFillAmounts),
});
}
// tslint:disable-next-line:prefer-function-over-method
public getGeistBuyQuotes(
geistInfo: GeistInfo,
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): SourceQuoteOperation<GeistFillData> {
return new SamplerNoOperation({
source: ERC20BridgeSource.Geist,
fillData: { ...geistInfo, takerToken },
callback: () => GeistSampler.sampleBuysFromGeist(geistInfo, takerToken, makerToken, makerFillAmounts),
});
}
public getCompoundSellQuotes(
cToken: string,
makerToken: string,
@@ -1548,6 +1580,13 @@ export class SamplerOperations {
};
return this.getAaveV2SellQuotes(info, makerToken, takerToken, takerFillAmounts);
}
case ERC20BridgeSource.Geist: {
const info: GeistInfo | undefined = getGeistInfoForPair(takerToken, makerToken);
if (!info) {
return [];
}
return this.getGeistSellQuotes(info, makerToken, takerToken, takerFillAmounts);
}
case ERC20BridgeSource.Compound: {
if (!this.compoundCTokenCache) {
return [];
@@ -1578,7 +1617,7 @@ export class SamplerOperations {
takerToken: string,
makerFillAmounts: BigNumber[],
): SourceQuoteOperation[] {
// Find the adjacent tokens in the provided tooken adjacency graph,
// Find the adjacent tokens in the provided token adjacency graph,
// e.g if this is DAI->USDC we may check for DAI->WETH->USDC
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, this.tokenAdjacencyGraph);
const _sources = BATCH_SOURCE_FILTERS.getAllowed(sources);
@@ -1849,6 +1888,13 @@ export class SamplerOperations {
};
return this.getAaveV2BuyQuotes(info, makerToken, takerToken, makerFillAmounts);
}
case ERC20BridgeSource.Geist: {
const info: GeistInfo | undefined = getGeistInfoForPair(takerToken, makerToken);
if (!info) {
return [];
}
return this.getGeistBuyQuotes(info, makerToken, takerToken, makerFillAmounts);
}
case ERC20BridgeSource.Compound: {
if (!this.compoundCTokenCache) {
return [];

View File

@@ -102,6 +102,7 @@ export enum ERC20BridgeSource {
SpookySwap = 'SpookySwap',
Beethovenx = 'Beethovenx',
MorpheusSwap = 'MorpheusSwap',
Geist = 'Geist',
}
export type SourcesWithPoolsCache =
| ERC20BridgeSource.Balancer
@@ -181,6 +182,12 @@ export interface AaveV2Info {
underlyingToken: string;
}
export interface GeistInfo {
lendingPool: string;
gToken: string;
underlyingToken: string;
}
// Internal `fillData` field for `Fill` objects.
export interface FillData {}
@@ -301,6 +308,13 @@ export interface CompoundFillData extends FillData {
makerToken: string;
}
export interface GeistFillData extends FillData {
lendingPool: string;
gToken: string;
underlyingToken: string;
takerToken: string;
}
/**
* Represents a node on a fill path.
*/