diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index fc88260319..97b7f5cdb2 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -169,6 +169,7 @@ export interface MarketBuySwapQuote extends SwapQuoteBase { * totalTakerAssetAmount: The total amount of takerAsset required to complete the swap (filling orders, and paying takerFees). * makerAssetAmount: The amount of makerAsset that will be acquired through the swap. * protocolFeeInWeiAmount: The amount of ETH to pay (in WEI) as protocol fee to perform the swap for desired asset. + * gas: Amount of estimated gas needed to fill the quote. */ export interface SwapQuoteInfo { feeTakerAssetAmount: BigNumber; @@ -176,6 +177,7 @@ export interface SwapQuoteInfo { totalTakerAssetAmount: BigNumber; makerAssetAmount: BigNumber; protocolFeeInWeiAmount: BigNumber; + gas: number; } /** diff --git a/packages/asset-swapper/src/utils/assert.ts b/packages/asset-swapper/src/utils/assert.ts index 73303fad85..377bdc6e88 100644 --- a/packages/asset-swapper/src/utils/assert.ts +++ b/packages/asset-swapper/src/utils/assert.ts @@ -82,6 +82,7 @@ export const assert = { ); }, isValidSwapQuoteInfo(variableName: string, swapQuoteInfo: SwapQuoteInfo): void { + sharedAssert.isNumber(`${variableName}.gas`, swapQuoteInfo.gas); sharedAssert.isBigNumber(`${variableName}.feeTakerAssetAmount`, swapQuoteInfo.feeTakerAssetAmount); sharedAssert.isBigNumber(`${variableName}.totalTakerAssetAmount`, swapQuoteInfo.totalTakerAssetAmount); sharedAssert.isBigNumber(`${variableName}.takerAssetAmount`, swapQuoteInfo.takerAssetAmount); 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 1612cb9de4..02b1042004 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -30,7 +30,8 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = { bridgeSlippage: 0.005, numSamples: 20, sampleDistributionBase: 1.05, - fees: {}, + feeSchedule: {}, + gasSchedule: {}, allowFallback: true, }; diff --git a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts index 694d12aa92..41b7493eda 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/fills.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/fills.ts @@ -26,18 +26,18 @@ export function createFillPaths(opts: { targetInput?: BigNumber; ethToOutputRate?: BigNumber; excludedSources?: ERC20BridgeSource[]; - fees?: { [source: string]: BigNumber }; + feeSchedule?: { [source: string]: BigNumber }; }): Fill[][] { const { side } = opts; const excludedSources = opts.excludedSources || []; - const fees = opts.fees || {}; + const feeSchedule = opts.feeSchedule || {}; const orders = opts.orders || []; const dexQuotes = opts.dexQuotes || []; const ethToOutputRate = opts.ethToOutputRate || ZERO_AMOUNT; // Create native fill paths. - const nativePath = nativeOrdersToPath(side, orders, ethToOutputRate, fees); + const nativePath = nativeOrdersToPath(side, orders, ethToOutputRate, feeSchedule); // Create DEX fill paths. - const dexPaths = dexQuotesToPaths(side, dexQuotes, ethToOutputRate, fees); + const dexPaths = dexQuotesToPaths(side, dexQuotes, ethToOutputRate, feeSchedule); return filterPaths([...dexPaths, nativePath].map(p => clipPathToInput(p, opts.targetInput)), excludedSources); } 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 dd3b7d46f2..055a27f01f 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -98,7 +98,7 @@ export class MarketOperationUtils { ethToOutputRate: ethToMakerAssetRate, bridgeSlippage: _opts.bridgeSlippage, excludedSources: _opts.excludedSources, - fees: _opts.fees, + feeSchedule: _opts.feeSchedule, allowFallback: _opts.allowFallback, }); } @@ -170,7 +170,7 @@ export class MarketOperationUtils { ethToOutputRate: ethToTakerAssetRate, bridgeSlippage: _opts.bridgeSlippage, excludedSources: _opts.excludedSources, - fees: _opts.fees, + feeSchedule: _opts.feeSchedule, allowFallback: _opts.allowFallback, }); } @@ -242,7 +242,7 @@ export class MarketOperationUtils { ethToOutputRate: ethToTakerAssetRate, bridgeSlippage: _opts.bridgeSlippage, excludedSources: _opts.excludedSources, - fees: _opts.fees, + feeSchedule: _opts.feeSchedule, allowFallback: _opts.allowFallback, }); }); @@ -260,7 +260,7 @@ export class MarketOperationUtils { ethToOutputRate?: BigNumber; bridgeSlippage?: number; excludedSources?: ERC20BridgeSource[]; - fees?: { [source: string]: BigNumber }; + feeSchedule?: { [source: string]: BigNumber }; allowFallback?: boolean; liquidityProviderAddress?: string; }): OptimizedMarketOrder[] { @@ -274,7 +274,7 @@ export class MarketOperationUtils { targetInput: inputAmount, ethToOutputRate: opts.ethToOutputRate, excludedSources: opts.excludedSources, - fees: opts.fees, + feeSchedule: opts.feeSchedule, }); // Find the optimal path. const optimalPath = findOptimalPath(side, paths, inputAmount, opts.runLimit); 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 b4e86e6d22..e6f33dd196 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -167,7 +167,11 @@ export interface GetMarketOrdersOpts { /** * Fees for each liquidity source, expressed in gas. */ - fees: { [source: string]: BigNumber }; + feeSchedule: { [source: string]: BigNumber }; + /** + * Estimated gas consumed by each liquidity source. + */ + gasSchedule: { [source: string]: number }; /** * Whether to pad the quote with a redundant fallback quote using different * sources. diff --git a/packages/asset-swapper/src/utils/swap_quote_calculator.ts b/packages/asset-swapper/src/utils/swap_quote_calculator.ts index 574ebfe014..5c57b4a488 100644 --- a/packages/asset-swapper/src/utils/swap_quote_calculator.ts +++ b/packages/asset-swapper/src/utils/swap_quote_calculator.ts @@ -106,6 +106,7 @@ export class SwapQuoteCalculator { operation, assetFillAmounts[i], gasPrice, + opts.gasSchedule, ); } else { return undefined; @@ -133,7 +134,7 @@ export class SwapQuoteCalculator { // Scale fees by gas price. const _opts = { ...opts, - fees: _.mapValues(opts.fees, (v, k) => v.times(gasPrice)), + fees: _.mapValues(opts.feeSchedule, v => v.times(gasPrice)), }; const firstOrderMakerAssetData = !!prunedOrders[0] @@ -169,6 +170,7 @@ export class SwapQuoteCalculator { operation, assetFillAmount, gasPrice, + opts.gasSchedule, ); } private async _createSwapQuoteAsync( @@ -178,17 +180,20 @@ export class SwapQuoteCalculator { operation: MarketOperation, assetFillAmount: BigNumber, gasPrice: BigNumber, + gasSchedule: { [source: string]: number }, ): Promise { const bestCaseQuoteInfo = await this._calculateQuoteInfoAsync( resultOrders, assetFillAmount, gasPrice, + gasSchedule, operation, ); const worstCaseQuoteInfo = await this._calculateQuoteInfoAsync( resultOrders, assetFillAmount, gasPrice, + gasSchedule, operation, true, ); @@ -226,14 +231,16 @@ export class SwapQuoteCalculator { orders: OptimizedMarketOrder[], assetFillAmount: BigNumber, gasPrice: BigNumber, + gasSchedule: { [source: string]: number }, operation: MarketOperation, worstCase: boolean = false, ): Promise { - if (operation === MarketOperation.Buy) { - return this._calculateMarketBuyQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase); - } else { - return this._calculateMarketSellQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase); - } + return { + ...(operation === MarketOperation.Buy + ? await this._calculateMarketBuyQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase) + : await this._calculateMarketSellQuoteInfoAsync(orders, assetFillAmount, gasPrice, worstCase)), + gas: getGasUsedByOrders(orders, gasSchedule), + }; } private async _calculateMarketSellQuoteInfoAsync( @@ -327,6 +334,7 @@ export class SwapQuoteCalculator { totalTakerAssetAmount: totalFeeTakerAssetAmount.plus(totalTakerAssetAmount), makerAssetAmount: totalMakerAssetAmount, protocolFeeInWeiAmount, + gas: 0, }; } @@ -416,6 +424,7 @@ export class SwapQuoteCalculator { totalTakerAssetAmount: totalFeeTakerAssetAmount.plus(totalTakerAssetAmount), makerAssetAmount: totalMakerAssetAmount, protocolFeeInWeiAmount, + gas: 0, }; } @@ -485,3 +494,12 @@ function getTakerAssetAmountBreakDown( takerAssetAmount: takerAssetAmountWithFees, }; } + +function getGasUsedByOrders(orders: OptimizedMarketOrder[], gasSchedule: { [source: string]: number }): number { + let totalUsage = 0; + for (const order of orders) { + totalUsage += gasSchedule[order.fill.source] || 0; + } + return totalUsage; +} +// tslint:disable: max-file-line-count diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 9ddae1a08d..b7f917556d 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -486,7 +486,7 @@ describe('MarketOperationUtils tests', () => { [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], }; - const fees = { + const feeSchedule = { [ERC20BridgeSource.Native]: FILL_AMOUNT.div(4) .times(nativeFeeRate) .dividedToIntegerBy(ETH_TO_MAKER_RATE), @@ -498,7 +498,7 @@ describe('MarketOperationUtils tests', () => { const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, - { ...DEFAULT_OPTS, numSamples: 4, fees }, + { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, ); const orderSources = improvedOrders.map(o => o.fill.source); const expectedSources = [ @@ -521,7 +521,7 @@ describe('MarketOperationUtils tests', () => { // Effectively [0.8, ~0.5, ~0, ~0] [ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2], }; - const fees = { + const feeSchedule = { [ERC20BridgeSource.Uniswap]: FILL_AMOUNT.div(4) .times(uniswapFeeRate) .dividedToIntegerBy(ETH_TO_MAKER_RATE), @@ -533,7 +533,7 @@ describe('MarketOperationUtils tests', () => { const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, - { ...DEFAULT_OPTS, numSamples: 4, fees }, + { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, ); const orderSources = improvedOrders.map(o => o.fill.source); const expectedSources = [ @@ -828,7 +828,7 @@ describe('MarketOperationUtils tests', () => { [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], }; - const fees = { + const feeSchedule = { [ERC20BridgeSource.Native]: FILL_AMOUNT.div(4) .times(nativeFeeRate) .dividedToIntegerBy(ETH_TO_TAKER_RATE), @@ -840,7 +840,7 @@ describe('MarketOperationUtils tests', () => { const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, - { ...DEFAULT_OPTS, numSamples: 4, fees }, + { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, ); const orderSources = improvedOrders.map(o => o.fill.source); const expectedSources = [ @@ -862,7 +862,7 @@ describe('MarketOperationUtils tests', () => { [ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2], [ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1], }; - const fees = { + const feeSchedule = { [ERC20BridgeSource.Uniswap]: FILL_AMOUNT.div(4) .times(uniswapFeeRate) .dividedToIntegerBy(ETH_TO_TAKER_RATE), @@ -874,7 +874,7 @@ describe('MarketOperationUtils tests', () => { const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), FILL_AMOUNT, - { ...DEFAULT_OPTS, numSamples: 4, fees }, + { ...DEFAULT_OPTS, numSamples: 4, feeSchedule }, ); const orderSources = improvedOrders.map(o => o.fill.source); const expectedSources = [ diff --git a/packages/asset-swapper/test/utils/swap_quote.ts b/packages/asset-swapper/test/utils/swap_quote.ts index 7321745f9d..c347602961 100644 --- a/packages/asset-swapper/test/utils/swap_quote.ts +++ b/packages/asset-swapper/test/utils/swap_quote.ts @@ -25,6 +25,7 @@ export async function getFullyFillableSwapQuoteWithNoFeesAsync( takerAssetAmount: totalTakerAssetAmount, totalTakerAssetAmount, protocolFeeInWeiAmount: await protocolFeeUtils.calculateWorstCaseProtocolFeeAsync(orders, gasPrice), + gas: 200e3, }; const breakdown = {