diff --git a/src/error.ts b/src/error.ts index 917a1f75..feac035e 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,4 +1,10 @@ -import { BaseError } from "viem"; +import { + BaseError, + RpcRequestError, + InvalidInputRpcError, + ExecutionRevertedError, + TransactionRejectedRpcError, +} from "viem"; /** * Specifies error severity @@ -32,3 +38,15 @@ export function errorSnapshot(header: string, err: any): string { } return message.join("\n"); } + +/** + * Checks if a viem BaseError is from eth node, copied from + * "viem/_types/utils/errors/getNodeError" since not a default export + */ +export function containsNodeError(err: BaseError) { + return ( + err instanceof TransactionRejectedRpcError || + err instanceof InvalidInputRpcError || + (err instanceof RpcRequestError && err.code === ExecutionRevertedError.code) + ); +} diff --git a/src/modes/interOrderbook.ts b/src/modes/interOrderbook.ts index 1df483d8..21dd5f98 100644 --- a/src/modes/interOrderbook.ts +++ b/src/modes/interOrderbook.ts @@ -1,8 +1,8 @@ -import { PublicClient } from "viem"; import { orderbookAbi } from "../abis"; -import { errorSnapshot } from "../error"; +import { BaseError, PublicClient } from "viem"; import { getBountyEnsureBytecode } from "../config"; import { BigNumber, Contract, ethers } from "ethers"; +import { containsNodeError, errorSnapshot } from "../error"; import { estimateProfit, withBigintSerializer } from "../utils"; import { BotConfig, BundledOrders, ViemClient, DryrunResult, SpanAttrs } from "../types"; @@ -104,6 +104,7 @@ export async function dryrun({ spanAttributes["blockNumber"] = blockNumber; gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx)); } catch (e) { + spanAttributes["isNodeError"] = containsNodeError(e as BaseError); spanAttributes["error"] = errorSnapshot("", e); spanAttributes["rawtx"] = JSON.stringify( { @@ -149,6 +150,7 @@ export async function dryrun({ task, ]); } catch (e) { + spanAttributes["isNodeError"] = containsNodeError(e as BaseError); spanAttributes["error"] = errorSnapshot("", e); spanAttributes["rawtx"] = JSON.stringify( { diff --git a/src/modes/intraOrderbook.ts b/src/modes/intraOrderbook.ts index 883ce71b..25d230a0 100644 --- a/src/modes/intraOrderbook.ts +++ b/src/modes/intraOrderbook.ts @@ -1,8 +1,8 @@ -import { PublicClient } from "viem"; -import { errorSnapshot } from "../error"; import { BigNumber, ethers } from "ethers"; +import { BaseError, PublicClient } from "viem"; import { erc20Abi, orderbookAbi } from "../abis"; import { getWithdrawEnsureBytecode } from "../config"; +import { containsNodeError, errorSnapshot } from "../error"; import { estimateProfit, withBigintSerializer } from "../utils"; import { BotConfig, @@ -92,6 +92,7 @@ export async function dryrun({ gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx)); } catch (e) { // reason, code, method, transaction, error, stack, message + spanAttributes["isNodeError"] = containsNodeError(e as BaseError); spanAttributes["error"] = errorSnapshot("", e); spanAttributes["rawtx"] = JSON.stringify( { @@ -162,6 +163,7 @@ export async function dryrun({ [clear2Calldata, withdrawInputCalldata, withdrawOutputCalldata], ]); } catch (e) { + spanAttributes["isNodeError"] = containsNodeError(e as BaseError); spanAttributes["error"] = errorSnapshot("", e); spanAttributes["rawtx"] = JSON.stringify( { diff --git a/src/modes/routeProcessor.ts b/src/modes/routeProcessor.ts index c92e82bf..165f64d3 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -1,9 +1,9 @@ -import { PublicClient } from "viem"; import { Token } from "sushi/currency"; -import { errorSnapshot } from "../error"; +import { BaseError, PublicClient } from "viem"; import { getBountyEnsureBytecode } from "../config"; import { ChainId, DataFetcher, Router } from "sushi"; import { BigNumber, Contract, ethers } from "ethers"; +import { containsNodeError, errorSnapshot } from "../error"; import { BotConfig, BundledOrders, ViemClient, DryrunResult, SpanAttrs } from "../types"; import { estimateProfit, RPoolFilter, visualizeRoute, withBigintSerializer } from "../utils"; @@ -170,6 +170,7 @@ export async function dryrun({ gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx)); } catch (e) { // reason, code, method, transaction, error, stack, message + spanAttributes["isNodeError"] = containsNodeError(e as BaseError); spanAttributes["error"] = errorSnapshot("", e); spanAttributes["rawtx"] = JSON.stringify( { @@ -216,6 +217,7 @@ export async function dryrun({ task, ]); } catch (e) { + spanAttributes["isNodeError"] = containsNodeError(e as BaseError); spanAttributes["error"] = errorSnapshot("", e); spanAttributes["rawtx"] = JSON.stringify( { diff --git a/src/processOrders.ts b/src/processOrders.ts index 728e6abe..597cdb7e 100644 --- a/src/processOrders.ts +++ b/src/processOrders.ts @@ -1,15 +1,15 @@ import { ChainId } from "sushi"; import { findOpp } from "./modes"; -import { PublicClient } from "viem"; import { Token } from "sushi/currency"; import { createViemClient } from "./config"; +import { BaseError, PublicClient } from "viem"; import { arbAbis, orderbookAbi } from "./abis"; import { privateKeyToAccount } from "viem/accounts"; import { BigNumber, Contract, ethers } from "ethers"; import { Tracer } from "@opentelemetry/sdk-trace-base"; -import { ErrorSeverity, errorSnapshot } from "./error"; import { fundOwnedOrders, rotateAccounts } from "./account"; import { Context, SpanStatusCode } from "@opentelemetry/api"; +import { containsNodeError, ErrorSeverity, errorSnapshot } from "./error"; import { Report, BotConfig, @@ -624,6 +624,7 @@ export async function processPair(args: { }, withBigintSerializer, ); + spanAttributes["details.isNodeError"] = containsNodeError(e as BaseError); result.error = e; result.reason = ProcessPairHaltReason.TxFailed; throw result; @@ -761,6 +762,7 @@ export async function processPair(args: { result.report.actualGasCost = ethers.utils.formatUnits(actualGasCost); } result.error = e; + spanAttributes["details.isNodeError"] = containsNodeError(e); result.reason = ProcessPairHaltReason.TxMineFailed; throw result; } diff --git a/test/mode-interOrderbook.test.js b/test/mode-interOrderbook.test.js index cc0e7226..810c1a12 100644 --- a/test/mode-interOrderbook.test.js +++ b/test/mode-interOrderbook.test.js @@ -423,6 +423,7 @@ describe("Test inter-orderbook find opp", async function () { [opposingOrderbookAddress]: { maxInput: vaultBalance.toString(), blockNumber: oppBlockNumber, + isNodeError: false, error: errorSnapshot("", err), rawtx: JSON.stringify(rawtx), }, diff --git a/test/mode-intraOrderbook.test.js b/test/mode-intraOrderbook.test.js index 34185363..9ec21bf7 100644 --- a/test/mode-intraOrderbook.test.js +++ b/test/mode-intraOrderbook.test.js @@ -308,6 +308,7 @@ describe("Test intra-orderbook find opp", async function () { intraOrderbook: [ JSON.stringify({ blockNumber: oppBlockNumber, + isNodeError: false, error: errorSnapshot("", err), rawtx: JSON.stringify(rawtx), }), diff --git a/test/mode-routeProcessor.test.js b/test/mode-routeProcessor.test.js index c05d7fcc..2f2997a6 100644 --- a/test/mode-routeProcessor.test.js +++ b/test/mode-routeProcessor.test.js @@ -224,6 +224,7 @@ describe("Test route processor dryrun", async function () { error: errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT), route: expectedRouteVisual, rawtx: JSON.stringify(rawtx), + isNodeError: false, }, }; assert.deepEqual(error, expected); @@ -455,9 +456,9 @@ describe("Test route processor find opp", async function () { reason: RouteProcessorDryrunHaltReason.NoOpportunity, spanAttributes: { hops: [ - `{"amountIn":"${formatUnits(vaultBalance)}","amountOut":"${formatUnits(getAmountOut(vaultBalance), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"error":${JSON.stringify(errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT))},"rawtx":${JSON.stringify(rawtx)}}`, - `{"amountIn":"${formatUnits(vaultBalance.div(2))}","amountOut":"${formatUnits(getAmountOut(vaultBalance.div(2)), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance.div(2)))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber}}`, - `{"amountIn":"${formatUnits(vaultBalance.div(4))}","amountOut":"${formatUnits(getAmountOut(vaultBalance.div(4)), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance.div(4)))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber}}`, + `{"amountIn":"${formatUnits(vaultBalance)}","amountOut":"${formatUnits(getAmountOut(vaultBalance), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"isNodeError":false,"error":${JSON.stringify(errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT))},"rawtx":${JSON.stringify(rawtx)}}`, + `{"amountIn":"${formatUnits(vaultBalance.div(2))}","amountOut":"${formatUnits(getAmountOut(vaultBalance.div(2)), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance.div(2)))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"isNodeError":false}`, + `{"amountIn":"${formatUnits(vaultBalance.div(4))}","amountOut":"${formatUnits(getAmountOut(vaultBalance.div(4)), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance.div(4)))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"isNodeError":false}`, ], }, }; @@ -650,9 +651,9 @@ describe("Test find opp with retries", async function () { reason: RouteProcessorDryrunHaltReason.NoOpportunity, spanAttributes: { hops: [ - `{"amountIn":"${formatUnits(vaultBalance)}","amountOut":"${formatUnits(getAmountOut(vaultBalance), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"error":${JSON.stringify(errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT))},"rawtx":${JSON.stringify(rawtx)}}`, - `{"amountIn":"${formatUnits(vaultBalance.div(2))}","amountOut":"${formatUnits(getAmountOut(vaultBalance.div(2)), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance.div(2)))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber}}`, - `{"amountIn":"${formatUnits(vaultBalance.div(4))}","amountOut":"${formatUnits(getAmountOut(vaultBalance.div(4)), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance.div(4)))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber}}`, + `{"amountIn":"${formatUnits(vaultBalance)}","amountOut":"${formatUnits(getAmountOut(vaultBalance), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"isNodeError":false,"error":${JSON.stringify(errorSnapshot("", ethers.errors.UNPREDICTABLE_GAS_LIMIT))},"rawtx":${JSON.stringify(rawtx)}}`, + `{"amountIn":"${formatUnits(vaultBalance.div(2))}","amountOut":"${formatUnits(getAmountOut(vaultBalance.div(2)), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance.div(2)))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"isNodeError":false}`, + `{"amountIn":"${formatUnits(vaultBalance.div(4))}","amountOut":"${formatUnits(getAmountOut(vaultBalance.div(4)), 6)}","marketPrice":"${formatUnits(getCurrentPrice(vaultBalance.div(4)))}","route":${JSON.stringify(expectedRouteVisual)},"blockNumber":${oppBlockNumber},"isNodeError":false}`, ], }, }; diff --git a/test/processPair.test.js b/test/processPair.test.js index d5913cbd..d6621afe 100644 --- a/test/processPair.test.js +++ b/test/processPair.test.js @@ -543,6 +543,7 @@ describe("Test process pair", async function () { "details.outputToEthPrice": "1", "details.marketQuote.num": 0.99699, "details.marketQuote.str": "0.99699", + "details.isNodeError": false, "details.quote": JSON.stringify({ maxOutput: formatUnits(vaultBalance), ratio: formatUnits(ethers.constants.Zero),