diff --git a/src/strategy-management/Toolkit.ts b/src/strategy-management/Toolkit.ts index 6ffece1..0b5421d 100644 --- a/src/strategy-management/Toolkit.ts +++ b/src/strategy-management/Toolkit.ts @@ -224,7 +224,7 @@ export class Toolkit { const orders = await this._cache.getOrdersByPair(sourceToken, targetToken); const maxSourceAmountWei = Object.values(orders).reduce( - (acc, order) => acc.add(getEncodedTradeSourceAmount(order.y, order)), + (acc, order) => acc.add(getEncodedTradeSourceAmount(order.y, order, false)), BigNumber.from(0) ); const decimals = await this._decimals.fetchDecimals(sourceToken); diff --git a/src/trade-matcher/match.ts b/src/trade-matcher/match.ts index bd64441..8fc0ac0 100644 --- a/src/trade-matcher/match.ts +++ b/src/trade-matcher/match.ts @@ -18,16 +18,17 @@ import { sortByMaxRate, sortByMinRate } from './utils'; const rateBySourceAmount = ( sourceAmount: BigNumber, - order: EncodedOrder + order: EncodedOrder, + simulated: boolean ): Rate => { let input = sourceAmount; - let output = tradeTargetAmount(input, order); + let output = tradeTargetAmount(input, order, simulated); if (output.gt(order.y)) { - input = tradeSourceAmount(order.y, order); - output = tradeTargetAmount(input, order); + input = tradeSourceAmount(order.y, order, simulated); + output = tradeTargetAmount(input, order, simulated); while (output.gt(order.y)) { input = input.sub(1); - output = tradeTargetAmount(input, order); + output = tradeTargetAmount(input, order, simulated); } } return { input, output }; @@ -35,10 +36,11 @@ const rateBySourceAmount = ( const rateByTargetAmount = ( targetAmount: BigNumber, - order: EncodedOrder + order: EncodedOrder, + simulated: boolean ): Rate => { const input = BigNumberMin(targetAmount, order.y); - const output = tradeSourceAmount(input, order); + const output = tradeSourceAmount(input, order, simulated); return { input, output }; }; @@ -63,7 +65,7 @@ const equalTargetAmount = (order: EncodedOrder, limit: BigNumber) => { }; const equalSourceAmount = (order: EncodedOrder, limit: BigNumber) => { - return tradeSourceAmount(equalTargetAmount(order, limit), order); + return tradeSourceAmount(equalTargetAmount(order, limit), order, false); }; /** @@ -79,13 +81,14 @@ const equalSourceAmount = (order: EncodedOrder, limit: BigNumber) => { const sortedQuotes = ( amount: BigNumber, ordersMap: OrdersMap, - trade: (amount: BigNumber, order: EncodedOrder) => Rate, - sort: (x: Rate, y: Rate) => number + trade: (amount: BigNumber, order: EncodedOrder, simulated: boolean) => Rate, + sort: (x: Rate, y: Rate) => number, + simulated: boolean ): Quote[] => Object.keys(ordersMap) .map((id) => ({ id: BigNumber.from(id), - rate: trade(amount, ordersMap[id]), + rate: trade(amount, ordersMap[id], simulated), })) .sort((a, b) => sort(a.rate, b.rate)); @@ -102,7 +105,8 @@ const matchFast = ( ordersMap: OrdersMap, quotes: Quote[], filter: Filter, - trade: (amount: BigNumber, order: EncodedOrder) => Rate + trade: (amount: BigNumber, order: EncodedOrder, simulated: boolean) => Rate, + simulated: boolean ): MatchAction[] => { const actions: MatchAction[] = []; @@ -128,7 +132,7 @@ const matchFast = ( } /* if (amount.lt(rate.input)) */ else { const adjustedRate: Rate = { input: amount, - output: trade(amount, ordersMap[quote.id.toString()]).output, + output: trade(amount, ordersMap[quote.id.toString()], simulated).output, }; if (filter(adjustedRate)) { actions.push({ @@ -159,8 +163,9 @@ const matchBest = ( ordersMap: OrdersMap, quotes: Quote[], filter: Filter, - trade: (amount: BigNumber, order: EncodedOrder) => Rate, - equalize: (order: EncodedOrder, limit: BigNumber) => BigNumber + trade: (amount: BigNumber, order: EncodedOrder, simulated: boolean) => Rate, + equalize: (order: EncodedOrder, limit: BigNumber) => BigNumber, + simulated: boolean ): MatchAction[] => { const order0: EncodedOrder = { y: BigNumber.from(0), @@ -181,7 +186,7 @@ const matchBest = ( limit = getLimit(orders[n]); rates = orders .slice(0, n) - .map((order) => trade(equalize(order, limit), order)); + .map((order) => trade(equalize(order, limit), order, simulated)); total = rates.reduce((sum, rate) => sum.add(rate.input), BigNumber.from(0)); delta = total.sub(amount); if (delta.eq(0)) { @@ -194,7 +199,7 @@ const matchBest = ( limit = lo.add(hi).div(2); rates = orders .slice(0, n) - .map((order) => trade(equalize(order, limit), order)); + .map((order) => trade(equalize(order, limit), order, simulated)); total = rates.reduce( (sum, rate) => sum.add(rate.input), BigNumber.from(0) @@ -214,7 +219,7 @@ const matchBest = ( if (delta.gt(0)) { for (let i = rates.length - 1; i >= 0; i--) { - const rate = trade(rates[i].input.sub(delta), orders[i]); + const rate = trade(rates[i].input.sub(delta), orders[i], simulated); delta = delta.add(rate.input.sub(rates[i].input)); rates[i] = rate; if (delta.lte(0)) { @@ -223,7 +228,7 @@ const matchBest = ( } } else if (delta.lt(0)) { for (let i = 0; i <= rates.length - 1; i++) { - const rate = trade(rates[i].input.sub(delta), orders[i]); + const rate = trade(rates[i].input.sub(delta), orders[i], simulated); delta = delta.add(rate.input.sub(rates[i].input)); if (delta.gt(0)) { break; @@ -246,14 +251,15 @@ const matchBy = ( ordersMap: OrdersMap, matchTypes: MatchType[], filter: Filter, - trade: (amount: BigNumber, order: EncodedOrder) => Rate, + trade: (amount: BigNumber, order: EncodedOrder, simulated: boolean) => Rate, sort: (x: Rate, y: Rate) => number, - equalize: (order: EncodedOrder, limit: BigNumber) => BigNumber + equalize: (order: EncodedOrder, limit: BigNumber) => BigNumber, + simulated: boolean ): MatchOptions => { - const quotes = sortedQuotes(amount, ordersMap, trade, sort); + const quotes = sortedQuotes(amount, ordersMap, trade, sort, simulated); const res: MatchOptions = {}; if (matchTypes.includes(MatchType.Fast)) { - res[MatchType.Fast] = matchFast(amount, ordersMap, quotes, filter, trade); + res[MatchType.Fast] = matchFast(amount, ordersMap, quotes, filter, trade, simulated); } if (matchTypes.includes(MatchType.Best)) { res[MatchType.Best] = matchBest( @@ -262,7 +268,8 @@ const matchBy = ( quotes, filter, trade, - equalize + equalize, + simulated ); } return res; @@ -275,7 +282,8 @@ export const matchBySourceAmount = ( amount: BigNumber, ordersMap: OrdersMap, matchTypes: MatchType[], - filter: Filter = defaultFilter + filter: Filter = defaultFilter, + simulated: boolean = false ): MatchOptions => { return matchBy( amount, @@ -284,7 +292,8 @@ export const matchBySourceAmount = ( filter, rateBySourceAmount, sortByMinRate, - equalSourceAmount + equalSourceAmount, + simulated ); }; @@ -292,7 +301,8 @@ export const matchByTargetAmount = ( amount: BigNumber, ordersMap: OrdersMap, matchTypes: MatchType[], - filter: Filter = defaultFilter + filter: Filter = defaultFilter, + simulated: boolean = false ): MatchOptions => { return matchBy( amount, @@ -301,6 +311,7 @@ export const matchByTargetAmount = ( filter, rateByTargetAmount, sortByMaxRate, - equalTargetAmount + equalTargetAmount, + simulated ); }; diff --git a/src/trade-matcher/trade.ts b/src/trade-matcher/trade.ts index e792eef..9a15935 100644 --- a/src/trade-matcher/trade.ts +++ b/src/trade-matcher/trade.ts @@ -37,10 +37,16 @@ const getEncodedTradeBySourceAmount = ( z: BigNumber, A: BigNumber, B: BigNumber, - forceLegacyTradeBySourceRange: boolean = false + simulated: boolean ): BigNumber => { - const legacyTradeBySourceRange = - forceLegacyTradeBySourceRange || isLegacyTradeBySourceRange; + if (simulated) { + const temp1 = z.mul(C); + const temp2 = y.mul(A).add(z.mul(B)); + const temp3 = temp2.mul(x); + const temp4 = temp1.mul(temp1); + const temp5 = temp3.mul(A); + return mulDivF(temp2, temp3, temp4.add(temp5)); + } if (A.eq(0)) { return mulDivF(x, mul(B, B), mul(C, C)); @@ -62,7 +68,7 @@ const getEncodedTradeBySourceAmount = ( // Before - in a case of overflow it would end up with a 0 rate and not get // picked up for trade. This added check is to avoid the order being picked up // for trade - just to be reverted in the case of overflow by the contract. - if (legacyTradeBySourceRange || temp4.add(temp5).lte(MAX_UINT256)) { + if (isLegacyTradeBySourceRange || temp4.add(temp5).lte(MAX_UINT256)) { return mulDivF(temp2, temp3.div(factor), temp4.add(temp5)); } return temp2.div(add(A, mulDivC(temp1, temp1, temp3))); @@ -78,8 +84,18 @@ const getEncodedTradeByTargetAmount = ( y: BigNumber, z: BigNumber, A: BigNumber, - B: BigNumber + B: BigNumber, + simulated: boolean ): BigNumber => { + if (simulated) { + const temp1 = z.mul(C); + const temp2 = y.mul(A).add(z.mul(B)); + const temp3 = temp2.sub(x.mul(A)); + const temp4 = temp1.mul(temp1); + const temp5 = temp2.mul(temp3); + return mulDivC(x, temp4, temp5); + } + if (A.eq(0)) { return mulDivC(x, mul(C, C), mul(B, B)); } @@ -131,7 +147,8 @@ const getDecodedTradeByTargetAmount = ( export const getEncodedTradeTargetAmount = ( amount: BigNumber, - order: EncodedOrder + order: EncodedOrder, + simulated: boolean ): BigNumber => { const x = amount; const y = order.y; @@ -139,7 +156,7 @@ export const getEncodedTradeTargetAmount = ( const A = decodeFloat(order.A); const B = decodeFloat(order.B); try { - return uint128(getEncodedTradeBySourceAmount(x, y, z, A, B)); + return uint128(getEncodedTradeBySourceAmount(x, y, z, A, B, simulated)); } catch (error) { return BigNumber.from(0); /* rate = zero / amount = zero */ } @@ -147,7 +164,8 @@ export const getEncodedTradeTargetAmount = ( export const getEncodedTradeSourceAmount = ( amount: BigNumber, - order: EncodedOrder + order: EncodedOrder, + simulated: boolean ): BigNumber => { const x = amount; const y = order.y; @@ -155,7 +173,7 @@ export const getEncodedTradeSourceAmount = ( const A = decodeFloat(order.A); const B = decodeFloat(order.B); try { - return uint128(getEncodedTradeByTargetAmount(x, y, z, A, B)); + return uint128(getEncodedTradeByTargetAmount(x, y, z, A, B, simulated)); } catch (error) { return MAX_UINT128; /* rate = amount / infinity = zero */ } diff --git a/tests/match.spec.ts b/tests/match.spec.ts index a40fa05..ec23a18 100644 --- a/tests/match.spec.ts +++ b/tests/match.spec.ts @@ -24,14 +24,15 @@ import BigPoolMatch from './data/BigPoolMatch.json' assert { type: 'json' }; import EthUsdcMatch from './data/EthUsdcMatch.json' assert { type: 'json' }; import SpecialMatch from './data/SpecialMatch.json' assert { type: 'json' }; -type TradeMethod = (amount: BigNumber, order: EncodedOrder) => BigNumber; +type TradeMethod = (amount: BigNumber, order: EncodedOrder, simulated: boolean) => BigNumber; type MatchMethod = 'matchBySourceAmount' | 'matchByTargetAmount'; type MatchFunction = ( amount: BigNumber, ordersMap: OrdersMap, matchTypes: MatchType[], - filter: Filter + filter: Filter, + simulated: boolean ) => MatchOptions; const methods: { @@ -122,7 +123,8 @@ describe('Match', () => { BigNumber.from(test.amount), ordersMap, Object.keys(test.actions) as MatchType[], - filter + filter, + false ); for (const matchType in actions) { expect(actions[matchType as MatchType]!.length).to.equal( @@ -145,7 +147,7 @@ describe('Match', () => { ).to.be.true; expect( action.output.eq( - trade(action.input, ordersMap[action.id.toNumber()]) + trade(action.input, ordersMap[action.id.toNumber()], false) ) ).to.be.true; }