diff --git a/README.md b/README.md index 814cbb65..31f47d1b 100644 --- a/README.md +++ b/README.md @@ -113,8 +113,8 @@ Other optional arguments are: - `--owner-profile`, Specifies the owner limit, example: --owner-profile 0x123456=12 . Will override the 'OWNER_PROFILE' in env variables - `--public-rpc`, Allows to use public RPCs as fallbacks, default is false. Will override the 'PUBLIC_RPC' in env variables - `--gas-price-multiplier`, Option to multiply the gas price fetched from the rpc as percentage, default is 107, ie +7%. Will override the 'GAS_PRICE_MULTIPLIER' in env variables -- `--gas-limit-multiplier`, Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5%. Will override the 'GAS_LIMIT_MULTIPLIER' in env variables -- `--tx-gas`, Option to set a static gas limit for all submitting txs. Will override the 'TX_GAS' in env variables +- `--gas-limit-multiplier`, Option to multiply the gas limit estimation from the rpc as percentage, default is 100, ie no change. Will override the 'GAS_LIMIT_MULTIPLIER' in env variables +- `--tx-gas`, Option to set a gas limit for all submitting txs optionally with appended percentage sign to apply as percentage to original gas. Will override the 'TX_GAS' in env variables - `--quote-gas`, Option to set a static gas limit for quote read calls, default is 1 milion. Will override the 'QUOTE_GAS' in env variables - `--rp-only`, Only clear orders through RP4, excludes intra and inter orderbook clears. Will override the 'RP_ONLY' in env variablesin env variables - `-V` or `--version`, output the version number @@ -264,10 +264,10 @@ ROUTE="single" # Option to multiply the gas price fetched from the rpc as percentage, default is 107, ie +7% GAS_PRICE_MULTIPLIER= -# Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5% +# Option to multiply the gas limit estimation from the rpc as percentage, default is 100, ie no change GAS_LIMIT_MULTIPLIER= -# Option to set a static gas limit for all submitting txs +# Option to set a gas limit for all submitting txs optionally with appended percentage sign to apply as percentage to original gas TX_GAS= # Option to set a static gas limit for quote read calls, default is 1 milion diff --git a/example.env b/example.env index 3f341c2a..772a7a99 100644 --- a/example.env +++ b/example.env @@ -88,10 +88,10 @@ ROUTE="single" # Option to multiply the gas price fetched from the rpc as percentage, default is 107, ie +7% GAS_PRICE_MULTIPLIER= -# Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5% +# Option to multiply the gas limit estimation from the rpc as percentage, default is 100, ie no change GAS_LIMIT_MULTIPLIER= -# Option to set a static gas limit for all submitting txs +# Option to set a gas limit for all submitting txs optionally with appended percentage sign to apply as percentage to original gas TX_GAS= # Option to set a static gas limit for quote read calls, default is 1 milion diff --git a/src/cli.ts b/src/cli.ts index d72e35c8..6c00106e 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -196,11 +196,11 @@ const getOptions = async (argv: any, version?: string) => { ) .option( "--gas-limit-multiplier ", - "Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5%. Will override the 'GAS_LIMIT_MULTIPLIER' in env variables", + "Option to multiply the gas limit estimation from the rpc as percentage, default is 100, ie no change. Will override the 'GAS_LIMIT_MULTIPLIER' in env variables", ) .option( "--tx-gas ", - "Option to set a static gas limit for all submitting txs. Will override the 'TX_GAS' in env variables", + "Option to set a gas limit for all submitting txs optionally with appended percentage sign to apply as percentage to original gas. Will override the 'TX_GAS' in env variables", ) .option( "--quote-gas ", @@ -458,18 +458,12 @@ export async function startup(argv: any, version?: string, tracer?: Tracer, ctx? throw "invalid gasLimitMultiplier value, must be an integer greater than zero"; } else throw "invalid gasLimitMultiplier value, must be an integer greater than zero"; } else { - options.gasLimitMultiplier = 105; + options.gasLimitMultiplier = 100; } if (options.txGas) { - if (typeof options.txGas === "number") { - if (options.txGas <= 0 || !Number.isInteger(options.txGas)) - throw "invalid txGas value, must be an integer greater than zero"; - else options.txGas = BigInt(options.txGas); - } else if (typeof options.txGas === "string" && /^[0-9]+$/.test(options.txGas)) { - options.txGas = BigInt(options.txGas); - if (options.txGas <= 0n) - throw "invalid txGas value, must be an integer greater than zero"; - } else throw "invalid txGas value, must be an integer greater than zero"; + if (typeof options.txGas !== "string" || !/^[0-9]+%?$/.test(options.txGas)) { + throw "invalid txGas value, must be an integer greater than zero optionally with appended percentage sign to apply as percentage to original gas"; + } } if (options.quoteGas) { try { diff --git a/src/error.ts b/src/error.ts index e8576204..5d20cf2f 100644 --- a/src/error.ts +++ b/src/error.ts @@ -131,7 +131,8 @@ export function errorSnapshot( * 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): boolean { +export function containsNodeError(err: BaseError, circuitBreaker = 0): boolean { + if (circuitBreaker > 25) return false; try { const snapshot = errorSnapshot("", err); const parsed = parseRevertError(err); @@ -145,7 +146,7 @@ export function containsNodeError(err: BaseError): boolean { err instanceof InsufficientFundsError || (err instanceof RpcRequestError && err.code === ExecutionRevertedError.code) || (snapshot.includes("exceeds allowance") && !snapshot.includes("out of gas")) || - ("cause" in err && containsNodeError(err.cause as any)) + ("cause" in err && containsNodeError(err.cause as any, ++circuitBreaker)) ); } catch (error) { return false; @@ -155,14 +156,15 @@ export function containsNodeError(err: BaseError): boolean { /** * Checks if a viem BaseError is timeout error */ -export function isTimeout(err: BaseError): boolean { +export function isTimeout(err: BaseError, circuitBreaker = 0): boolean { + if (circuitBreaker > 25) return false; try { return ( err instanceof TimeoutError || err instanceof TransactionNotFoundError || err instanceof TransactionReceiptNotFoundError || err instanceof WaitForTransactionReceiptTimeoutError || - ("cause" in err && isTimeout(err.cause as any)) + ("cause" in err && isTimeout(err.cause as any, ++circuitBreaker)) ); } catch (error) { return false; @@ -227,14 +229,23 @@ export async function handleRevert( /** * Parses a revert error to TxRevertError type */ -export function parseRevertError(error: BaseError): TxRevertError { +export function parseRevertError(error: BaseError, circuitBreaker = 0): TxRevertError { + if (circuitBreaker > 25) { + return { + raw: { + code: -2, + message: + "Couldn't extract rpc error from the viem error object because the viem error object is circular", + }, + }; + } if ("cause" in error) { - return parseRevertError(error.cause as any); + return parseRevertError(error.cause as any, ++circuitBreaker); } else { let decoded: DecodedError | undefined; const raw: RawError = { - code: (error as any).code ?? NaN, - message: error.message, + code: (error as any).code ?? -2, + message: error.message ?? "No error msg", data: (error as any).data ?? undefined, }; if ("data" in error && isHex(error.data)) { diff --git a/src/modes/index.ts b/src/modes/index.ts index 24867c46..2014d16f 100644 --- a/src/modes/index.ts +++ b/src/modes/index.ts @@ -45,10 +45,11 @@ export async function findOpp({ fromToken: Token; }): Promise { try { - gasPrice = BigNumber.from(await viemClient.getGasPrice()) + const gp = BigNumber.from(await viemClient.getGasPrice()) .mul(config.gasPriceMultiplier) .div("100") .toBigInt(); + if (gp > gasPrice) gasPrice = gp; } catch { /**/ } diff --git a/src/modes/interOrderbook.ts b/src/modes/interOrderbook.ts index d272791d..ed95d2de 100644 --- a/src/modes/interOrderbook.ts +++ b/src/modes/interOrderbook.ts @@ -200,9 +200,6 @@ export async function dryrun({ } } rawtx.gas = gasLimit.toBigInt(); - if (typeof config.txGas === "bigint") { - rawtx.gas = config.txGas; - } // if reached here, it means there was a success and found opp spanAttributes["oppBlockNumber"] = blockNumber; diff --git a/src/modes/intraOrderbook.ts b/src/modes/intraOrderbook.ts index ab753257..85ddb0d5 100644 --- a/src/modes/intraOrderbook.ts +++ b/src/modes/intraOrderbook.ts @@ -216,9 +216,6 @@ export async function dryrun({ } } rawtx.gas = gasLimit.toBigInt(); - if (typeof config.txGas === "bigint") { - rawtx.gas = config.txGas; - } // if reached here, it means there was a success and found opp spanAttributes["oppBlockNumber"] = blockNumber; diff --git a/src/modes/routeProcessor.ts b/src/modes/routeProcessor.ts index 69a2d858..55ca3597 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -96,6 +96,7 @@ export async function dryrun({ config.route, ); if (route.status == "NoWay") { + if (hasPriceMatch) hasPriceMatch.value = false; spanAttributes["route"] = "no-way"; result.reason = RouteProcessorDryrunHaltReason.NoRoute; return Promise.reject(result); @@ -286,9 +287,6 @@ export async function dryrun({ } } rawtx.gas = gasLimit.toBigInt(); - if (typeof config.txGas === "bigint") { - rawtx.gas = config.txGas; - } // if reached here, it means there was a success and found opp // rest of span attr are not needed since they are present in the result.data diff --git a/src/tx.ts b/src/tx.ts index deb0cdc4..f9b143ca 100644 --- a/src/tx.ts +++ b/src/tx.ts @@ -41,9 +41,7 @@ export async function handleTransaction( let txhash: `0x${string}`, txUrl: string; let time = 0; const sendTx = async () => { - if (writeSigner !== undefined) { - rawtx.gas = undefined; - } + rawtx.gas = getTxGas(config, rawtx.gas!); txhash = writeSigner !== undefined ? await writeSigner.sendTx({ @@ -384,3 +382,19 @@ export async function pollSigners(accounts: ViemClient[]): Promise { } } } + +/** + * Returns the gas limit for a tx by applying the specified config + */ +export function getTxGas(config: BotConfig, gas: bigint): bigint { + if (config.txGas) { + if (config.txGas.endsWith("%")) { + const multiplier = BigInt(config.txGas.substring(0, config.txGas.length - 1)); + return (gas * multiplier) / 100n; + } else { + return BigInt(config.txGas); + } + } else { + return gas; + } +} diff --git a/src/types.ts b/src/types.ts index 49b5c8f3..6f631c16 100644 --- a/src/types.ts +++ b/src/types.ts @@ -50,7 +50,7 @@ export type CliOptions = { route?: string; gasPriceMultiplier: number; gasLimitMultiplier: number; - txGas?: bigint; + txGas?: string; quoteGas: bigint; rpOnly?: boolean; }; @@ -193,7 +193,7 @@ export type BotConfig = { rpcRecords: Record; gasPriceMultiplier: number; gasLimitMultiplier: number; - txGas?: bigint; + txGas?: string; quoteGas: bigint; rpOnly?: boolean; onFetchRequest?: (request: Request) => void; diff --git a/test/cli.test.js b/test/cli.test.js index e91fd249..08cb2e78 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -212,7 +212,7 @@ describe("Test cli", async function () { }, gasPriceMultiplier: 120, gasLimitMultiplier: 110, - txGas: 123456789n, + txGas: "123456789", quoteGas: 7777n, rpOnly: true, }, @@ -220,7 +220,7 @@ describe("Test cli", async function () { botMinBalance: "0.123", gasPriceMultiplier: 120, gasLimitMultiplier: 110, - txGas: 123456789n, + txGas: "123456789", quoteGas: 7777n, rpOnly: true, }, diff --git a/test/e2e/e2e.test.js b/test/e2e/e2e.test.js index 98510da8..ea00789c 100644 --- a/test/e2e/e2e.test.js +++ b/test/e2e/e2e.test.js @@ -72,6 +72,9 @@ for (let i = 0; i < testData.length; i++) { provider.register(); const tracer = provider.getTracer("arb-bot-tracer"); + config.rpc = [rpc]; + const dataFetcherPromise = getDataFetcher(config, liquidityProviders, false); + // run tests on each rp version for (let j = 0; j < rpVersions.length; j++) { const rpVersion = rpVersions[j]; @@ -79,7 +82,7 @@ for (let i = 0; i < testData.length; i++) { it(`should clear orders successfully using route processor v${rpVersion}`, async function () { config.rpc = [rpc]; const viemClient = await viem.getPublicClient(); - const dataFetcher = await getDataFetcher(config, liquidityProviders, false); + const dataFetcher = await dataFetcherPromise; const testSpan = tracer.startSpan("test-clearing"); const ctx = trace.setSpan(context.active(), testSpan); @@ -342,7 +345,7 @@ for (let i = 0; i < testData.length; i++) { it("should clear orders successfully using inter-orderbook", async function () { config.rpc = [rpc]; const viemClient = await viem.getPublicClient(); - const dataFetcher = await getDataFetcher(config, liquidityProviders, false); + const dataFetcher = await dataFetcherPromise; const testSpan = tracer.startSpan("test-clearing"); const ctx = trace.setSpan(context.active(), testSpan); @@ -690,7 +693,7 @@ for (let i = 0; i < testData.length; i++) { it("should clear orders successfully using intra-orderbook", async function () { config.rpc = [rpc]; const viemClient = await viem.getPublicClient(); - const dataFetcher = await getDataFetcher(config, liquidityProviders, false); + const dataFetcher = await dataFetcherPromise; const testSpan = tracer.startSpan("test-clearing"); const ctx = trace.setSpan(context.active(), testSpan); diff --git a/test/tx.test.ts b/test/tx.test.ts index e25e44b6..00789f5e 100644 --- a/test/tx.test.ts +++ b/test/tx.test.ts @@ -10,6 +10,7 @@ import { handleReceipt, sendTransaction, handleTransaction, + getTxGas, } from "../src/tx"; describe("Test tx", async function () { @@ -423,4 +424,27 @@ describe("Test tx", async function () { const result = await pollSigners(someMockedSigners); assert.equal(result, someMockedSigners[2]); }); + + it("should test getTxGas", async function () { + const gas = 500n; + + // percentage + const config = { + txGas: "120%", + } as any; + let result = getTxGas(config, gas); + let expected = 600n; + assert.equal(result, expected); + + // static + config.txGas = "900"; + result = getTxGas(config, gas); + expected = 900n; + assert.equal(result, expected); + + // none + config.txGas = undefined; + result = getTxGas(config, gas); + assert.equal(result, gas); + }); });