diff --git a/src/modes/routeProcessor.ts b/src/modes/routeProcessor.ts index 6c0f7725..48890675 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -48,6 +48,7 @@ export async function dryrun({ ethPrice, config, viemClient, + hasPriceMatch, }: { mode: number; config: BotConfig; @@ -61,6 +62,7 @@ export async function dryrun({ toToken: Token; fromToken: Token; maximumInput: BigNumber; + hasPriceMatch?: { value: boolean }; }) { const spanAttributes: SpanAttrs = {}; const result: DryrunResult = { @@ -112,6 +114,7 @@ export async function dryrun({ // exit early if market price is lower than order quote ratio if (price.lt(orderPairObject.takeOrders[0].quote!.ratio)) { + if (hasPriceMatch) hasPriceMatch.value = false; result.reason = RouteProcessorDryrunHaltReason.NoOpportunity; spanAttributes["error"] = "Order's ratio greater than market price"; return Promise.reject(result); @@ -333,6 +336,9 @@ export async function findOpp({ }; let noRoute = true; + const hasPriceMatch = { + value: true, + }; const initAmount = orderPairObject.takeOrders.reduce( (a, b) => a.add(b.quote!.maxOutput), ethers.constants.Zero, @@ -355,6 +361,7 @@ export async function findOpp({ ethPrice, config, viemClient, + hasPriceMatch, }); } catch (e: any) { // the fail reason can only be no route in case all hops fail reasons are no route @@ -362,38 +369,39 @@ export async function findOpp({ allNoneNodeErrors.push(e?.value?.noneNodeError); allHopsAttributes.push(JSON.stringify(e.spanAttributes)); } - const maxTradeSize = findMaxInput({ - orderPairObject, - dataFetcher, - fromToken, - toToken, - maximumInput, - gasPrice, - config, - }); - if (maxTradeSize) { - try { - return await dryrun({ - mode, - orderPairObject, - dataFetcher, - fromToken, - toToken, - signer, - maximumInput: maxTradeSize, - gasPrice, - arb, - ethPrice, - config, - viemClient, - }); - } catch (e: any) { - // the fail reason can only be no route in case all hops fail reasons are no route - if (e.reason !== RouteProcessorDryrunHaltReason.NoRoute) noRoute = false; - delete e.spanAttributes["error"]; - delete e.spanAttributes["rawtx"]; - allNoneNodeErrors.push(e?.value?.noneNodeError); - allHopsAttributes.push(JSON.stringify(e.spanAttributes)); + if (!hasPriceMatch.value) { + const maxTradeSize = findMaxInput({ + orderPairObject, + dataFetcher, + fromToken, + toToken, + maximumInput, + gasPrice, + config, + }); + if (maxTradeSize) { + try { + return await dryrun({ + mode, + orderPairObject, + dataFetcher, + fromToken, + toToken, + signer, + maximumInput: maxTradeSize, + gasPrice, + arb, + ethPrice, + config, + viemClient, + }); + } catch (e: any) { + // the fail reason can only be no route in case all hops fail reasons are no route + if (e.reason !== RouteProcessorDryrunHaltReason.NoRoute) noRoute = false; + delete e.spanAttributes["rawtx"]; + allNoneNodeErrors.push(e?.value?.noneNodeError); + allHopsAttributes.push(JSON.stringify(e.spanAttributes)); + } } } // in case of no successfull hop, allHopsAttributes will be included @@ -555,7 +563,7 @@ export function findMaxInput({ } else { const amountOut = scale18(route.amountOutBI, toToken.decimals); if (amountOut.gte(maximumOutput)) { - result.unshift(maximumInput); + result.unshift(scale18(maximumInput, fromToken.decimals)); maximumInput = maximumInput.add(initAmount.div(2 ** i)); } else { maximumInput = maximumInput.sub(initAmount.div(2 ** i)); diff --git a/test/mode-routeProcessor.test.js b/test/mode-routeProcessor.test.js index 65059702..8040e646 100644 --- a/test/mode-routeProcessor.test.js +++ b/test/mode-routeProcessor.test.js @@ -1,7 +1,7 @@ const { assert } = require("chai"); const testData = require("./data"); const { errorSnapshot } = require("../src/error"); -const { estimateProfit } = require("../src/utils"); +const { estimateProfit, clone } = require("../src/utils"); const { ethers, utils: { formatUnits }, @@ -331,18 +331,17 @@ describe("Test route processor find opp", async function () { dataFetcher.getCurrentPoolCodeMap = () => { return poolCodeMap; }; - // mock the signer to reject the first attempt on gas estimation - // so the dryrun goes into binary search - let rejectFirst = true; signer.estimateGas = async () => { - if (rejectFirst) { - rejectFirst = false; - return Promise.reject(ethers.errors.UNPREDICTABLE_GAS_LIMIT); - } else return gasLimitEstimation; + return gasLimitEstimation; }; + const orderPairObjectCopy = clone(orderPairObject); + orderPairObjectCopy.takeOrders[0].quote.ratio = ethers.utils.parseUnits("0.009900695135"); + orderPairObjectCopy.takeOrders[0].quote.maxOutput = ethers.BigNumber.from( + "1" + "0".repeat(25), + ); const result = await findOpp({ mode: 0, - orderPairObject, + orderPairObject: orderPairObjectCopy, dataFetcher, fromToken, toToken, @@ -355,7 +354,7 @@ describe("Test route processor find opp", async function () { }); const expectedTakeOrdersConfigStruct = { minimumInput: ethers.constants.One, - maximumInput: ethers.utils.parseUnits("9.999999701976776119"), + maximumInput: ethers.utils.parseUnits("9999999.701976776123046875"), maximumIORatio: ethers.constants.MaxUint256, orders: [orderPairObject.takeOrders[0].takeOrder], data: expectedRouteData, @@ -384,26 +383,28 @@ describe("Test route processor find opp", async function () { gasPrice, gas: gasLimitEstimation.toBigInt(), }, - maximumInput: ethers.utils.parseUnits("9.999999701976776119"), - price: getCurrentPrice(ethers.utils.parseUnits("9.999999701976776119")), + maximumInput: ethers.utils.parseUnits("9999999.701976776123046875"), + price: getCurrentPrice(ethers.utils.parseUnits("9999999.701976776123046875")), routeVisual: expectedRouteVisual, oppBlockNumber, estimatedProfit: estimateProfit( - orderPairObject, + orderPairObjectCopy, ethers.utils.parseUnits(ethPrice), undefined, undefined, - ethers.utils.parseUnits("0.996900529709950975"), - ethers.utils.parseUnits("9.999999701976776119"), + ethers.utils.parseUnits("0.009900695426163716"), + ethers.utils.parseUnits("9999999.701976776123046875"), ), }, reason: undefined, spanAttributes: { oppBlockNumber, foundOpp: true, - amountIn: "9.999999701976776119", - amountOut: "9.969005", - marketPrice: "0.996900529709950975", + amountIn: "9999999.701976776123046875", + amountOut: "99006.951311", + marketPrice: ethers.utils.formatUnits( + getCurrentPrice(ethers.utils.parseUnits("9999999.701976776123046875")), + ), route: expectedRouteVisual, }, }; @@ -472,7 +473,6 @@ describe("Test route processor find opp", async function () { spanAttributes: { hops: [ `{"amountIn":"${formatUnits(vaultBalance)}","amountOut":"${formatUnits(getAmountOut(vaultBalance), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"stage":1,"isNodeError":false,"error":${JSON.stringify(errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT))},"rawtx":${JSON.stringify(rawtx)}}`, - `{"amountIn":"9.999999701976776119","amountOut":"9.969005","marketPrice":"0.996900529709950975","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"stage":1,"isNodeError":false}`, ], }, }; @@ -669,7 +669,6 @@ describe("Test find opp with retries", async function () { spanAttributes: { hops: [ `{"amountIn":"${formatUnits(vaultBalance)}","amountOut":"${formatUnits(getAmountOut(vaultBalance), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"stage":1,"isNodeError":false,"error":${JSON.stringify(errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT))},"rawtx":${JSON.stringify(rawtx)}}`, - `{"amountIn":"9.999999701976776119","amountOut":"9.969005","marketPrice":"0.996900529709950975","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"stage":1,"isNodeError":false}`, ], }, };