From 279efc320f2fc1975ec164a32ebcbbbba3f7ac00 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Fri, 6 Sep 2024 14:26:27 +0700 Subject: [PATCH] perf: cache gas units for fill --- api/_utils.ts | 179 ++++++++++++++++++++++++++++++++---------- api/limits.ts | 29 +++++-- api/suggested-fees.ts | 18 +++-- 3 files changed, 172 insertions(+), 54 deletions(-) diff --git a/api/_utils.ts b/api/_utils.ts index bdda67da4..19411d5b1 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -14,7 +14,14 @@ import { } from "@balancer-labs/sdk"; import { Log, Logging } from "@google-cloud/logging"; import axios from "axios"; -import { BigNumber, ethers, providers, Signer, utils } from "ethers"; +import { + BigNumber, + BigNumberish, + ethers, + providers, + utils, + Signer, +} from "ethers"; import { StructError, define } from "superstruct"; import enabledMainnetRoutesAsJson from "../src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json"; @@ -532,16 +539,7 @@ export const getRelayerFeeCalculator = ( relayerAddress: string; }> = {} ) => { - const queries = sdk.relayFeeCalculator.QueryBase__factory.create( - destinationChainId, - getProvider(destinationChainId), - undefined, - overrides.spokePoolAddress || getSpokePoolAddress(destinationChainId), - overrides.relayerAddress, - REACT_APP_COINGECKO_PRO_API_KEY, - getLogger(), - getGasMarkup(destinationChainId) - ); + const queries = getRelayerFeeCalculatorQueries(destinationChainId, overrides); const relayerFeeCalculatorConfig = { feeLimitPercent: maxRelayFeePct * 100, queries, @@ -557,6 +555,25 @@ export const getRelayerFeeCalculator = ( ); }; +const getRelayerFeeCalculatorQueries = ( + destinationChainId: number, + overrides: Partial<{ + spokePoolAddress: string; + relayerAddress: string; + }> = {} +) => { + return sdk.relayFeeCalculator.QueryBase__factory.create( + destinationChainId, + getProvider(destinationChainId), + undefined, + overrides.spokePoolAddress || getSpokePoolAddress(destinationChainId), + overrides.relayerAddress, + REACT_APP_COINGECKO_PRO_API_KEY, + getLogger(), + getGasMarkup(destinationChainId) + ); +}; + /** * Resolves a tokenAddress to a given textual symbol * @param tokenAddress The token address to convert into a symbol @@ -584,48 +601,53 @@ export const getTokenSymbol = (tokenAddress: string): string => { * @param tokenPrice An optional overred price to prevent the SDK from creating its own call * @param message An optional message to include in the transfer * @param relayerAddress An optional relayer address to use for the transfer + * @param gasPrice An optional gas price to use for the transfer + * @param gasUnits An optional gas unit to use for the transfer * @returns The a promise to the relayer fee for the given `amount` of transferring `l1Token` to `destinationChainId` */ export const getRelayerFeeDetails = async ( - inputToken: string, - outputToken: string, - amount: sdk.utils.BigNumberish, - originChainId: number, - destinationChainId: number, - recipientAddress: string, + deposit: { + inputToken: string; + outputToken: string; + amount: sdk.utils.BigNumberish; + originChainId: number; + destinationChainId: number; + recipientAddress: string; + }, tokenPrice?: number, message?: string, relayerAddress?: string, - gasPrice?: sdk.utils.BigNumberish + gasPrice?: sdk.utils.BigNumberish, + gasUnits?: sdk.utils.BigNumberish ): Promise => { + const { + inputToken, + outputToken, + amount, + originChainId, + destinationChainId, + recipientAddress, + } = deposit; const relayFeeCalculator = getRelayerFeeCalculator(destinationChainId, { relayerAddress, }); try { return await relayFeeCalculator.relayerFeeDetails( - { - inputAmount: sdk.utils.toBN(amount), - outputAmount: sdk.utils.toBN(amount), - depositId: sdk.utils.bnUint32Max.toNumber(), - depositor: recipientAddress, - recipient: recipientAddress, - destinationChainId, - originChainId, - quoteTimestamp: sdk.utils.getCurrentTime() - 60, // Set the quote timestamp to 60 seconds ago ~ 1 ETH block + buildDepositForSimulation({ + amount: amount.toString(), inputToken, outputToken, - fillDeadline: sdk.utils.bnUint32Max.toNumber(), // Defined as `INFINITE_FILL_DEADLINE` in SpokePool.sol - exclusiveRelayer: sdk.constants.ZERO_ADDRESS, - exclusivityDeadline: 0, // Defined as ZERO in SpokePool.sol - message: message ?? sdk.constants.EMPTY_MESSAGE, - fromLiteChain: false, // FIXME - toLiteChain: false, // FIXME - }, + recipientAddress, + originChainId, + destinationChainId, + message, + }), amount, sdk.utils.isMessageEmpty(message), relayerAddress, - tokenPrice - // gasPrice // FIXME + tokenPrice, + gasPrice, // FIXME: We need properly cache the gas price before this can be used reliably + gasUnits ); } catch (err: unknown) { const reason = resolveEthersError(err); @@ -633,6 +655,44 @@ export const getRelayerFeeDetails = async ( } }; +export const buildDepositForSimulation = (depositArgs: { + amount: BigNumberish; + inputToken: string; + outputToken: string; + recipientAddress: string; + originChainId: number; + destinationChainId: number; + message?: string; +}) => { + const { + amount, + inputToken, + outputToken, + recipientAddress, + originChainId, + destinationChainId, + message, + } = depositArgs; + return { + inputAmount: sdk.utils.toBN(amount), + outputAmount: sdk.utils.toBN(amount), + depositId: sdk.utils.bnUint32Max.toNumber(), + depositor: recipientAddress, + recipient: recipientAddress, + destinationChainId, + originChainId, + quoteTimestamp: sdk.utils.getCurrentTime() - 60, // Set the quote timestamp to 60 seconds ago ~ 1 ETH block + inputToken, + outputToken, + fillDeadline: sdk.utils.bnUint32Max.toNumber(), // Defined as `INFINITE_FILL_DEADLINE` in SpokePool.sol + exclusiveRelayer: sdk.constants.ZERO_ADDRESS, + exclusivityDeadline: 0, // Defined as ZERO in SpokePool.sol + message: message ?? sdk.constants.EMPTY_MESSAGE, + fromLiteChain: false, // FIXME + toLiteChain: false, // FIXME + }; +}; + /** * Creates an HTTP call to the `/api/coingecko` endpoint to resolve a CoinGecko price * @param l1Token The ERC20 token address of the coin to find the cached price of @@ -911,7 +971,7 @@ export function getMulticall3( chainId: number, signerOrProvider?: Signer | providers.Provider ): Multicall3 | undefined { - const address = sdk.utils.multicall3Addresses[chainId]; + const address = sdk.utils.getMulticallAddress(chainId); // no multicall on this chain if (!address) { @@ -1762,14 +1822,17 @@ export function getCachedLatestBlock(chainId: number) { export function getCachedGasPrice(chainId: number) { const ttlPerChain = { - default: 10, - [CHAIN_IDs.MAINNET]: 12, + default: 5, + [CHAIN_IDs.ARBITRUM]: 2, }; return getCachedValue( buildInternalCacheKey("gasPrice", chainId), ttlPerChain[chainId] || ttlPerChain.default, - () => getProvider(chainId).getGasPrice(), + async () => { + const gasPrice = await getProvider(chainId).getGasPrice(); + return gasPrice.mul(2); + }, (bnFromCache) => BigNumber.from(bnFromCache) ); } @@ -1792,6 +1855,42 @@ export function getCachedLatestBalance( ); } +export function getCachedFillGasUsage( + deposit: Parameters[0], + overrides?: Partial<{ + spokePoolAddress: string; + relayerAddress: string; + }> +) { + const ttlPerChain = { + default: 10, + [CHAIN_IDs.ARBITRUM]: 10, + }; + + const cacheKey = buildInternalCacheKey( + "fillGasUsage", + deposit.destinationChainId, + deposit.outputToken + ); + const ttl = ttlPerChain[deposit.destinationChainId] || ttlPerChain.default; + const fetchFn = async () => { + const relayerFeeCalculatorQueries = getRelayerFeeCalculatorQueries( + deposit.destinationChainId, + overrides + ); + const { nativeGasCost } = await relayerFeeCalculatorQueries.getGasCosts( + buildDepositForSimulation(deposit), + overrides?.relayerAddress, + await getCachedGasPrice(deposit.destinationChainId) + ); + return nativeGasCost; + }; + + return getCachedValue(cacheKey, ttl, fetchFn, (bnFromCache) => + BigNumber.from(bnFromCache) + ); +} + /** * Builds a URL search string from an object of query parameters. * diff --git a/api/limits.ts b/api/limits.ts index 66b298542..7c8fe4b5f 100644 --- a/api/limits.ts +++ b/api/limits.ts @@ -31,6 +31,7 @@ import { validateChainAndTokenParams, getCachedLatestBlock, getCachedGasPrice, + getCachedFillGasUsage, } from "./_utils"; const LimitsQueryParamsSchema = object({ @@ -111,7 +112,20 @@ const handler = async ( }, ]; - const [tokenPriceNative, _tokenPriceUsd, latestBlock, gasPrice] = + const depositArgs = { + amount: ethers.BigNumber.from("10").pow(l1Token.decimals), + inputToken: inputToken.address, + outputToken: outputToken.address, + recipientAddress: DEFAULT_SIMULATED_RECIPIENT_ADDRESS, + originChainId: computedOriginChainId, + destinationChainId, + }; + const relayerAddress = getDefaultRelayerAddress( + destinationChainId, + l1Token.symbol + ); + + const [tokenPriceNative, _tokenPriceUsd, latestBlock, gasPrice, gasUnits] = await Promise.all([ getCachedTokenPrice( l1Token.address, @@ -120,6 +134,9 @@ const handler = async ( getCachedTokenPrice(l1Token.address, "usd"), getCachedLatestBlock(HUB_POOL_CHAIN_ID), getCachedGasPrice(destinationChainId), + getCachedFillGasUsage(depositArgs, { + relayerAddress, + }), ]); const tokenPriceUsd = ethers.utils.parseUnits(_tokenPriceUsd.toString()); @@ -131,16 +148,12 @@ const handler = async ( fullRelayerMainnetBalances, ] = await Promise.all([ getRelayerFeeDetails( - inputToken.address, - outputToken.address, - ethers.BigNumber.from("10").pow(l1Token.decimals), - computedOriginChainId, - destinationChainId, - DEFAULT_SIMULATED_RECIPIENT_ADDRESS, + depositArgs, tokenPriceNative, undefined, getDefaultRelayerAddress(destinationChainId, l1Token.symbol), - gasPrice + gasPrice, + gasUnits ), callViaMulticall3(provider, multiCalls, { blockTag: latestBlock.number, diff --git a/api/suggested-fees.ts b/api/suggested-fees.ts index 834e98ecf..7adcfe701 100644 --- a/api/suggested-fees.ts +++ b/api/suggested-fees.ts @@ -29,6 +29,7 @@ import { getCachedLimits, getCachedLatestBlock, getCachedGasPrice, + getCachedFillGasUsage, } from "./_utils"; import { selectExclusiveRelayer } from "./_exclusivity"; import { resolveTiming, resolveRebalanceTiming } from "./_timings"; @@ -205,6 +206,15 @@ const handler = async ( }, ]; + const depositArgs = { + amount, + inputToken: inputToken.address, + outputToken: outputToken.address, + recipientAddress: recipient, + originChainId: computedOriginChainId, + destinationChainId, + }; + const [ [currentUt, nextUt, _quoteTimestamp, rawL1TokenConfig], tokenPrice, @@ -222,6 +232,7 @@ const handler = async ( destinationChainId ), getCachedGasPrice(destinationChainId), + getCachedFillGasUsage(depositArgs, { relayerAddress: relayer }), ]); const quoteTimestamp = parseInt(_quoteTimestamp.toString()); @@ -253,12 +264,7 @@ const handler = async ( ); const lpFeeTotal = amount.mul(lpFeePct).div(ethers.constants.WeiPerEther); const relayerFeeDetails = await getRelayerFeeDetails( - inputToken.address, - outputToken.address, - amount, - computedOriginChainId, - destinationChainId, - recipient, + depositArgs, tokenPrice, message, relayer,