From 4680e5b020ec635abfe956ca67dec9a822b30810 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 16 Jan 2025 03:03:00 +0000 Subject: [PATCH] update --- src/abis.ts | 49 ++++++++++++++++++++++++++++++++++++++++++ src/gas.ts | 51 +++++++++++++++++++++++++++++++++++++++++++- src/processOrders.ts | 13 ++++++----- test/gas.test.ts | 31 ++++++++++++++++++++++++++- 4 files changed, 135 insertions(+), 9 deletions(-) diff --git a/src/abis.ts b/src/abis.ts index 8789822e..8c6a2956 100644 --- a/src/abis.ts +++ b/src/abis.ts @@ -29,6 +29,8 @@ export const TakeOrdersConfigV3 = `(uint256 minimumInput, uint256 maximumInput, uint256 maximumIORatio, ${TakeOrderConfigV3}[] orders, bytes data)` as const; export const ClearConfig = "(uint256 aliceInputIOIndex, uint256 aliceOutputIOIndex, uint256 bobInputIOIndex, uint256 bobOutputIOIndex, uint256 aliceBountyVaultId, uint256 bobBountyVaultId)" as const; +export const Quote = + `(${OrderV3} order, uint256 inputIOIndex, uint256 outputIOIndex, ${SignedContextV1}[] signedContext)` as const; /** * Minimal ABI for Orderbook contract only including vaultBalance() function @@ -48,6 +50,7 @@ export const orderbookAbi = [ `function takeOrders2(${TakeOrdersConfigV3} memory config) external returns (uint256 totalInput, uint256 totalOutput)`, `function clear2(${OrderV3} memory aliceOrder, ${OrderV3} memory bobOrder, ${ClearConfig} calldata clearConfig, ${SignedContextV1}[] memory aliceSignedContext, ${SignedContextV1}[] memory bobSignedContext) external`, `event TakeOrderV2(address sender, ${TakeOrderConfigV3} config, uint256 input, uint256 output)`, + `function quote(${Quote} calldata quoteConfig) external view returns (bool, uint256, uint256)`, ] as const; /** @@ -104,3 +107,49 @@ export const DefaultArbEvaluable = { } as const; export const TakeOrderV2EventAbi = parseAbi([orderbookAbi[13]]); + +/** + * Arbitrum node interface address, used to get L1 gas limit. + * This is not an actual deployed smart contract, it is only + * available to be called through an Arbitrum RPC node, and not + * as normally other smart contracts are called. + */ +export const ArbitrumNodeInterfaceAddress: `0x${string}` = + "0x00000000000000000000000000000000000000C8" as const; + +/** + * Arbitrum node interface abi, used to get L1 gas limit + */ +export const ArbitrumNodeInterfaceAbi = [ + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "bool", name: "contractCreation", type: "bool" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "gasEstimateComponents", + outputs: [ + { internalType: "uint64", name: "gasEstimate", type: "uint64" }, + { internalType: "uint64", name: "gasEstimateForL1", type: "uint64" }, + { internalType: "uint256", name: "baseFee", type: "uint256" }, + { internalType: "uint256", name: "l1BaseFeeEstimate", type: "uint256" }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "bool", name: "contractCreation", type: "bool" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "gasEstimateL1Component", + outputs: [ + { internalType: "uint64", name: "gasEstimateForL1", type: "uint64" }, + { internalType: "uint256", name: "baseFee", type: "uint256" }, + { internalType: "uint256", name: "l1BaseFeeEstimate", type: "uint256" }, + ], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/src/gas.ts b/src/gas.ts index c8ef953b..fecf0af3 100644 --- a/src/gas.ts +++ b/src/gas.ts @@ -1,6 +1,10 @@ +import { ChainId } from "sushi"; import { BigNumber } from "ethers"; +import { getQuoteConfig } from "./utils"; import { publicActionsL2, walletActionsL2 } from "viem/op-stack"; -import { BotConfig, OperationState, RawTx, ViemClient } from "./types"; +import { encodeFunctionData, multicall3Abi, parseAbi, toHex } from "viem"; +import { BotConfig, BundledOrders, OperationState, RawTx, ViemClient } from "./types"; +import { ArbitrumNodeInterfaceAbi, ArbitrumNodeInterfaceAddress, orderbookAbi } from "./abis"; /** * Estimates gas cost of the given tx, also takes into account L1 gas cost if the chain is a special L2. @@ -85,3 +89,48 @@ export async function getGasPrice(config: BotConfig, state: OperationState) { state.l1GasPrice = l1GasPriceResult.value; } } + +/** + * Calculates the gas limit that used for quoting orders + */ +export async function getQuoteGas( + config: BotConfig, + orderDetails: BundledOrders, + multicallAddressOverride?: string, +): Promise { + if (config.chain.id === ChainId.ARBITRUM) { + const quoteConfig = getQuoteConfig(orderDetails.takeOrders[0]) as any; + quoteConfig.inputIOIndex = BigInt(quoteConfig.inputIOIndex); + quoteConfig.outputIOIndex = BigInt(quoteConfig.outputIOIndex); + quoteConfig.order.evaluable.bytecode = toHex(quoteConfig.order.evaluable.bytecode); + const multicallConfig = { + target: orderDetails.orderbook as `0x${string}`, + allowFailure: false, + callData: encodeFunctionData({ + abi: parseAbi([orderbookAbi[14]]), + functionName: "quote", + args: [quoteConfig], + }), + }; + const calldata = encodeFunctionData({ + abi: multicall3Abi, + functionName: "aggregate3", + args: [[multicallConfig] as const], + }); + + const multicallAddress = + (multicallAddressOverride as `0x${string}` | undefined) ?? + config.viemClient.chain?.contracts?.multicall3?.address; + if (!multicallAddress) throw "unknown multicall address"; + + const result = await config.viemClient.simulateContract({ + address: ArbitrumNodeInterfaceAddress, + abi: ArbitrumNodeInterfaceAbi, + functionName: "gasEstimateL1Component", + args: [multicallAddress, false, calldata], + }); + return config.quoteGas + (result?.result[0] ?? 0n); + } else { + return config.quoteGas; + } +} diff --git a/src/processOrders.ts b/src/processOrders.ts index ee68292c..e803eeab 100644 --- a/src/processOrders.ts +++ b/src/processOrders.ts @@ -1,5 +1,6 @@ import { ChainId } from "sushi"; import { findOpp } from "./modes"; +import { getQuoteGas } from "./gas"; import { PublicClient } from "viem"; import { Token } from "sushi/currency"; import { createViemClient } from "./config"; @@ -435,7 +436,7 @@ export async function processPair(args: { orderbooksOrders, state, } = args; - + const isE2eTest = (config as any).isTest; const spanAttributes: SpanAttrs = {}; const result: ProcessPairResult = { reason: undefined, @@ -467,14 +468,12 @@ export async function processPair(args: { symbol: orderPairObject.buyTokenSymbol, }); - // eslint-disable-next-line no-console - console.log(config.quoteGas); try { await quoteSingleOrder( orderPairObject, - (config as any).isTest ? (config as any).quoteRpc : config.rpc, + isE2eTest ? (config as any).quoteRpc : config.rpc, undefined, - config.quoteGas, + isE2eTest ? config.quoteGas : await getQuoteGas(config, orderPairObject), ); if (orderPairObject.takeOrders[0].quote?.maxOutput.isZero()) { result.report = { @@ -510,7 +509,7 @@ export async function processPair(args: { fetchPoolsTimeout: 90000, }; // pin block number for test case - if ((config as any).isTest && (config as any).testBlockNumber) { + if (isE2eTest && (config as any).testBlockNumber) { (options as any).blockNumber = (config as any).testBlockNumber; } await dataFetcher.fetchPoolsForToken(fromToken, toToken, PoolBlackList, options); @@ -546,7 +545,7 @@ export async function processPair(args: { fetchPoolsTimeout: 30000, }; // pin block number for test case - if ((config as any).isTest && (config as any).testBlockNumber) { + if (isE2eTest && (config as any).testBlockNumber) { (options as any).blockNumber = (config as any).testBlockNumber; } inputToEthPrice = await getEthPrice( diff --git a/test/gas.test.ts b/test/gas.test.ts index 6e5d5c66..60ca5276 100644 --- a/test/gas.test.ts +++ b/test/gas.test.ts @@ -1,6 +1,8 @@ import { assert } from "chai"; +import { orderPairObject1 } from "./data"; import { OperationState, ViemClient } from "../src/types"; -import { estimateGasCost, getGasPrice, getL1Fee, getTxFee } from "../src/gas"; +import { estimateGasCost, getGasPrice, getL1Fee, getQuoteGas, getTxFee } from "../src/gas"; +import { ChainId } from "sushi"; describe("Test gas", async function () { it("should estimate gas correctly for L1 and L2 chains", async function () { @@ -126,4 +128,31 @@ describe("Test gas", async function () { assert.equal(state2.gasPrice, gasPrice); assert.equal(state2.l1GasPrice, l1GasPrice); }); + + it("should get quote gas", async function () { + const limitGas = 1_000_000n; + const arbitrumL1Gas = 2_000_000n; + const multicallAddress = "0x" + "1".repeat(40); + + // mock order and bot config and viem client + const orderDetails = orderPairObject1; + const config = { + chain: { + id: ChainId.ARBITRUM, + }, + quoteGas: limitGas, + viemClient: { + simulateContract: async () => ({ result: [arbitrumL1Gas, 1_500_000n, 123_000n] }), + }, + } as any; + + // arbitrum chain + let result = await getQuoteGas(config, orderDetails, multicallAddress); + assert.equal(result, limitGas + arbitrumL1Gas); + + // other chains + config.chain.id = 1; + result = await getQuoteGas(config, orderDetails, multicallAddress); + assert.equal(result, limitGas); + }); });