From d0805d4bbba51a6e4b0113d3fd08192afec883bb Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Fri, 6 Mar 2020 18:40:38 -0500 Subject: [PATCH] `@0x/asset-swapper`: Rebase and fix some minor bugs. --- packages/asset-swapper/src/swap_quoter.ts | 3 +- .../market_operation_utils/create_order.ts | 234 ------------------ .../src/utils/market_operation_utils/index.ts | 3 +- .../utils/market_operation_utils/orders.ts | 18 +- .../market_operation_utils/path_optimizer.ts | 2 +- .../sampler_operations.ts | 8 +- .../test/market_operation_utils_test.ts | 6 +- .../test/swap_quote_calculator_test.ts | 3 +- 8 files changed, 25 insertions(+), 252 deletions(-) delete mode 100644 packages/asset-swapper/src/utils/market_operation_utils/create_order.ts diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index f98818457a..67d5361ffe 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -21,8 +21,9 @@ import { } from './types'; import { assert } from './utils/assert'; import { calculateLiquidity } from './utils/calculate_liquidity'; -import { DexOrderSampler, MarketOperationUtils } from './utils/market_operation_utils'; +import { MarketOperationUtils } from './utils/market_operation_utils'; import { createDummyOrderForSampler } from './utils/market_operation_utils/orders'; +import { DexOrderSampler } from './utils/market_operation_utils/sampler'; import { orderPrunerUtils } from './utils/order_prune_utils'; import { OrderStateUtils } from './utils/order_state_utils'; import { ProtocolFeeUtils } from './utils/protocol_fee_utils'; diff --git a/packages/asset-swapper/src/utils/market_operation_utils/create_order.ts b/packages/asset-swapper/src/utils/market_operation_utils/create_order.ts deleted file mode 100644 index 82a5f9b054..0000000000 --- a/packages/asset-swapper/src/utils/market_operation_utils/create_order.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { assert } from '@0x/assert'; -import { ContractAddresses } from '@0x/contract-addresses'; -import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils'; -import { SignedOrder } from '@0x/types'; -import { AbiEncoder, BigNumber } from '@0x/utils'; - -import { constants } from '../../constants'; - -import { constants as marketOperationUtilConstants } from './constants'; -import { - AggregationError, - CollapsedFill, - ERC20BridgeSource, - NativeCollapsedFill, - OptimizedMarketOrder, - OrderDomain, -} from './types'; - -const { NULL_BYTES, NULL_ADDRESS, ZERO_AMOUNT } = constants; -const { INFINITE_TIMESTAMP_SEC, WALLET_SIGNATURE } = marketOperationUtilConstants; - -export class CreateOrderUtils { - private readonly _contractAddress: ContractAddresses; - - // utility function for asset-swapper to ignore market operation utils for specific asset types - public static convertNativeOrderToFullyFillableOptimizedOrders(order: SignedOrder): OptimizedMarketOrder { - return { - ...order, - fillableMakerAssetAmount: order.makerAssetAmount, - fillableTakerAssetAmount: order.takerAssetAmount, - fillableTakerFeeAmount: order.takerFee, - fill: { - source: ERC20BridgeSource.Native, - totalMakerAssetAmount: order.makerAssetAmount, - totalTakerAssetAmount: order.takerAssetAmount, - subFills: [], - }, - }; - } - - constructor(contractAddress: ContractAddresses) { - this._contractAddress = contractAddress; - } - - // Convert sell fills into orders. - public createSellOrdersFromPath( - orderDomain: OrderDomain, - inputToken: string, - outputToken: string, - path: CollapsedFill[], - bridgeSlippage: number, - liquidityProviderAddress?: string, - ): OptimizedMarketOrder[] { - const orders: OptimizedMarketOrder[] = []; - for (const fill of path) { - if (fill.source === ERC20BridgeSource.Native) { - orders.push(createNativeOrder(fill)); - } else { - orders.push( - createBridgeOrder( - orderDomain, - fill, - this._getBridgeAddressFromSource(fill.source, liquidityProviderAddress), - outputToken, - inputToken, - bridgeSlippage, - ), - ); - } - } - return orders; - } - - // Convert buy fills into orders. - public createBuyOrdersFromPath( - orderDomain: OrderDomain, - inputToken: string, - outputToken: string, - path: CollapsedFill[], - bridgeSlippage: number, - liquidityProviderAddress?: string, - ): OptimizedMarketOrder[] { - const orders: OptimizedMarketOrder[] = []; - for (const fill of path) { - if (fill.source === ERC20BridgeSource.Native) { - orders.push(createNativeOrder(fill)); - } else { - orders.push( - createBridgeOrder( - orderDomain, - fill, - this._getBridgeAddressFromSource(fill.source, liquidityProviderAddress), - inputToken, - outputToken, - bridgeSlippage, - true, - ), - ); - } - } - return orders; - } - - private _getBridgeAddressFromSource(source: ERC20BridgeSource, liquidityProviderAddress?: string): string { - switch (source) { - case ERC20BridgeSource.Eth2Dai: - return this._contractAddress.eth2DaiBridge; - case ERC20BridgeSource.Kyber: - return this._contractAddress.kyberBridge; - case ERC20BridgeSource.Uniswap: - return this._contractAddress.uniswapBridge; - case ERC20BridgeSource.CurveUsdcDai: - case ERC20BridgeSource.CurveUsdcDaiUsdt: - case ERC20BridgeSource.CurveUsdcDaiUsdtTusd: - case ERC20BridgeSource.CurveUsdcDaiUsdtBusd: - return this._contractAddress.curveBridge; - case ERC20BridgeSource.LiquidityProvider: - if (liquidityProviderAddress === undefined) { - throw new Error( - 'Cannot create a LiquidityProvider order without a LiquidityProvider pool address.', - ); - } - assert.isETHAddressHex('liquidityProviderAddress', liquidityProviderAddress); - return liquidityProviderAddress; - default: - break; - } - throw new Error(AggregationError.NoBridgeForSource); - } -} - -function createBridgeOrder( - orderDomain: OrderDomain, - fill: CollapsedFill, - bridgeAddress: string, - makerToken: string, - takerToken: string, - slippage: number, - isBuy: boolean = false, -): OptimizedMarketOrder { - let makerAssetData; - if (Object.keys(constants.DEFAULT_CURVE_OPTS).includes(fill.source)) { - const { curveAddress, tokens, version } = constants.DEFAULT_CURVE_OPTS[fill.source]; - const fromTokenIdx = tokens.indexOf(takerToken); - const toTokenIdx = tokens.indexOf(makerToken); - makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( - makerToken, - bridgeAddress, - createCurveBridgeData(curveAddress, fromTokenIdx, toTokenIdx, version), - ); - } else { - makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( - makerToken, - bridgeAddress, - createBridgeData(takerToken), - ); - } - return { - makerAddress: bridgeAddress, - makerAssetData, - takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken), - ...createCommonOrderFields(orderDomain, fill, slippage, isBuy), - }; -} - -function createBridgeData(tokenAddress: string): string { - const encoder = AbiEncoder.create([{ name: 'tokenAddress', type: 'address' }]); - return encoder.encode({ tokenAddress }); -} - -function createCurveBridgeData( - curveAddress: string, - fromTokenIdx: number, - toTokenIdx: number, - version: number, -): string { - const curveBridgeDataEncoder = AbiEncoder.create([ - { name: 'curveAddress', type: 'address' }, - { name: 'fromTokenIdx', type: 'int128' }, - { name: 'toTokenIdx', type: 'int128' }, - { name: 'version', type: 'int128' }, - ]); - return curveBridgeDataEncoder.encode([curveAddress, fromTokenIdx, toTokenIdx, version]); -} - -type CommonOrderFields = Pick< - OptimizedMarketOrder, - Exclude ->; - -function createCommonOrderFields( - orderDomain: OrderDomain, - fill: CollapsedFill, - slippage: number, - isBuy: boolean = false, -): CommonOrderFields { - const makerAssetAmountAdjustedWithSlippage = isBuy - ? fill.totalMakerAssetAmount - : fill.totalMakerAssetAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN); - const takerAssetAmountAdjustedWithSlippage = isBuy - ? fill.totalTakerAssetAmount.times(slippage + 1).integerValue(BigNumber.ROUND_UP) - : fill.totalTakerAssetAmount; - return { - fill, - takerAddress: NULL_ADDRESS, - senderAddress: NULL_ADDRESS, - feeRecipientAddress: NULL_ADDRESS, - salt: generatePseudoRandomSalt(), - expirationTimeSeconds: INFINITE_TIMESTAMP_SEC, - makerFeeAssetData: NULL_BYTES, - takerFeeAssetData: NULL_BYTES, - makerFee: ZERO_AMOUNT, - takerFee: ZERO_AMOUNT, - makerAssetAmount: makerAssetAmountAdjustedWithSlippage, - fillableMakerAssetAmount: makerAssetAmountAdjustedWithSlippage, - takerAssetAmount: takerAssetAmountAdjustedWithSlippage, - fillableTakerAssetAmount: takerAssetAmountAdjustedWithSlippage, - fillableTakerFeeAmount: ZERO_AMOUNT, - signature: WALLET_SIGNATURE, - ...orderDomain, - }; -} - -function createNativeOrder(fill: CollapsedFill): OptimizedMarketOrder { - return { - fill: { - source: fill.source, - totalMakerAssetAmount: fill.totalMakerAssetAmount, - totalTakerAssetAmount: fill.totalTakerAssetAmount, - subFills: fill.subFills, - }, - ...(fill as NativeCollapsedFill).nativeOrder, - }; -} diff --git a/packages/asset-swapper/src/utils/market_operation_utils/index.ts b/packages/asset-swapper/src/utils/market_operation_utils/index.ts index 67ea911937..2d8ac2e747 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -20,8 +20,6 @@ import { OrderDomain, } from './types'; -export { DexOrderSampler } from './sampler'; - export class MarketOperationUtils { private readonly _wethAddress: string; @@ -298,6 +296,7 @@ export class MarketOperationUtils { orderDomain: this._orderDomain, contractAddresses: this.contractAddresses, bridgeSlippage: opts.bridgeSlippage || 0, + liquidityProviderAddress: opts.liquidityProviderAddress, }); } 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 35f41f94fb..e4e082b2b9 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -119,6 +119,7 @@ export interface CreateOrderFromPathOpts { orderDomain: OrderDomain; contractAddresses: ContractAddresses; bridgeSlippage: number; + liquidityProviderAddress?: string; } // Convert sell fills into orders. @@ -135,19 +136,24 @@ export function createOrdersFromPath(path: Fill[], opts: CreateOrderFromPathOpts return orders; } -function getBridgeAddressFromSource(source: ERC20BridgeSource, contractAddresses: ContractAddresses): string { +function getBridgeAddressFromSource(source: ERC20BridgeSource, opts: CreateOrderFromPathOpts): string { switch (source) { case ERC20BridgeSource.Eth2Dai: - return contractAddresses.eth2DaiBridge; + return opts.contractAddresses.eth2DaiBridge; case ERC20BridgeSource.Kyber: - return contractAddresses.kyberBridge; + return opts.contractAddresses.kyberBridge; case ERC20BridgeSource.Uniswap: - return contractAddresses.uniswapBridge; + return opts.contractAddresses.uniswapBridge; case ERC20BridgeSource.CurveUsdcDai: case ERC20BridgeSource.CurveUsdcDaiUsdt: case ERC20BridgeSource.CurveUsdcDaiUsdtTusd: case ERC20BridgeSource.CurveUsdcDaiUsdtBusd: - return contractAddresses.curveBridge; + return opts.contractAddresses.curveBridge; + case ERC20BridgeSource.LiquidityProvider: + if (opts.liquidityProviderAddress === undefined) { + throw new Error('Cannot create a LiquidityProvider order without a LiquidityProvider pool address.'); + } + return opts.liquidityProviderAddress; default: break; } @@ -157,7 +163,7 @@ function getBridgeAddressFromSource(source: ERC20BridgeSource, contractAddresses function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts): OptimizedMarketOrder { const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken; const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken; - const bridgeAddress = getBridgeAddressFromSource(fill.source, opts.contractAddresses); + const bridgeAddress = getBridgeAddressFromSource(fill.source, opts); let makerAssetData; if (Object.keys(DEFAULT_CURVE_OPTS).includes(fill.source)) { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts b/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts index 61893ca26f..ef2feb51fe 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/path_optimizer.ts @@ -30,7 +30,7 @@ const RATE_DECIMALS = 8; function hillClimbToOptimalPath(paths: Fill[][], targetInput: BigNumber): Fill[] { // Flatten and sort path fills by descending ADJUSTED rate. const fills = paths - .reduce((acc, p) => acc.concat(p)) + .reduce((acc, p) => acc.concat(p), []) .sort((a, b) => b.adjustedRate.dp(RATE_DECIMALS).comparedTo(a.adjustedRate.dp(RATE_DECIMALS))); // Build up a path by picking the next best, valid fill until we meet our input target. const path: Fill[] = []; 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 ebf444d42f..602c3f16ca 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 @@ -1,6 +1,6 @@ import { BigNumber, ERC20BridgeSource, SignedOrder } from '../..'; -import { constants } from '../../constants'; +import { DEFAULT_CURVE_OPTS } from './constants'; import { BatchedOperation, DexSample } from './types'; /** @@ -233,8 +233,8 @@ export const samplerOperations = { }, getLiquidityProviderFromRegistry( registryAddress: string, - takerToken: string, makerToken: string, + takerToken: string, ): BatchedOperation { return { encodeCall: contract => { @@ -263,8 +263,8 @@ export const samplerOperations = { batchedOperation = samplerOperations.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts); } else if (source === ERC20BridgeSource.Kyber) { batchedOperation = samplerOperations.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts); - } else if (Object.keys(constants.DEFAULT_CURVE_OPTS).includes(source)) { - const { curveAddress, tokens } = constants.DEFAULT_CURVE_OPTS[source]; + } else if (Object.keys(DEFAULT_CURVE_OPTS).includes(source)) { + const { curveAddress, tokens } = DEFAULT_CURVE_OPTS[source]; const fromTokenIdx = tokens.indexOf(takerToken); const toTokenIdx = tokens.indexOf(makerToken); if (fromTokenIdx !== -1 && toTokenIdx !== -1) { diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 16871bd873..93ea1ef819 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -652,7 +652,7 @@ describe('MarketOperationUtils tests', () => { }), ], Web3Wrapper.toBaseUnitAmount(10, 18), - { excludedSources: SELL_SOURCES, numSamples: 4 }, + { excludedSources: SELL_SOURCES, numSamples: 4, bridgeSlippage: 0 }, ); expect(result.length).to.eql(1); expect(result[0].makerAddress).to.eql(liquidityProviderAddress); @@ -667,8 +667,8 @@ describe('MarketOperationUtils tests', () => { expect(getSellQuotesParams.sources).contains(ERC20BridgeSource.LiquidityProvider); expect(getSellQuotesParams.liquidityProviderAddress).is.eql(registryAddress); expect(getLiquidityProviderParams.registryAddress).is.eql(registryAddress); - expect(getLiquidityProviderParams.makerToken).is.eql(xAsset); - expect(getLiquidityProviderParams.takerToken).is.eql(yAsset); + expect(getLiquidityProviderParams.makerToken).is.eql(yAsset); + expect(getLiquidityProviderParams.takerToken).is.eql(xAsset); }); }); diff --git a/packages/asset-swapper/test/swap_quote_calculator_test.ts b/packages/asset-swapper/test/swap_quote_calculator_test.ts index d63d1f8fba..6e5175625b 100644 --- a/packages/asset-swapper/test/swap_quote_calculator_test.ts +++ b/packages/asset-swapper/test/swap_quote_calculator_test.ts @@ -8,8 +8,9 @@ import 'mocha'; import { constants } from '../src/constants'; import { CalculateSwapQuoteOpts, SignedOrderWithFillableAmounts } from '../src/types'; -import { DexOrderSampler, MarketOperationUtils } from '../src/utils/market_operation_utils/'; +import { MarketOperationUtils } from '../src/utils/market_operation_utils/'; import { DEFAULT_GET_MARKET_ORDERS_OPTS, SELL_SOURCES } from '../src/utils/market_operation_utils/constants'; +import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler'; import { ProtocolFeeUtils } from '../src/utils/protocol_fee_utils'; import { SwapQuoteCalculator } from '../src/utils/swap_quote_calculator';