fix: slippage inconsistency when recalculated in exchange proxy quote consumer (#412)

* fix: Pass slippage down rather than recalculate due to accuracy

* CHANGELOG
This commit is contained in:
Jacob Evans 2022-02-04 10:37:38 +10:00 committed by GitHub
parent 0f701f42d3
commit 5d2cdb00c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 25 additions and 20 deletions

View File

@ -1,4 +1,13 @@
[ [
{
"version": "16.49.3",
"changes": [
{
"note": "Fix `slippage` inconsistency when recalculated in exchange proxy quote consumer",
"pr": 412
}
]
},
{ {
"version": "16.49.2", "version": "16.49.2",
"changes": [ "changes": [

View File

@ -691,7 +691,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): OptimizedMarketOrder[] { function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): OptimizedMarketOrder[] {
const slippage = getMaxQuoteSlippageRate(quote); const slippage = getMaxQuoteSlippageRate(quote);
if (!slippage) { if (slippage === 0) {
return quote.orders; return quote.orders;
} }
return quote.orders.map(o => { return quote.orders.map(o => {
@ -716,18 +716,5 @@ function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): O
} }
function getMaxQuoteSlippageRate(quote: MarketBuySwapQuote | MarketSellSwapQuote): number { function getMaxQuoteSlippageRate(quote: MarketBuySwapQuote | MarketSellSwapQuote): number {
if (quote.type === MarketOperation.Buy) { return quote.worstCaseQuoteInfo.slippage;
// (worstCaseTaker - bestCaseTaker) / bestCaseTaker
// where worstCaseTaker >= bestCaseTaker
return quote.worstCaseQuoteInfo.takerAmount
.minus(quote.bestCaseQuoteInfo.takerAmount)
.div(quote.bestCaseQuoteInfo.takerAmount)
.toNumber();
}
// (bestCaseMaker - worstCaseMaker) / bestCaseMaker
// where bestCaseMaker >= worstCaseMaker
return quote.bestCaseQuoteInfo.makerAmount
.minus(quote.worstCaseQuoteInfo.makerAmount)
.div(quote.bestCaseQuoteInfo.makerAmount)
.toNumber();
} }

View File

@ -578,8 +578,8 @@ function calculateQuoteInfo(
}); });
return { return {
bestCaseQuoteInfo: fillResultsToQuoteInfo(bestCaseFillResult), bestCaseQuoteInfo: fillResultsToQuoteInfo(bestCaseFillResult, 0),
worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult), worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult, slippage),
sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource), sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource),
}; };
} }
@ -600,6 +600,7 @@ function calculateTwoHopQuoteInfo(
}), }),
).toNumber(); ).toNumber();
const isSell = operation === MarketOperation.Sell; const isSell = operation === MarketOperation.Sell;
return { return {
bestCaseQuoteInfo: { bestCaseQuoteInfo: {
makerAmount: isSell ? secondHopFill.output : secondHopFill.input, makerAmount: isSell ? secondHopFill.output : secondHopFill.input,
@ -608,6 +609,7 @@ function calculateTwoHopQuoteInfo(
feeTakerTokenAmount: constants.ZERO_AMOUNT, feeTakerTokenAmount: constants.ZERO_AMOUNT,
protocolFeeInWeiAmount: constants.ZERO_AMOUNT, protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
gas, gas,
slippage: 0,
}, },
// TODO jacob consolidate this with quote simulation worstCase // TODO jacob consolidate this with quote simulation worstCase
worstCaseQuoteInfo: { worstCaseQuoteInfo: {
@ -616,13 +618,14 @@ function calculateTwoHopQuoteInfo(
: secondHopOrder.makerAmount, : secondHopOrder.makerAmount,
takerAmount: isSell takerAmount: isSell
? firstHopOrder.takerAmount ? firstHopOrder.takerAmount
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(), : firstHopOrder.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP),
totalTakerAmount: isSell totalTakerAmount: isSell
? firstHopOrder.takerAmount ? firstHopOrder.takerAmount
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(), : firstHopOrder.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP),
feeTakerTokenAmount: constants.ZERO_AMOUNT, feeTakerTokenAmount: constants.ZERO_AMOUNT,
protocolFeeInWeiAmount: constants.ZERO_AMOUNT, protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
gas, gas,
slippage,
}, },
sourceBreakdown: { sourceBreakdown: {
[ERC20BridgeSource.MultiHop]: { [ERC20BridgeSource.MultiHop]: {
@ -648,7 +651,7 @@ function getSwapQuoteOrdersBreakdown(fillAmountBySource: { [source: string]: Big
return breakdown; return breakdown;
} }
function fillResultsToQuoteInfo(fr: QuoteFillResult): SwapQuoteInfo { function fillResultsToQuoteInfo(fr: QuoteFillResult, slippage: number): SwapQuoteInfo {
return { return {
makerAmount: fr.totalMakerAssetAmount, makerAmount: fr.totalMakerAssetAmount,
takerAmount: fr.takerAssetAmount, takerAmount: fr.takerAssetAmount,
@ -656,6 +659,7 @@ function fillResultsToQuoteInfo(fr: QuoteFillResult): SwapQuoteInfo {
feeTakerTokenAmount: fr.takerFeeTakerAssetAmount, feeTakerTokenAmount: fr.takerFeeTakerAssetAmount,
protocolFeeInWeiAmount: fr.protocolFeeAmount, protocolFeeInWeiAmount: fr.protocolFeeAmount,
gas: fr.gas, gas: fr.gas,
slippage,
}; };
} }

View File

@ -208,6 +208,7 @@ export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
* makerTokenAmount: The amount of makerAsset that will be acquired through the swap. * makerTokenAmount: 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. * 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. * gas: Amount of estimated gas needed to fill the quote.
* slippage: Amount of slippage to allow for.
*/ */
export interface SwapQuoteInfo { export interface SwapQuoteInfo {
feeTakerTokenAmount: BigNumber; feeTakerTokenAmount: BigNumber;
@ -216,6 +217,7 @@ export interface SwapQuoteInfo {
makerAmount: BigNumber; makerAmount: BigNumber;
protocolFeeInWeiAmount: BigNumber; protocolFeeInWeiAmount: BigNumber;
gas: number; gas: number;
slippage: number;
} }
/** /**

View File

@ -125,6 +125,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
gas: Math.floor(Math.random() * 8e6), gas: Math.floor(Math.random() * 8e6),
protocolFeeInWeiAmount: getRandomAmount(), protocolFeeInWeiAmount: getRandomAmount(),
feeTakerTokenAmount: getRandomAmount(), feeTakerTokenAmount: getRandomAmount(),
slippage: 0,
}, },
worstCaseQuoteInfo: { worstCaseQuoteInfo: {
makerAmount: makerTokenFillAmount, makerAmount: makerTokenFillAmount,
@ -133,6 +134,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
gas: Math.floor(Math.random() * 8e6), gas: Math.floor(Math.random() * 8e6),
protocolFeeInWeiAmount: getRandomAmount(), protocolFeeInWeiAmount: getRandomAmount(),
feeTakerTokenAmount: getRandomAmount(), feeTakerTokenAmount: getRandomAmount(),
slippage: 0,
}, },
makerAmountPerEth: getRandomInteger(1, 1e9), makerAmountPerEth: getRandomInteger(1, 1e9),
takerAmountPerEth: getRandomInteger(1, 1e9), takerAmountPerEth: getRandomInteger(1, 1e9),

View File

@ -24,6 +24,7 @@ export async function getFullyFillableSwapQuoteWithNoFeesAsync(
totalTakerAmount: takerAmount, totalTakerAmount: takerAmount,
protocolFeeInWeiAmount: protocolFeePerOrder.times(orders.length), protocolFeeInWeiAmount: protocolFeePerOrder.times(orders.length),
gas: 200e3, gas: 200e3,
slippage: 0,
}; };
const breakdown = { const breakdown = {