From 0450e430f1c9003140c2800657fb5c59b2749269 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Tue, 14 Jul 2020 16:32:36 -0400 Subject: [PATCH] `@0x/asset-swapper`: Support more varied curves. --- packages/asset-swapper/CHANGELOG.json | 6 +- packages/asset-swapper/src/index.ts | 2 + .../utils/market_operation_utils/constants.ts | 111 ++++++++++++------ .../market_operation_utils/curve_utils.ts | 9 +- .../utils/market_operation_utils/orders.ts | 19 ++- .../sampler_operations.ts | 68 +++++------ .../src/utils/market_operation_utils/types.ts | 37 ++++-- .../test/market_operation_utils_test.ts | 12 +- 8 files changed, 168 insertions(+), 96 deletions(-) diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index c8c823b9dd..a603c24b93 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -1,6 +1,6 @@ [ { - "version": "4.6.1", + "version": "4.7.0", "changes": [ { "note": "Allow an empty override for sampler overrides", @@ -9,6 +9,10 @@ { "note": "Potentially heavy CPU functions inside the optimizer now yield to the event loop. As such they are now async.", "pr": 2637 + }, + { + "note": "Support more varied curves", + "pr": 2633 } ] }, diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 20df2ce2ec..7fd68e151e 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -72,6 +72,7 @@ export { BalancerFillData, CollapsedFill, CurveFillData, + CurveInfo, ERC20BridgeSource, FeeSchedule, FillData, @@ -80,6 +81,7 @@ export { NativeFillData, OptimizedMarketOrder, UniswapV2FillData, + CurveFunctionSelectors, } from './utils/market_operation_utils/types'; export { ProtocolFeeUtils } from './utils/protocol_fee_utils'; export { QuoteRequestor } from './utils/quote_requestor'; diff --git a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts index 4fec2a348c..5ca3ddcd1d 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -1,6 +1,6 @@ import { BigNumber } from '@0x/utils'; -import { ERC20BridgeSource, FakeBuyOpts, GetMarketOrdersOpts } from './types'; +import { CurveFunctionSelectors, CurveInfo, ERC20BridgeSource, GetMarketOrdersOpts } from './types'; // tslint:disable: custom-no-magic-numbers @@ -42,11 +42,6 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = { shouldBatchBridgeOrders: true, }; -export const DEFAULT_FAKE_BUY_OPTS: FakeBuyOpts = { - targetSlippageBps: new BigNumber(5), - maxIterations: new BigNumber(5), -}; - /** * Sources to poll for ETH fee price estimates. */ @@ -60,34 +55,82 @@ export const FEE_QUOTE_SOURCES = [ /** * Mainnet Curve configuration */ -export const MAINNET_CURVE_CONTRACTS: { [curveAddress: string]: string[] } = { - '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - ], - '0x52ea46506b9cc5ef470c5bf89f17dc28bb35d85c': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - ], - '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - '0x0000000000085d4780b73119b644ae5ecd22b376', // TUSD - ], - '0x79a8c46dea5ada233abaffd40f3a0a2b1e5a4f27': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - '0x4fabb145d64652a948d72533023f6e7a623c7c53', // BUSD - ], - '0xa5407eae9ba41422680e2e00537571bcc53efbfd': [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - '0x57ab1ec28d129707052df4df418d58a2d46d5f51', // SUSD - ], +export const MAINNET_CURVE_INFOS: { [name: string]: CurveInfo } = { + // Busted? + // DaiUsdc: { + // exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + // sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + // buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + // poolAddress: '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56', + // tokens: ['0x6b175474e89094c44da98b954eedeac495271d0f', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'], + // }, + // Busted? + // DaiUsdcUsdt: { + // exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + // sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + // buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + // poolAddress: '0x52ea46506b9cc5ef470c5bf89f17dc28bb35d85c', + // tokens: [ + // '0x6b175474e89094c44da98b954eedeac495271d0f', + // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + // '0xdac17f958d2ee523a2206206994597c13d831ec7', + // ], + // }, + DaiUsdcUsdtTusd: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + poolAddress: '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51', + tokens: [ + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + '0x0000000000085d4780b73119b644ae5ecd22b376', + ], + }, + // Looks like it's dying. + DaiUsdcUsdtBusd: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + poolAddress: '0x79a8c46dea5ada233abaffd40f3a0a2b1e5a4f27', + tokens: [ + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + '0x4fabb145d64652a948d72533023f6e7a623c7c53', + ], + }, + DaiUsdcUsdtSusd: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying, + buyQuoteFunctionSelector: CurveFunctionSelectors.get_dx_underlying, + poolAddress: '0xa5407eae9ba41422680e2e00537571bcc53efbfd', + tokens: [ + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + '0x57ab1ec28d129707052df4df418d58a2d46d5f51', + ], + }, + RenbtcWbtc: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy, + buyQuoteFunctionSelector: CurveFunctionSelectors.None, + poolAddress: '0x93054188d876f558f4a66b2ef1d97d16edf0895b', + tokens: ['0xeb4c2781e4eba804ce9a9803c67d0893436bb27d', '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'], + }, + RenbtcWbtcSbtc: { + exchangeFunctionSelector: CurveFunctionSelectors.exchange, + sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy, + buyQuoteFunctionSelector: CurveFunctionSelectors.None, + poolAddress: '0x7fc77b5c7614e1533320ea6ddc2eb61fa00a9714', + tokens: [ + '0xeb4c2781e4eba804ce9a9803c67d0893436bb27d', + '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', + '0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6', + ], + }, }; export const ERC20_PROXY_ID = '0xf47261b0'; diff --git a/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts b/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts index 2af692acfe..182029e9fb 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts @@ -1,8 +1,7 @@ -import { MAINNET_CURVE_CONTRACTS } from './constants'; +import { MAINNET_CURVE_INFOS } from './constants'; +import { CurveInfo } from './types'; // tslint:disable completed-docs -export function getCurveAddressesForPair(takerToken: string, makerToken: string): string[] { - return Object.keys(MAINNET_CURVE_CONTRACTS).filter(a => - [makerToken, takerToken].every(t => MAINNET_CURVE_CONTRACTS[a].includes(t)), - ); +export function getCurveInfosForPair(takerToken: string, makerToken: string): CurveInfo[] { + return Object.values(MAINNET_CURVE_INFOS).filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t))); } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts index 66ecf6387d..abeba32e73 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -219,10 +219,11 @@ function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts): makerToken, bridgeAddress, createCurveBridgeData( - curveFillData.poolAddress, + curveFillData.curve.poolAddress, + curveFillData.curve.exchangeFunctionSelector, + takerToken, curveFillData.fromTokenIdx, curveFillData.toTokenIdx, - 1, // "version" ), ); break; @@ -341,17 +342,25 @@ function createBalancerBridgeData(takerToken: string, poolAddress: string): stri function createCurveBridgeData( curveAddress: string, + exchangeFunctionSelector: string, + takerToken: string, fromTokenIdx: number, toTokenIdx: number, - version: number, ): string { const curveBridgeDataEncoder = AbiEncoder.create([ { name: 'curveAddress', type: 'address' }, + { name: 'exchangeFunctionSelector', type: 'bytes4' }, + { name: 'fromTokenAddress', type: 'address' }, { name: 'fromTokenIdx', type: 'int128' }, { name: 'toTokenIdx', type: 'int128' }, - { name: 'version', type: 'int128' }, ]); - return curveBridgeDataEncoder.encode([curveAddress, fromTokenIdx, toTokenIdx, version]); + return curveBridgeDataEncoder.encode([ + curveAddress, + exchangeFunctionSelector, + takerToken, + fromTokenIdx, + toTokenIdx, + ]); } function createUniswapV2BridgeData(tokenAddressPath: string[]): string { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts index ec04b0c2d9..4090914a83 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts @@ -3,15 +3,14 @@ import * as _ from 'lodash'; import { BigNumber, ERC20BridgeSource, SignedOrder } from '../..'; import { BalancerPool, BalancerPoolsCache, computeBalancerBuyQuote, computeBalancerSellQuote } from './balancer_utils'; -import { DEFAULT_FAKE_BUY_OPTS, MAINNET_CURVE_CONTRACTS } from './constants'; -import { getCurveAddressesForPair } from './curve_utils'; +import { getCurveInfosForPair } from './curve_utils'; import { getMultiBridgeIntermediateToken } from './multibridge_utils'; import { BalancerFillData, BatchedOperation, CurveFillData, + CurveInfo, DexSample, - FakeBuyOpts, SourceQuoteOperation, UniswapV2FillData, } from './types'; @@ -58,17 +57,12 @@ export const samplerOperations = { }, }; }, - getKyberBuyQuotes( - makerToken: string, - takerToken: string, - makerFillAmounts: BigNumber[], - fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, - ): SourceQuoteOperation { + getKyberBuyQuotes(makerToken: string, takerToken: string, makerFillAmounts: BigNumber[]): SourceQuoteOperation { return { source: ERC20BridgeSource.Kyber, encodeCall: contract => { return contract - .sampleBuysFromKyberNetwork(takerToken, makerToken, makerFillAmounts, fakeBuyOpts) + .sampleBuysFromKyberNetwork(takerToken, makerToken, makerFillAmounts) .getABIEncodedTransactionData(); }, handleCallResultsAsync: async (contract, callResults) => { @@ -162,19 +156,12 @@ export const samplerOperations = { makerToken: string, takerToken: string, makerFillAmounts: BigNumber[], - fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, ): SourceQuoteOperation { return { source: ERC20BridgeSource.LiquidityProvider, encodeCall: contract => { return contract - .sampleBuysFromLiquidityProviderRegistry( - registryAddress, - takerToken, - makerToken, - makerFillAmounts, - fakeBuyOpts, - ) + .sampleBuysFromLiquidityProviderRegistry(registryAddress, takerToken, makerToken, makerFillAmounts) .getABIEncodedTransactionData(); }, handleCallResultsAsync: async (contract, callResults) => { @@ -237,7 +224,7 @@ export const samplerOperations = { }; }, getCurveSellQuotes( - curveAddress: string, + curve: CurveInfo, fromTokenIdx: number, toTokenIdx: number, takerFillAmounts: BigNumber[], @@ -245,14 +232,18 @@ export const samplerOperations = { return { source: ERC20BridgeSource.Curve, fillData: { - poolAddress: curveAddress, + curve, fromTokenIdx, toTokenIdx, }, encodeCall: contract => { return contract .sampleSellsFromCurve( - curveAddress, + { + poolAddress: curve.poolAddress, + sellQuoteFunctionSelector: curve.sellQuoteFunctionSelector, + buyQuoteFunctionSelector: curve.buyQuoteFunctionSelector, + }, new BigNumber(fromTokenIdx), new BigNumber(toTokenIdx), takerFillAmounts, @@ -265,7 +256,7 @@ export const samplerOperations = { }; }, getCurveBuyQuotes( - curveAddress: string, + curve: CurveInfo, fromTokenIdx: number, toTokenIdx: number, makerFillAmounts: BigNumber[], @@ -273,14 +264,18 @@ export const samplerOperations = { return { source: ERC20BridgeSource.Curve, fillData: { - poolAddress: curveAddress, + curve, fromTokenIdx, toTokenIdx, }, encodeCall: contract => { return contract .sampleBuysFromCurve( - curveAddress, + { + poolAddress: curve.poolAddress, + sellQuoteFunctionSelector: curve.sellQuoteFunctionSelector, + buyQuoteFunctionSelector: curve.buyQuoteFunctionSelector, + }, new BigNumber(fromTokenIdx), new BigNumber(toTokenIdx), makerFillAmounts, @@ -416,11 +411,11 @@ export const samplerOperations = { case ERC20BridgeSource.Kyber: return samplerOperations.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts); case ERC20BridgeSource.Curve: - return getCurveAddressesForPair(takerToken, makerToken).map(curveAddress => + return getCurveInfosForPair(takerToken, makerToken).map(curve => samplerOperations.getCurveSellQuotes( - curveAddress, - MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(takerToken), - MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(makerToken), + curve, + curve.tokens.indexOf(takerToken), + curve.tokens.indexOf(makerToken), takerFillAmounts, ), ); @@ -502,7 +497,6 @@ export const samplerOperations = { wethAddress: string, balancerPoolsCache?: BalancerPoolsCache, liquidityProviderRegistryAddress?: string, - fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, ): Promise> => { const subOps = _.flatten( await Promise.all( @@ -527,18 +521,13 @@ export const samplerOperations = { } return ops; case ERC20BridgeSource.Kyber: - return samplerOperations.getKyberBuyQuotes( - makerToken, - takerToken, - makerFillAmounts, - fakeBuyOpts, - ); + return samplerOperations.getKyberBuyQuotes(makerToken, takerToken, makerFillAmounts); case ERC20BridgeSource.Curve: - return getCurveAddressesForPair(takerToken, makerToken).map(curveAddress => + return getCurveInfosForPair(takerToken, makerToken).map(curve => samplerOperations.getCurveBuyQuotes( - curveAddress, - MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(takerToken), - MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(makerToken), + curve, + curve.tokens.indexOf(takerToken), + curve.tokens.indexOf(makerToken), makerFillAmounts, ), ); @@ -553,7 +542,6 @@ export const samplerOperations = { makerToken, takerToken, makerFillAmounts, - fakeBuyOpts, ); case ERC20BridgeSource.Balancer: if (balancerPoolsCache === undefined) { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 779e16947c..05709676c7 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -37,6 +37,32 @@ export enum ERC20BridgeSource { Balancer = 'Balancer', } +// tslint:disable: enum-naming +/** + * Curve contract function selectors. + */ +export enum CurveFunctionSelectors { + None = '0x00000000', + exchange = '0x3df02124', + exchange_underlying = '0xa6417ed6', + get_dy_underlying = '0x07211ef7', + get_dx_underlying = '0x0e71d1b9', + get_dy = '0x5e0d443f', + get_dx = '0x67df02ca', +} +// tslint:enable: enum-naming + +/** + * Configuration info on a Curve pool. + */ +export interface CurveInfo { + exchangeFunctionSelector: CurveFunctionSelectors; + sellQuoteFunctionSelector: CurveFunctionSelectors; + buyQuoteFunctionSelector: CurveFunctionSelectors; + poolAddress: string; + tokens: string[]; +} + // Internal `fillData` field for `Fill` objects. export interface FillData {} @@ -46,9 +72,9 @@ export interface NativeFillData extends FillData { } export interface CurveFillData extends FillData { - poolAddress: string; fromTokenIdx: number; toTokenIdx: number; + curve: CurveInfo; } export interface BalancerFillData extends FillData { @@ -228,12 +254,3 @@ export interface SourceQuoteOperation ext source: ERC20BridgeSource; fillData?: TFillData; } - -/** - * Used in the ERC20BridgeSampler when a source does not natively - * support sampling via a specific buy amount. - */ -export interface FakeBuyOpts { - targetSlippageBps: BigNumber; - maxIterations: BigNumber; -} diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 52f291fae1..765f2ae24c 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -302,7 +302,17 @@ describe('MarketOperationUtils tests', () => { const DEFAULT_FILL_DATA: FillDataBySource = { [ERC20BridgeSource.UniswapV2]: { tokenAddressPath: [] }, [ERC20BridgeSource.Balancer]: { poolAddress: randomAddress() }, - [ERC20BridgeSource.Curve]: { poolAddress: randomAddress(), fromTokenIdx: 0, toTokenIdx: 1 }, + [ERC20BridgeSource.Curve]: { + curve: { + poolAddress: randomAddress(), + tokens: [TAKER_TOKEN, MAKER_TOKEN], + exchangeFunctionSelector: hexUtils.random(4), + sellQuoteFunctionSelector: hexUtils.random(4), + buyQuoteFunctionSelector: hexUtils.random(4), + }, + fromTokenIdx: 0, + toTokenIdx: 1, + }, }; const DEFAULT_OPS = {