From a02af8414d8c4b56dfe213a6650b36cf6fd7e65e Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Tue, 17 Dec 2024 01:50:29 +0000 Subject: [PATCH 1/9] update --- src/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index e55991f7..2d8dfba9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -515,7 +515,9 @@ export const visualizeRoute = (fromToken: Token, toToken: Token, legs: RouteLeg[ (e) => e.tokenFrom.address.toLowerCase() === portions.at(-1)?.tokenTo.address.toLowerCase() && - !portions.includes(e), + portions.every( + (k) => k.poolAddress.toLowerCase() !== e.poolAddress.toLowerCase(), + ), ); if (legPortion) { portions.push(legPortion); From 668a9245f31c00628f8e18a1f3a35946acd1d132 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 25 Dec 2024 23:31:56 +0000 Subject: [PATCH 2/9] init --- README.md | 8 ++++---- example.env | 4 ++-- src/cli.ts | 18 ++++++------------ src/modes/interOrderbook.ts | 3 --- src/modes/intraOrderbook.ts | 3 --- src/modes/routeProcessor.ts | 3 --- src/tx.ts | 20 +++++++++++++++++--- src/types.ts | 4 ++-- test/cli.test.js | 4 ++-- test/tx.test.ts | 20 ++++++++++++++++++++ 10 files changed, 53 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index ef29e2b1..4e0df47c 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 108, ie +8%. 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 105, ie +5%. 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 - `--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 - `-h` or `--help`, output usage information @@ -263,10 +263,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 108, ie +8% +# Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5% 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= # Only clear orders through RP4, excludes intra and inter orderbook clears diff --git a/example.env b/example.env index 25e38948..3639d4cb 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 108, ie +8% +# Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5% 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= # Only clear orders through RP4, excludes intra and inter orderbook clears diff --git a/src/cli.ts b/src/cli.ts index 8e6b5b67..a39bd659 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -195,11 +195,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 108, ie +8%. Will override the 'GAS_LIMIT_MULTIPLIER' in env variables", + "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( "--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( "--rp-only", @@ -452,18 +452,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 = 108; + options.gasLimitMultiplier = 105; } 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"; + } } const poolUpdateInterval = _poolUpdateInterval * 60 * 1000; let ordersDetails: SgOrder[] = []; diff --git a/src/modes/interOrderbook.ts b/src/modes/interOrderbook.ts index fa1763d6..4cae6a25 100644 --- a/src/modes/interOrderbook.ts +++ b/src/modes/interOrderbook.ts @@ -195,9 +195,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 9f69724a..f975754a 100644 --- a/src/modes/intraOrderbook.ts +++ b/src/modes/intraOrderbook.ts @@ -211,9 +211,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 de93eb3d..01750fce 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -273,9 +273,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 af73f053..b6cbcc20 100644 --- a/src/tx.ts +++ b/src/tx.ts @@ -40,9 +40,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({ @@ -388,3 +386,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 4284019e..29643104 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; rpOnly?: boolean; }; @@ -190,7 +190,7 @@ export type BotConfig = { rpcRecords: Record; gasPriceMultiplier: number; gasLimitMultiplier: number; - txGas?: bigint; + txGas?: string; rpOnly?: boolean; onFetchRequest?: (request: Request) => void; onFetchResponse?: (request: Response) => void; diff --git a/test/cli.test.js b/test/cli.test.js index 4d4ed3fd..5055cff8 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -210,14 +210,14 @@ describe("Test cli", async function () { }, gasPriceMultiplier: 120, gasLimitMultiplier: 110, - txGas: 123456789n, + txGas: "123456789", rpOnly: true, }, options: { botMinBalance: "0.123", gasPriceMultiplier: 120, gasLimitMultiplier: 110, - txGas: 123456789n, + txGas: "123456789", rpOnly: true, }, }; diff --git a/test/tx.test.ts b/test/tx.test.ts index 1432e166..38ba41d6 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 () { @@ -415,4 +416,23 @@ describe("Test tx", async function () { const result = await pollSigners(someMockedSigners); assert.equal(result, someMockedSigners[2]); }); + + it("should test getTxGas", async function () { + const gas = 500n; + const config = { + txGas: "120%", + } as any; + let result = getTxGas(config, gas); + let expected = 600n; + assert.equal(result, expected); + + config.txGas = "900"; + result = getTxGas(config, gas); + expected = 900n; + assert.equal(result, expected); + + config.txGas = undefined; + result = getTxGas(config, gas); + assert.equal(result, gas); + }); }); From 227eb0f7e97a9b65a5d583e64718f094bed07d61 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 25 Dec 2024 23:35:46 +0000 Subject: [PATCH 3/9] update --- README.md | 4 ++-- example.env | 2 +- src/cli.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4e0df47c..7dda91c9 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ 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 +- `--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 - `--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 @@ -263,7 +263,7 @@ 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 gas limit for all submitting txs optionally with appended percentage sign to apply as percentage to original gas diff --git a/example.env b/example.env index 3639d4cb..6a133bc9 100644 --- a/example.env +++ b/example.env @@ -88,7 +88,7 @@ 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 gas limit for all submitting txs optionally with appended percentage sign to apply as percentage to original gas diff --git a/src/cli.ts b/src/cli.ts index a39bd659..b74d3d90 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -195,7 +195,7 @@ 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 ", @@ -452,7 +452,7 @@ 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 !== "string" || !/^[0-9]+%?$/.test(options.txGas)) { From 7f823010e7a8f0b360e25bd3baa85298e886e2f6 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 26 Dec 2024 01:00:39 +0000 Subject: [PATCH 4/9] update --- src/modes/index.ts | 3 ++- test/e2e/e2e.test.js | 9 ++++++--- test/tx.test.ts | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/modes/index.ts b/src/modes/index.ts index 80e08cdd..7e52e00f 100644 --- a/src/modes/index.ts +++ b/src/modes/index.ts @@ -44,10 +44,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/test/e2e/e2e.test.js b/test/e2e/e2e.test.js index 3f0a2a73..8b653022 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); @@ -693,7 +696,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 38ba41d6..9c4046ab 100644 --- a/test/tx.test.ts +++ b/test/tx.test.ts @@ -419,6 +419,8 @@ describe("Test tx", async function () { it("should test getTxGas", async function () { const gas = 500n; + + // percentage const config = { txGas: "120%", } as any; @@ -426,11 +428,13 @@ describe("Test tx", async function () { 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); From 0c91fc1810c667cf8c3ae02d0bb023224fe623ee Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 26 Dec 2024 02:53:11 +0000 Subject: [PATCH 5/9] update --- src/error.ts | 27 +++++++++++++++++++-------- src/modes/routeProcessor.ts | 1 + 2 files changed, 20 insertions(+), 8 deletions(-) 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/routeProcessor.ts b/src/modes/routeProcessor.ts index 01750fce..bf2958a0 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -91,6 +91,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); From a9078be548c953150d85818e9d7771e980fbe878 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 26 Dec 2024 04:05:03 +0000 Subject: [PATCH 6/9] test --- src/modes/routeProcessor.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/modes/routeProcessor.ts b/src/modes/routeProcessor.ts index bf2958a0..d1bacf56 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -540,6 +540,23 @@ export function findMaxInput({ const result: BigNumber[] = []; const ratio = orderPairObject.takeOrders[0].quote!.ratio; const pcMap = dataFetcher.getCurrentPoolCodeMap(fromToken, toToken); + if ( + fromToken.address.toLowerCase() === + "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() || + toToken.address.toLowerCase() === "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() + ) { + pcMap.forEach((x) => { + if ( + x.pool.token0.address.toLowerCase() === + "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() || + x.pool.token1.address.toLowerCase() === + "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() + ) { + // eslint-disable-next-line no-console + console.log(x); + } + }); + } const initAmount = scale18To(maximumInputFixed, fromToken.decimals).div(2); let maximumInput = BigNumber.from(initAmount.toString()); for (let i = 1; i < 26; i++) { @@ -558,8 +575,26 @@ export function findMaxInput({ ); if (route.status == "NoWay") { + if ( + fromToken.address.toLowerCase() === + "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() || + toToken.address.toLowerCase() === + "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() + ) { + // eslint-disable-next-line no-console + console.log("noway", maximumInput.toString()); + } maximumInput = maximumInput.sub(initAmount.div(2 ** i)); } else { + if ( + fromToken.address.toLowerCase() === + "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() || + toToken.address.toLowerCase() === + "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() + ) { + // eslint-disable-next-line no-console + console.log("way", maximumInput.toString()); + } const amountOut = scale18(route.amountOutBI, toToken.decimals); const price = amountOut.mul("1" + "0".repeat(18)).div(maxInput18); From a5de0810b95a2c2f20883de1cd7c8354522eb3cf Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 26 Dec 2024 04:13:07 +0000 Subject: [PATCH 7/9] Revert "test" This reverts commit a9078be548c953150d85818e9d7771e980fbe878. --- src/modes/routeProcessor.ts | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/src/modes/routeProcessor.ts b/src/modes/routeProcessor.ts index d1bacf56..bf2958a0 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -540,23 +540,6 @@ export function findMaxInput({ const result: BigNumber[] = []; const ratio = orderPairObject.takeOrders[0].quote!.ratio; const pcMap = dataFetcher.getCurrentPoolCodeMap(fromToken, toToken); - if ( - fromToken.address.toLowerCase() === - "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() || - toToken.address.toLowerCase() === "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() - ) { - pcMap.forEach((x) => { - if ( - x.pool.token0.address.toLowerCase() === - "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() || - x.pool.token1.address.toLowerCase() === - "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() - ) { - // eslint-disable-next-line no-console - console.log(x); - } - }); - } const initAmount = scale18To(maximumInputFixed, fromToken.decimals).div(2); let maximumInput = BigNumber.from(initAmount.toString()); for (let i = 1; i < 26; i++) { @@ -575,26 +558,8 @@ export function findMaxInput({ ); if (route.status == "NoWay") { - if ( - fromToken.address.toLowerCase() === - "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() || - toToken.address.toLowerCase() === - "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() - ) { - // eslint-disable-next-line no-console - console.log("noway", maximumInput.toString()); - } maximumInput = maximumInput.sub(initAmount.div(2 ** i)); } else { - if ( - fromToken.address.toLowerCase() === - "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() || - toToken.address.toLowerCase() === - "0xb02541995f317fd47c5df8e8fba7284b502b5d7b".toLowerCase() - ) { - // eslint-disable-next-line no-console - console.log("way", maximumInput.toString()); - } const amountOut = scale18(route.amountOutBI, toToken.decimals); const price = amountOut.mul("1" + "0".repeat(18)).div(maxInput18); From 0053435b282becc987a92918fa866f15441bcf9a Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 26 Dec 2024 08:50:54 +0000 Subject: [PATCH 8/9] init --- src/modes/interOrderbook.ts | 15 +++---- src/modes/intraOrderbook.ts | 51 +++++++++++------------ src/modes/routeProcessor.ts | 25 +++--------- src/tx.ts | 70 ++++++++++---------------------- src/utils.ts | 31 +++++++------- test/findOpp.test.js | 1 + test/mode-intraOrderbook.test.js | 1 + test/processPair.test.js | 1 + 8 files changed, 80 insertions(+), 115 deletions(-) diff --git a/src/modes/interOrderbook.ts b/src/modes/interOrderbook.ts index 4cae6a25..e2368370 100644 --- a/src/modes/interOrderbook.ts +++ b/src/modes/interOrderbook.ts @@ -3,7 +3,7 @@ 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 { estimateProfit, scale18To, withBigintSerializer } from "../utils"; import { BotConfig, BundledOrders, ViemClient, DryrunResult, SpanAttrs } from "../types"; /** @@ -39,16 +39,17 @@ export async function dryrun({ spanAttributes, }; - const maximumInput = maximumInputFixed.div( - "1" + "0".repeat(18 - orderPairObject.sellTokenDecimals), - ); + const maximumInput = scale18To(maximumInputFixed, orderPairObject.sellTokenDecimals); spanAttributes["maxInput"] = maximumInput.toString(); const opposingMaxInput = orderPairObject.takeOrders[0].quote!.ratio.isZero() ? ethers.constants.MaxUint256 - : maximumInputFixed - .mul(orderPairObject.takeOrders[0].quote!.ratio) - .div(`1${"0".repeat(36 - orderPairObject.buyTokenDecimals)}`); + : scale18To( + maximumInputFixed + .mul(orderPairObject.takeOrders[0].quote!.ratio) + .div(`1${"0".repeat(18)}`), + orderPairObject.buyTokenDecimals, + ); const opposingMaxIORatio = orderPairObject.takeOrders[0].quote!.ratio.isZero() ? ethers.constants.MaxUint256 diff --git a/src/modes/intraOrderbook.ts b/src/modes/intraOrderbook.ts index f975754a..ddc841e6 100644 --- a/src/modes/intraOrderbook.ts +++ b/src/modes/intraOrderbook.ts @@ -1,16 +1,16 @@ +import { orderbookAbi } from "../abis"; import { BigNumber, ethers } from "ethers"; -import { BaseError, PublicClient } from "viem"; -import { erc20Abi, orderbookAbi } from "../abis"; import { getWithdrawEnsureBytecode } from "../config"; +import { BaseError, erc20Abi, PublicClient } from "viem"; import { containsNodeError, errorSnapshot } from "../error"; -import { estimateProfit, withBigintSerializer } from "../utils"; +import { estimateProfit, scale18, withBigintSerializer } from "../utils"; import { + SpanAttrs, BotConfig, - BundledOrders, ViemClient, - TakeOrderDetails, DryrunResult, - SpanAttrs, + BundledOrders, + TakeOrderDetails, } from "../types"; /** @@ -286,27 +286,24 @@ export async function findOpp({ const allErrorAttributes: string[] = []; const allNoneNodeErrors: (string | undefined)[] = []; - const erc20 = new ethers.utils.Interface(erc20Abi); - const inputBalance = ethers.BigNumber.from( - ( - await viemClient.call({ - to: orderPairObject.buyToken as `0x${string}`, - data: erc20.encodeFunctionData("balanceOf", [ - signer.account.address, - ]) as `0x${string}`, - }) - ).data, - ).mul("1" + "0".repeat(18 - orderPairObject.buyTokenDecimals)); - const outputBalance = ethers.BigNumber.from( - ( - await viemClient.call({ - to: orderPairObject.sellToken as `0x${string}`, - data: erc20.encodeFunctionData("balanceOf", [ - signer.account.address, - ]) as `0x${string}`, - }) - ).data, - ).mul("1" + "0".repeat(18 - orderPairObject.sellTokenDecimals)); + const inputBalance = scale18( + await viemClient.readContract({ + address: orderPairObject.buyToken as `0x${string}`, + abi: erc20Abi, + functionName: "balanceOf", + args: [signer.account.address], + }), + orderPairObject.buyTokenDecimals, + ); + const outputBalance = scale18( + await viemClient.readContract({ + address: orderPairObject.sellToken as `0x${string}`, + abi: erc20Abi, + functionName: "balanceOf", + args: [signer.account.address], + }), + orderPairObject.sellTokenDecimals, + ); for (let i = 0; i < opposingOrders.length; i++) { try { return await dryrun({ diff --git a/src/modes/routeProcessor.ts b/src/modes/routeProcessor.ts index bf2958a0..11af85a4 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -6,6 +6,7 @@ import { BigNumber, Contract, ethers } from "ethers"; import { containsNodeError, errorSnapshot } from "../error"; import { BotConfig, BundledOrders, ViemClient, DryrunResult, SpanAttrs } from "../types"; import { + ONE18, scale18, scale18To, RPoolFilter, @@ -22,16 +23,6 @@ export enum RouteProcessorDryrunHaltReason { NoRoute = 2, } -/** - * Route Processor versions - */ -const getRouteProcessorParamsVersion = { - "3": Router.routeProcessor3Params, - "3.1": Router.routeProcessor3_1Params, - "3.2": Router.routeProcessor3_2Params, - "4": Router.routeProcessor4Params, -} as const; - /** * Executes a extimateGas call for an arb() tx, to determine if the tx is successfull ot not */ @@ -71,9 +62,7 @@ export async function dryrun({ spanAttributes, }; - const maximumInput = maximumInputFixed.div( - "1" + "0".repeat(18 - orderPairObject.sellTokenDecimals), - ); + const maximumInput = scale18To(maximumInputFixed, orderPairObject.sellTokenDecimals); spanAttributes["amountIn"] = ethers.utils.formatUnits(maximumInputFixed); // get route details from sushi dataFetcher @@ -97,10 +86,8 @@ export async function dryrun({ return Promise.reject(result); } else { spanAttributes["amountOut"] = ethers.utils.formatUnits(route.amountOutBI, toToken.decimals); - const rateFixed = ethers.BigNumber.from(route.amountOutBI).mul( - "1" + "0".repeat(18 - orderPairObject.buyTokenDecimals), - ); - const price = rateFixed.mul("1" + "0".repeat(18)).div(maximumInputFixed); + const rateFixed = scale18(route.amountOutBI, orderPairObject.buyTokenDecimals); + const price = rateFixed.mul(ONE18).div(maximumInputFixed); spanAttributes["marketPrice"] = ethers.utils.formatEther(price); const routeVisual: string[] = []; @@ -121,7 +108,7 @@ export async function dryrun({ return Promise.reject(result); } - const rpParams = getRouteProcessorParamsVersion["4"]( + const rpParams = Router.routeProcessor4Params( pcMap, route, fromToken, @@ -561,7 +548,7 @@ export function findMaxInput({ maximumInput = maximumInput.sub(initAmount.div(2 ** i)); } else { const amountOut = scale18(route.amountOutBI, toToken.decimals); - const price = amountOut.mul("1" + "0".repeat(18)).div(maxInput18); + const price = amountOut.mul(ONE18).div(maxInput18); if (price.lt(ratio)) { maximumInput = maximumInput.sub(initAmount.div(2 ** i)); diff --git a/src/tx.ts b/src/tx.ts index b6cbcc20..1bf0b201 100644 --- a/src/tx.ts +++ b/src/tx.ts @@ -1,6 +1,6 @@ import { Token } from "sushi/currency"; +import { Contract, ethers } from "ethers"; import { addWatchedToken } from "./account"; -import { BigNumber, Contract, ethers } from "ethers"; import { containsNodeError, handleRevert } from "./error"; import { ProcessPairHaltReason, ProcessPairReportStatus } from "./processOrders"; import { BotConfig, BundledOrders, ProcessPairResult, RawTx, ViemClient } from "./types"; @@ -83,11 +83,23 @@ export async function handleTransaction( } // start getting tx receipt in background and return the settler fn - const receiptPromise = viemClient.waitForTransactionReceipt({ - hash: txhash!, - confirmations: 1, - timeout: 120_000, - }); + const receiptPromise = (async () => { + try { + return await viemClient.waitForTransactionReceipt({ + hash: txhash!, + confirmations: 1, + timeout: 120_000, + }); + } catch { + // in case waiting for tx receipt was unsuccessful, try getting the receipt directly + try { + return await viemClient.getTransactionReceipt({ hash: txhash! }); + } catch { + await sleep(Math.max(90_000 + time - Date.now(), 0)); + return await viemClient.getTransactionReceipt({ hash: txhash! }); + } + } + })(); return async () => { try { const receipt = await receiptPromise; @@ -110,44 +122,6 @@ export async function handleTransaction( time, ); } catch (e: any) { - try { - const newReceipt = await (async () => { - try { - return await viemClient.getTransactionReceipt({ hash: txhash }); - } catch { - await sleep(Math.max(90_000 + time - Date.now(), 0)); - return await viemClient.getTransactionReceipt({ hash: txhash }); - } - })(); - if (newReceipt) { - return handleReceipt( - txhash, - newReceipt, - signer, - spanAttributes, - rawtx, - orderbook, - orderPairObject, - inputToEthPrice, - outputToEthPrice, - result, - txUrl, - pair, - toToken, - fromToken, - config, - time, - ); - } - } catch {} - // keep track of gas consumption of the account - let actualGasCost; - try { - actualGasCost = BigNumber.from(e.receipt.effectiveGasPrice).mul(e.receipt.gasUsed); - signer.BALANCE = signer.BALANCE.sub(actualGasCost); - } catch { - /**/ - } result.report = { status: ProcessPairReportStatus.FoundOpportunity, txUrl, @@ -155,9 +129,6 @@ export async function handleTransaction( buyToken: orderPairObject.buyToken, sellToken: orderPairObject.sellToken, }; - if (actualGasCost) { - result.report.actualGasCost = ethers.utils.formatUnits(actualGasCost); - } result.error = e; spanAttributes["details.rawTx"] = JSON.stringify( { @@ -290,7 +261,10 @@ export async function handleReceipt( ); if (result.snapshot.includes("simulation failed to find the revert reason")) { // wait at least 90s before simulating the revert tx - // in order for rpcs to catch up + // in order for rpcs to catch up, this is concurrent to + // whole bot operation, so ideally all of it or at least + // partially will overlap with when bot is processing other + // orders await sleep(Math.max(90_000 + time - Date.now(), 0)); return await handleRevert( signer, diff --git a/src/utils.ts b/src/utils.ts index 2d8dfba9..7328a5da 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,8 +9,13 @@ import { BigNumber, BigNumberish, ethers } from "ethers"; import { erc20Abi, orderbookAbi, OrderV3 } from "./abis"; import { parseAbi, PublicClient, TransactionReceipt } from "viem"; import { doQuoteTargets, QuoteTarget } from "@rainlanguage/orderbook/quote"; -import { BotConfig, BundledOrders, OwnedOrder, TakeOrder, TokenDetails, ViemClient } from "./types"; import { DataFetcher, DataFetcherOptions, LiquidityProviders, Router } from "sushi/router"; +import { BotConfig, BundledOrders, OwnedOrder, TakeOrder, TokenDetails, ViemClient } from "./types"; + +/** + * One ether which equals to 1e18 + */ +export const ONE18 = 1_000_000_000_000_000_000n as const; export function RPoolFilter(pool: any) { return !BlackList.includes(pool.address) && !BlackList.includes(pool.address.toLowerCase()); @@ -152,7 +157,7 @@ export const getActualPrice = ( ); if (eventObj[0] && eventObj[0]?.value) return ethers.utils.formatUnits( - eventObj[0].value.mul("1" + "0".repeat(36 - buyDecimals)).div(amount), + scale18(eventObj[0].value, buyDecimals).mul(ONE18).div(amount), ); else return undefined; }; @@ -931,23 +936,23 @@ export function getTotalIncome( if (inputTokenIncome && outputTokenIncome) { const inputTokenIncomeInEth = ethers.utils .parseUnits(inputTokenPrice) - .mul(inputTokenIncome) - .div("1" + "0".repeat(inputTokenDecimals)); + .mul(scale18(inputTokenIncome, inputTokenDecimals)) + .div(ONE18); const outputTokenIncomeInEth = ethers.utils .parseUnits(outputTokenPrice) - .mul(outputTokenIncome) - .div("1" + "0".repeat(outputTokenDecimals)); + .mul(scale18(outputTokenIncome, outputTokenDecimals)) + .div(ONE18); return inputTokenIncomeInEth.add(outputTokenIncomeInEth); } else if (inputTokenIncome && !outputTokenIncome) { return ethers.utils .parseUnits(inputTokenPrice) - .mul(inputTokenIncome) - .div("1" + "0".repeat(inputTokenDecimals)); + .mul(scale18(inputTokenIncome, inputTokenDecimals)) + .div(ONE18); } else if (!inputTokenIncome && outputTokenIncome) { return ethers.utils .parseUnits(outputTokenPrice) - .mul(outputTokenIncome) - .div("1" + "0".repeat(outputTokenDecimals)); + .mul(scale18(outputTokenIncome, outputTokenDecimals)) + .div(ONE18); } return undefined; } @@ -1298,10 +1303,8 @@ export function getMarketQuote( if (route.status == "NoWay") { return undefined; } else { - const rateFixed = ethers.BigNumber.from(route.amountOutBI).mul( - "1" + "0".repeat(18 - toToken.decimals), - ); - const price = rateFixed.mul("1" + "0".repeat(18)).div(amountInFixed); + const rateFixed = scale18(route.amountOutBI, toToken.decimals); + const price = rateFixed.mul(ONE18).div(amountInFixed); return { price: ethers.utils.formatUnits(price), amountOut: ethers.utils.formatUnits(route.amountOutBI, toToken.decimals), diff --git a/test/findOpp.test.js b/test/findOpp.test.js index 45100f4d..27e14227 100644 --- a/test/findOpp.test.js +++ b/test/findOpp.test.js @@ -15,6 +15,7 @@ let dataFetcher = {}; const viemClient = { getBlockNumber: async () => BigInt(oppBlockNumber), call: async () => ({ data: ethers.BigNumber.from("1000000000000000000").toHexString() }), + readContract: async () => 1000000000000000000n, }; const oppBlockNumber = 123456; diff --git a/test/mode-intraOrderbook.test.js b/test/mode-intraOrderbook.test.js index 2eb00dbe..ba0c4aac 100644 --- a/test/mode-intraOrderbook.test.js +++ b/test/mode-intraOrderbook.test.js @@ -11,6 +11,7 @@ let signer = {}; const viemClient = { getBlockNumber: async () => BigInt(oppBlockNumber), call: async () => ({ data: ethers.BigNumber.from("1000000000000000000").toHexString() }), + readContract: async () => 1000000000000000000n, }; const oppBlockNumber = 123456; diff --git a/test/processPair.test.js b/test/processPair.test.js index 141778eb..952c46a9 100644 --- a/test/processPair.test.js +++ b/test/processPair.test.js @@ -684,6 +684,7 @@ describe("Test process pair", async function () { }; signer.sendTx = async () => txHash; viemClient.waitForTransactionReceipt = async () => Promise.reject(errorRejection); + viemClient.getTransactionReceipt = async () => Promise.reject(errorRejection); try { await ( await processPair({ From 3050a7e98ea0dac41caf1318e059c4ba843a74d3 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 26 Dec 2024 09:05:55 +0000 Subject: [PATCH 9/9] update --- src/utils.ts | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 7328a5da..c117f19f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -933,28 +933,28 @@ export function getTotalIncome( inputTokenDecimals: number, outputTokenDecimals: number, ): BigNumber | undefined { - if (inputTokenIncome && outputTokenIncome) { - const inputTokenIncomeInEth = ethers.utils - .parseUnits(inputTokenPrice) - .mul(scale18(inputTokenIncome, inputTokenDecimals)) - .div(ONE18); - const outputTokenIncomeInEth = ethers.utils - .parseUnits(outputTokenPrice) - .mul(scale18(outputTokenIncome, outputTokenDecimals)) - .div(ONE18); - return inputTokenIncomeInEth.add(outputTokenIncomeInEth); - } else if (inputTokenIncome && !outputTokenIncome) { - return ethers.utils - .parseUnits(inputTokenPrice) - .mul(scale18(inputTokenIncome, inputTokenDecimals)) - .div(ONE18); - } else if (!inputTokenIncome && outputTokenIncome) { - return ethers.utils - .parseUnits(outputTokenPrice) - .mul(scale18(outputTokenIncome, outputTokenDecimals)) - .div(ONE18); - } - return undefined; + if (!inputTokenIncome && !outputTokenIncome) return undefined; + const inputTokenIncomeInEth = (() => { + if (inputTokenIncome) { + return ethers.utils + .parseUnits(inputTokenPrice) + .mul(scale18(inputTokenIncome, inputTokenDecimals)) + .div(ONE18); + } else { + return ethers.constants.Zero; + } + })(); + const outputTokenIncomeInEth = (() => { + if (outputTokenIncome) { + return ethers.utils + .parseUnits(outputTokenPrice) + .mul(scale18(outputTokenIncome, outputTokenDecimals)) + .div(ONE18); + } else { + return ethers.constants.Zero; + } + })(); + return inputTokenIncomeInEth.add(outputTokenIncomeInEth); } /**