From 224dc45dff2645ef6a83e1bf331c51a045878679 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 27 Jan 2025 20:21:17 +0000 Subject: [PATCH] update --- src/modes/index.ts | 19 ++++------ src/modes/interOrderbook.ts | 61 ++++++++++---------------------- src/modes/intraOrderbook.ts | 54 ++++++++++------------------ src/modes/routeProcessor.ts | 49 +++++++++---------------- src/processOrders.ts | 35 ++++++++++++++++-- src/utils.ts | 10 +++--- test/findOpp.test.js | 47 ++++++++++++------------ test/mode-interOrderbook.test.js | 17 +++++---- test/mode-intraOrderbook.test.js | 14 +++++--- test/mode-routeProcessor.test.js | 46 +++++++++++++----------- 10 files changed, 166 insertions(+), 186 deletions(-) diff --git a/src/modes/index.ts b/src/modes/index.ts index 7021ff1f..2a53796b 100644 --- a/src/modes/index.ts +++ b/src/modes/index.ts @@ -6,7 +6,6 @@ import { findOpp as findInterObOpp } from "./interOrderbook"; import { findOpp as findIntraObOpp } from "./intraOrderbook"; import { findOppWithRetries as findRpOpp } from "./routeProcessor"; import { BotConfig, BundledOrders, ViemClient, DryrunResult, SpanAttrs } from "../types"; -import { extendSpanAttributes } from "../utils"; /** * The main entrypoint for the main logic to find opps. @@ -117,24 +116,18 @@ export async function findOpp({ noneNodeError: undefined, }; if ((allResults[0] as any)?.reason?.spanAttributes) { - extendSpanAttributes( - spanAttributes, - JSON.stringify((allResults[0] as any).reason.spanAttributes), - "routeProcessor", + spanAttributes["routeProcessor"] = JSON.stringify( + (allResults[0] as any).reason.spanAttributes, ); } if ((allResults[1] as any)?.reason?.spanAttributes) { - extendSpanAttributes( - spanAttributes, - JSON.stringify((allResults[1] as any).reason.spanAttributes), - "intraOrderbook", + spanAttributes["intraOrderbook"] = JSON.stringify( + (allResults[1] as any).reason.spanAttributes["intraOrderbook"], ); } if ((allResults[2] as any)?.reason?.spanAttributes) { - extendSpanAttributes( - spanAttributes, - JSON.stringify((allResults[2] as any).reason.spanAttributes), - "interOrderbook", + spanAttributes["interOrderbook"] = JSON.stringify( + (allResults[2] as any).reason.spanAttributes, ); } if ((allResults[0] as any)?.reason?.value?.noneNodeError) { diff --git a/src/modes/interOrderbook.ts b/src/modes/interOrderbook.ts index 23b67b17..d6df0651 100644 --- a/src/modes/interOrderbook.ts +++ b/src/modes/interOrderbook.ts @@ -4,14 +4,8 @@ import { BigNumber, Contract, ethers } from "ethers"; import { containsNodeError, errorSnapshot } from "../error"; import { getBountyEnsureRainlang, parseRainlang } from "../task"; import { BaseError, ExecutionRevertedError, PublicClient } from "viem"; +import { ONE18, scale18To, estimateProfit, withBigintSerializer } from "../utils"; import { BotConfig, BundledOrders, ViemClient, DryrunResult, SpanAttrs } from "../types"; -import { - ONE18, - scale18To, - estimateProfit, - withBigintSerializer, - extendSpanAttributes, -} from "../utils"; /** * Executes a extimateGas call for an inter-orderbook arb() tx, to determine if the tx is successfull ot not @@ -127,21 +121,13 @@ export async function dryrun({ gasLimit = ethers.BigNumber.from(estimation.gas).mul(config.gasLimitMultiplier).div(100); // include dryrun headroom gas estimation in otel logs - extendSpanAttributes( - spanAttributes, - JSON.stringify({ - gasLimit: estimation.gas.toString(), - totalCost: estimation.totalGasCost.toString(), - gasPrice: estimation.gasPrice.toString(), - ...(config.isSpecialL2 - ? { - l1Cost: estimation.l1Cost.toString(), - l1GasPrice: estimation.l1GasPrice.toString(), - } - : {}), - }), - "gasEst.headroom", - ); + spanAttributes["gasEst.headroom.gasLimit"] = estimation.gas.toString(); + spanAttributes["gasEst.headroom.totalCost"] = estimation.totalGasCost.toString(); + spanAttributes["gasEst.headroom.gasPrice"] = estimation.gasPrice.toString(); + if (config.isSpecialL2) { + spanAttributes["gasEst.headroom.l1Cost"] = estimation.l1Cost.toString(); + spanAttributes["gasEst.headroom.l1GasPrice"] = estimation.l1GasPrice.toString(); + } } catch (e) { const isNodeError = containsNodeError(e as BaseError); const errMsg = errorSnapshot("", e); @@ -211,21 +197,13 @@ export async function dryrun({ gasCost = gasLimit.mul(gasPrice).add(estimation.l1Cost); // include dryrun final gas estimation in otel logs - extendSpanAttributes( - spanAttributes, - JSON.stringify({ - gasLimit: estimation.gas.toString(), - totalCost: estimation.totalGasCost.toString(), - gasPrice: estimation.gasPrice.toString(), - ...(config.isSpecialL2 - ? { - l1Cost: estimation.l1Cost.toString(), - l1GasPrice: estimation.l1GasPrice.toString(), - } - : {}), - }), - "gasEst.final", - ); + spanAttributes["gasEst.final.gasLimit"] = estimation.gas.toString(); + spanAttributes["gasEst.final.totalCost"] = estimation.totalGasCost.toString(); + spanAttributes["gasEst.final.gasPrice"] = estimation.gasPrice.toString(); + if (config.isSpecialL2) { + spanAttributes["gasEst.final.l1Cost"] = estimation.l1Cost.toString(); + spanAttributes["gasEst.final.l1GasPrice"] = estimation.l1GasPrice.toString(); + } task.evaluable.bytecode = await parseRainlang( await getBountyEnsureRainlang( ethers.utils.parseUnits(inputToEthPrice), @@ -422,13 +400,12 @@ export async function findOpp({ // } catch { // /**/ // } + const allOrderbooksAttributes: any = {}; for (let i = 0; i < e.errors.length; i++) { - extendSpanAttributes( - spanAttributes, - JSON.stringify(e.errors[i].spanAttributes), - "againstOrderbooks." + opposingOrderbookOrders[i].orderbook, - ); + allOrderbooksAttributes[opposingOrderbookOrders[i].orderbook] = + e.errors[i].spanAttributes; } + spanAttributes["againstOrderbooks"] = JSON.stringify(allOrderbooksAttributes); const noneNodeErrors = allNoneNodeErrors.filter((v) => !!v); if (allNoneNodeErrors.length && noneNodeErrors.length / allNoneNodeErrors.length > 0.5) { result.value = { diff --git a/src/modes/intraOrderbook.ts b/src/modes/intraOrderbook.ts index 69110ddf..b28125d5 100644 --- a/src/modes/intraOrderbook.ts +++ b/src/modes/intraOrderbook.ts @@ -3,8 +3,8 @@ import { estimateGasCost } from "../gas"; import { BigNumber, ethers } from "ethers"; import { containsNodeError, errorSnapshot } from "../error"; import { getWithdrawEnsureRainlang, parseRainlang } from "../task"; +import { estimateProfit, scale18, withBigintSerializer } from "../utils"; import { BaseError, erc20Abi, ExecutionRevertedError, PublicClient } from "viem"; -import { estimateProfit, scale18, withBigintSerializer, extendSpanAttributes } from "../utils"; import { SpanAttrs, BotConfig, @@ -119,21 +119,13 @@ export async function dryrun({ gasLimit = ethers.BigNumber.from(estimation.gas).mul(config.gasLimitMultiplier).div(100); // include dryrun headroom gas estimation in otel logs - extendSpanAttributes( - spanAttributes, - JSON.stringify({ - gasLimit: estimation.gas.toString(), - totalCost: estimation.totalGasCost.toString(), - gasPrice: estimation.gasPrice.toString(), - ...(config.isSpecialL2 - ? { - l1Cost: estimation.l1Cost.toString(), - l1GasPrice: estimation.l1GasPrice.toString(), - } - : {}), - }), - "gasEst.headroom", - ); + spanAttributes["gasEst.headroom.gasLimit"] = estimation.gas.toString(); + spanAttributes["gasEst.headroom.totalCost"] = estimation.totalGasCost.toString(); + spanAttributes["gasEst.headroom.gasPrice"] = estimation.gasPrice.toString(); + if (config.isSpecialL2) { + spanAttributes["gasEst.headroom.l1Cost"] = estimation.l1Cost.toString(); + spanAttributes["gasEst.headroom.l1GasPrice"] = estimation.l1GasPrice.toString(); + } } catch (e) { // reason, code, method, transaction, error, stack, message const isNodeError = containsNodeError(e as BaseError); @@ -213,21 +205,13 @@ export async function dryrun({ gasCost = gasLimit.mul(gasPrice).add(estimation.l1Cost); // include dryrun final gas estimation in otel logs - extendSpanAttributes( - spanAttributes, - JSON.stringify({ - gasLimit: estimation.gas.toString(), - totalCost: estimation.totalGasCost.toString(), - gasPrice: estimation.gasPrice.toString(), - ...(config.isSpecialL2 - ? { - l1Cost: estimation.l1Cost.toString(), - l1GasPrice: estimation.l1GasPrice.toString(), - } - : {}), - }), - "gasEst.final", - ); + spanAttributes["gasEst.final.gasLimit"] = estimation.gas.toString(); + spanAttributes["gasEst.final.totalCost"] = estimation.totalGasCost.toString(); + spanAttributes["gasEst.final.gasPrice"] = estimation.gasPrice.toString(); + if (config.isSpecialL2) { + spanAttributes["gasEst.final.l1Cost"] = estimation.l1Cost.toString(); + spanAttributes["gasEst.final.l1GasPrice"] = estimation.l1GasPrice.toString(); + } task.evaluable.bytecode = await parseRainlang( await getWithdrawEnsureRainlang( signer.account.address, @@ -357,6 +341,7 @@ export async function findOpp({ ); if (!opposingOrders || !opposingOrders.length) throw undefined; + const allErrorAttributes: string[] = []; const allNoneNodeErrors: (string | undefined)[] = []; const inputBalance = scale18( await viemClient.readContract({ @@ -393,13 +378,10 @@ export async function findOpp({ }); } catch (e: any) { allNoneNodeErrors.push(e?.value?.noneNodeError); - extendSpanAttributes( - spanAttributes, - JSON.stringify(e.spanAttributes), - "intraOrderbook." + i, - ); + allErrorAttributes.push(JSON.stringify(e.spanAttributes)); } } + spanAttributes["intraOrderbook"] = allErrorAttributes; const noneNodeErrors = allNoneNodeErrors.filter((v) => !!v); if (allNoneNodeErrors.length && noneNodeErrors.length / allNoneNodeErrors.length > 0.5) { result.value = { diff --git a/src/modes/routeProcessor.ts b/src/modes/routeProcessor.ts index c4b55c4d..f7d08ad6 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -14,7 +14,6 @@ import { estimateProfit, visualizeRoute, withBigintSerializer, - extendSpanAttributes, } from "../utils"; /** @@ -191,21 +190,13 @@ export async function dryrun({ .div(100); // include dryrun headroom gas estimation in otel logs - extendSpanAttributes( - spanAttributes, - JSON.stringify({ - gasLimit: estimation.gas.toString(), - totalCost: estimation.totalGasCost.toString(), - gasPrice: estimation.gasPrice.toString(), - ...(config.isSpecialL2 - ? { - l1Cost: estimation.l1Cost.toString(), - l1GasPrice: estimation.l1GasPrice.toString(), - } - : {}), - }), - "gasEst.headroom", - ); + spanAttributes["gasEst.headroom.gasLimit"] = estimation.gas.toString(); + spanAttributes["gasEst.headroom.totalCost"] = estimation.totalGasCost.toString(); + spanAttributes["gasEst.headroom.gasPrice"] = estimation.gasPrice.toString(); + if (config.isSpecialL2) { + spanAttributes["gasEst.headroom.l1Cost"] = estimation.l1Cost.toString(); + spanAttributes["gasEst.headroom.l1GasPrice"] = estimation.l1GasPrice.toString(); + } } catch (e) { // reason, code, method, transaction, error, stack, message const isNodeError = containsNodeError(e as BaseError); @@ -277,21 +268,13 @@ export async function dryrun({ gasCost = gasLimit.mul(gasPrice).add(estimation.l1Cost); // include dryrun final gas estimation in otel logs - extendSpanAttributes( - spanAttributes, - JSON.stringify({ - gasLimit: estimation.gas.toString(), - totalCost: estimation.totalGasCost.toString(), - gasPrice: estimation.gasPrice.toString(), - ...(config.isSpecialL2 - ? { - l1Cost: estimation.l1Cost.toString(), - l1GasPrice: estimation.l1GasPrice.toString(), - } - : {}), - }), - "gasEst.final", - ); + spanAttributes["gasEst.final.gasLimit"] = estimation.gas.toString(); + spanAttributes["gasEst.final.totalCost"] = estimation.totalGasCost.toString(); + spanAttributes["gasEst.final.gasPrice"] = estimation.gasPrice.toString(); + if (config.isSpecialL2) { + spanAttributes["gasEst.final.l1Cost"] = estimation.l1Cost.toString(); + spanAttributes["gasEst.final.l1GasPrice"] = estimation.l1GasPrice.toString(); + } task.evaluable.bytecode = await parseRainlang( await getBountyEnsureRainlang( ethers.utils.parseUnits(ethPrice), @@ -430,7 +413,7 @@ export async function findOpp({ // the fail reason can only be no route in case all hops fail reasons are no route if (e.reason !== RouteProcessorDryrunHaltReason.NoRoute) noRoute = false; allNoneNodeErrors.push(e?.value?.noneNodeError); - extendSpanAttributes(spanAttributes, JSON.stringify(e.spanAttributes), "full"); + spanAttributes["full"] = JSON.stringify(e.spanAttributes); } if (!hasPriceMatch.value) { const maxTradeSize = findMaxInput({ @@ -463,7 +446,7 @@ export async function findOpp({ // the fail reason can only be no route in case all hops fail reasons are no route if (e.reason !== RouteProcessorDryrunHaltReason.NoRoute) noRoute = false; allNoneNodeErrors.push(e?.value?.noneNodeError); - extendSpanAttributes(spanAttributes, JSON.stringify(e.spanAttributes), "partial"); + spanAttributes["partial"] = JSON.stringify(e.spanAttributes); } } } diff --git a/src/processOrders.ts b/src/processOrders.ts index 1dc8695d..50ada6bf 100644 --- a/src/processOrders.ts +++ b/src/processOrders.ts @@ -631,9 +631,40 @@ export async function processPair(args: { } } } catch (e: any) { - // record all span attributes + // record all span attributes in their scopes for (const attrKey in e.spanAttributes) { - spanAttributes["details." + attrKey] = e.spanAttributes[attrKey]; + if (attrKey === "routeProcessor") { + const rpAttrs = JSON.parse(e.spanAttributes[attrKey]); + for (const key in rpAttrs) { + const innerAttrs = JSON.parse(rpAttrs[key]); + for (const innerKey in innerAttrs) { + spanAttributes["details.routeProcessor." + key + "." + innerKey] = + innerAttrs[innerKey]; + } + } + } else if (attrKey === "intraOrderbook") { + const intraAttrs = JSON.parse(e.spanAttributes[attrKey]); + for (let i = 0; i < intraAttrs.length; i++) { + const innerAttrs = JSON.parse(intraAttrs[i]); + for (const innerKey in innerAttrs) { + spanAttributes["details.intraOrderbook." + i + "." + innerKey] = + innerAttrs[innerKey]; + } + } + } else if (attrKey === "interOrderbook") { + const interAttrs = JSON.parse( + JSON.parse(e.spanAttributes[attrKey])["againstOrderbooks"], + ); + for (const key in interAttrs) { + for (const innerKey in interAttrs[key]) { + spanAttributes[ + "details.interOrderbook.againstOrderbooks." + key + "." + innerKey + ] = interAttrs[key][innerKey]; + } + } + } else { + spanAttributes["details." + attrKey] = e.spanAttributes[attrKey]; + } } if (e.noneNodeError) { spanAttributes["details.noneNodeError"] = true; diff --git a/src/utils.ts b/src/utils.ts index c8ce1854..87329b82 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1405,17 +1405,15 @@ export function scale18To(value: BigNumberish, targetDecimals: BigNumberish): Bi */ export function extendSpanAttributes( spanAttributes: Record, - newAttributes: string, + newAttributes: Record, header: string, excludeHeaderForKeys: string[] = [], ) { - const attrs = JSON.parse(newAttributes); - for (const attrKey in attrs) { + for (const attrKey in newAttributes) { if (!excludeHeaderForKeys.includes(attrKey)) { - spanAttributes[header + "." + attrKey] = attrs[attrKey]; + spanAttributes[header + "." + attrKey] = newAttributes[attrKey]; } else { - spanAttributes[attrKey] = attrs[attrKey]; + spanAttributes[attrKey] = newAttributes[attrKey]; } - delete attrs[attrKey]; } } diff --git a/test/findOpp.test.js b/test/findOpp.test.js index 1d71152f..57cae604 100644 --- a/test/findOpp.test.js +++ b/test/findOpp.test.js @@ -504,28 +504,31 @@ describe("Test find opp", async function () { oppBlockNumber: undefined, noneNodeError: errorSnapshot("", err), spanAttributes: { - // rp span attrs - "routeProcessor.full.stage": 1, - "routeProcessor.full.rawtx": rawtx, - "routeProcessor.full.isNodeError": false, - "routeProcessor.full.route": expectedRouteVisual, - "routeProcessor.full.blockNumber": oppBlockNumber, - "routeProcessor.full.error": errorSnapshot("", err), - "routeProcessor.full.amountIn": formatUnits(vaultBalance), - "routeProcessor.full.amountOut": formatUnits(getAmountOut(vaultBalance), 6), - "routeProcessor.full.marketPrice": formatUnits(getCurrentPrice(vaultBalance)), - - // inter-ob span attrs - [`interOrderbook.againstOrderbooks.${opposingOrderbookAddress}.stage`]: 1, - [`interOrderbook.againstOrderbooks.${opposingOrderbookAddress}.isNodeError`]: false, - [`interOrderbook.againstOrderbooks.${opposingOrderbookAddress}.blockNumber`]: - oppBlockNumber, - [`interOrderbook.againstOrderbooks.${opposingOrderbookAddress}.rawtx`]: - JSON.stringify(rawtx2, withBigintSerializer), - [`interOrderbook.againstOrderbooks.${opposingOrderbookAddress}.maxInput`]: - vaultBalance.toString(), - [`interOrderbook.againstOrderbooks.${opposingOrderbookAddress}.error`]: - errorSnapshot("", err), + routeProcessor: JSON.stringify({ + full: JSON.stringify({ + amountIn: formatUnits(vaultBalance), + amountOut: formatUnits(getAmountOut(vaultBalance), 6), + marketPrice: formatUnits(getCurrentPrice(vaultBalance)), + route: expectedRouteVisual, + blockNumber: oppBlockNumber, + stage: 1, + isNodeError: false, + error: errorSnapshot("", err), + rawtx: rawtx, + }), + }), + interOrderbook: JSON.stringify({ + againstOrderbooks: JSON.stringify({ + [opposingOrderbookAddress]: { + maxInput: vaultBalance.toString(), + blockNumber: oppBlockNumber, + stage: 1, + isNodeError: false, + error: errorSnapshot("", err), + rawtx: JSON.stringify(rawtx2, withBigintSerializer), + }, + }), + }), }, }; assert.deepEqual(error, expected); diff --git a/test/mode-interOrderbook.test.js b/test/mode-interOrderbook.test.js index f5b3a198..7991895f 100644 --- a/test/mode-interOrderbook.test.js +++ b/test/mode-interOrderbook.test.js @@ -375,13 +375,16 @@ describe("Test inter-orderbook find opp", async function () { }, reason: undefined, spanAttributes: { - [`againstOrderbooks.${opposingOrderbookAddress}.blockNumber`]: oppBlockNumber, - [`againstOrderbooks.${opposingOrderbookAddress}.stage`]: 1, - [`againstOrderbooks.${opposingOrderbookAddress}.isNodeError`]: false, - [`againstOrderbooks.${opposingOrderbookAddress}.error`]: errorSnapshot("", err), - [`againstOrderbooks.${opposingOrderbookAddress}.rawtx`]: JSON.stringify(rawtx), - [`againstOrderbooks.${opposingOrderbookAddress}.maxInput`]: - vaultBalance.toString(), + againstOrderbooks: JSON.stringify({ + [opposingOrderbookAddress]: { + maxInput: vaultBalance.toString(), + blockNumber: oppBlockNumber, + stage: 1, + isNodeError: false, + error: errorSnapshot("", err), + rawtx: JSON.stringify(rawtx), + }, + }), }, }; assert.deepEqual(error, expected); diff --git a/test/mode-intraOrderbook.test.js b/test/mode-intraOrderbook.test.js index fd713563..b0d40eee 100644 --- a/test/mode-intraOrderbook.test.js +++ b/test/mode-intraOrderbook.test.js @@ -375,11 +375,15 @@ describe("Test intra-orderbook find opp", async function () { }, reason: undefined, spanAttributes: { - "intraOrderbook.0.blockNumber": oppBlockNumber, - "intraOrderbook.0.stage": 1, - "intraOrderbook.0.isNodeError": false, - "intraOrderbook.0.error": errorSnapshot("", err), - "intraOrderbook.0.rawtx": JSON.stringify(rawtx), + intraOrderbook: [ + JSON.stringify({ + blockNumber: oppBlockNumber, + stage: 1, + isNodeError: false, + error: errorSnapshot("", err), + rawtx: JSON.stringify(rawtx), + }), + ], }, }; assert.deepEqual(error, expected); diff --git a/test/mode-routeProcessor.test.js b/test/mode-routeProcessor.test.js index 0c983006..7e4f4e75 100644 --- a/test/mode-routeProcessor.test.js +++ b/test/mode-routeProcessor.test.js @@ -532,15 +532,17 @@ describe("Test route processor find opp", async function () { }, reason: RouteProcessorDryrunHaltReason.NoOpportunity, spanAttributes: { - "full.amountIn": formatUnits(vaultBalance), - "full.amountOut": formatUnits(getAmountOut(vaultBalance), 6), - "full.marketPrice": formatUnits(getCurrentPrice(vaultBalance)), - "full.route": expectedRouteVisual, - "full.blockNumber": oppBlockNumber, - "full.stage": 1, - "full.isNodeError": false, - "full.error": errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT), - "full.rawtx": rawtx, + full: JSON.stringify({ + amountIn: formatUnits(vaultBalance), + amountOut: formatUnits(getAmountOut(vaultBalance), 6), + marketPrice: formatUnits(getCurrentPrice(vaultBalance)), + route: expectedRouteVisual, + blockNumber: oppBlockNumber, + stage: 1, + isNodeError: false, + error: errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT), + rawtx: rawtx, + }), }, }; assert.deepEqual(error, expected); @@ -571,8 +573,10 @@ describe("Test route processor find opp", async function () { value: undefined, reason: RouteProcessorDryrunHaltReason.NoRoute, spanAttributes: { - "full.amountIn": formatUnits(vaultBalance), - "full.route": "no-way", + full: JSON.stringify({ + amountIn: formatUnits(vaultBalance), + route: "no-way", + }), }, }; assert.deepEqual(error, expected); @@ -756,15 +760,17 @@ describe("Test find opp with retries", async function () { }, reason: RouteProcessorDryrunHaltReason.NoOpportunity, spanAttributes: { - "full.amountIn": formatUnits(vaultBalance), - "full.amountOut": formatUnits(getAmountOut(vaultBalance), 6), - "full.marketPrice": formatUnits(getCurrentPrice(vaultBalance)), - "full.route": expectedRouteVisual, - "full.blockNumber": oppBlockNumber, - "full.stage": 1, - "full.isNodeError": false, - "full.error": errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT), - "full.rawtx": rawtx, + full: JSON.stringify({ + amountIn: formatUnits(vaultBalance), + amountOut: formatUnits(getAmountOut(vaultBalance), 6), + marketPrice: formatUnits(getCurrentPrice(vaultBalance)), + route: expectedRouteVisual, + blockNumber: oppBlockNumber, + stage: 1, + isNodeError: false, + error: errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT), + rawtx: rawtx, + }), }, }; assert.deepEqual(error, expected);