@0x/asset-swapper: Support more varied curves.

This commit is contained in:
Lawrence Forman 2020-07-14 16:32:36 -04:00 committed by Lawrence Forman
parent b6700af6a8
commit 0450e430f1
8 changed files with 168 additions and 96 deletions

View File

@ -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
}
]
},

View File

@ -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';

View File

@ -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';

View File

@ -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)));
}

View File

@ -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 {

View File

@ -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<BatchedOperation<DexSample[][]>> => {
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) {

View File

@ -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<TFillData extends FillData = FillData> 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;
}

View File

@ -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 = {