diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 168d5f3b00..cc4d84e97a 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -129,6 +129,10 @@ { "note": "Fix Balancer sampling", "pr": 2711 + }, + { + "note": "Respect max slippage in EP consumer", + "pr": 2712 } ] }, diff --git a/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts b/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts index 3ff074ac04..a53a4a8be0 100644 --- a/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts +++ b/packages/asset-swapper/src/quote_consumers/exchange_proxy_swap_quote_consumer.ts @@ -30,6 +30,8 @@ import { assert } from '../utils/assert'; import { ERC20BridgeSource, UniswapV2FillData } from '../utils/market_operation_utils/types'; import { getTokenFromAssetData } from '../utils/utils'; +import { getMinBuyAmount } from './utils'; + // tslint:disable-next-line:custom-no-magic-numbers const MAX_UINT256 = new BigNumber(2).pow(256).minus(1); const { NULL_ADDRESS, ZERO_AMOUNT } = constants; @@ -93,6 +95,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { const sellToken = getTokenFromAssetData(quote.takerAssetData); const buyToken = getTokenFromAssetData(quote.makerAssetData); const sellAmount = quote.worstCaseQuoteInfo.totalTakerAssetAmount; + const minBuyAmount = getMinBuyAmount(quote); // VIP routes. if (isDirectUniswapCompatible(quote, optsWithDefaults)) { @@ -111,7 +114,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { return a; }), sellAmount, - quote.worstCaseQuoteInfo.makerAssetAmount, + minBuyAmount, source === ERC20BridgeSource.SushiSwap, ) .getABIEncodedTransactionData(), @@ -225,7 +228,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase { }), }); - const minBuyAmount = BigNumber.max(0, quote.worstCaseQuoteInfo.makerAssetAmount.minus(buyTokenFeeAmount)); const calldataHexString = this._exchangeProxy .transformERC20( isFromETH ? ETH_TOKEN_ADDRESS : sellToken, diff --git a/packages/asset-swapper/src/quote_consumers/utils.ts b/packages/asset-swapper/src/quote_consumers/utils.ts new file mode 100644 index 0000000000..f87f8318e1 --- /dev/null +++ b/packages/asset-swapper/src/quote_consumers/utils.ts @@ -0,0 +1,28 @@ +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; + +import { MarketOperation, SwapQuote } from '../types'; + +/** + * Compute the mminimum buy token amount for market operations by inferring + * the slippage from the orders in a quote. We cannot rely on + * `worstCaseQuoteInfo.makerAssetAmount` because that does not stop at + * maximum slippage. + */ +export function getMinBuyAmount(quote: SwapQuote): BigNumber { + // Infer the allowed maker asset slippage from the orders. + const totalOrderMakerAssetAmount = BigNumber.sum(...quote.orders.map(o => o.makerAssetAmount)); + const totalFillMakerAssetAmount = + quote.type === MarketOperation.Sell + ? BigNumber.sum(...quote.orders.map(o => BigNumber.sum(0, ...o.fills.map(f => f.output)))) + : BigNumber.sum(...quote.orders.map(o => BigNumber.sum(0, ...o.fills.map(f => f.input)))); + if (totalFillMakerAssetAmount.eq(0)) { + return quote.worstCaseQuoteInfo.makerAssetAmount; + } + if (totalOrderMakerAssetAmount.eq(totalFillMakerAssetAmount)) { + // No slippage allowed on bought tokens. + return quote.bestCaseQuoteInfo.makerAssetAmount; + } + const slipRatio = totalOrderMakerAssetAmount.div(totalFillMakerAssetAmount); + return quote.bestCaseQuoteInfo.makerAssetAmount.times(slipRatio).integerValue(BigNumber.ROUND_DOWN); +}