From 44a2298e2971c94f2efae6c57ec44f8f0a33ab31 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Fri, 10 Jan 2025 07:12:51 -0500 Subject: [PATCH] improve(API): Pass gasPrice into relayerFeeDetails call + add OP_STACK_L1_DATA_FEE_MARKUP (#1366) --- api/_cache.ts | 5 +++- api/_utils.ts | 46 +++++++++++++++++++++++++------ api/gas-prices.ts | 70 ++++++++++++++++++++++------------------------- api/limits.ts | 37 ++++++++++++++----------- package.json | 2 +- yarn.lock | 8 +++--- 6 files changed, 101 insertions(+), 67 deletions(-) diff --git a/api/_cache.ts b/api/_cache.ts index 2380cbc62..26d385239 100644 --- a/api/_cache.ts +++ b/api/_cache.ts @@ -73,7 +73,10 @@ export function buildCacheKey( } export function buildInternalCacheKey(...args: (string | number)[]): string { - return buildCacheKey("QUOTES_API", ...args); + return buildCacheKey( + `${process.env.CACHE_PREFIX ? process.env.CACHE_PREFIX + "-" : ""}QUOTES_API`, + ...args + ); } export async function getCachedValue( diff --git a/api/_utils.ts b/api/_utils.ts index 4b94d2296..20e004d7a 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -87,6 +87,7 @@ const { REACT_APP_COINGECKO_PRO_API_KEY, BASE_FEE_MARKUP, PRIORITY_FEE_MARKUP, + OP_STACK_L1_DATA_FEE_MARKUP, VERCEL_ENV, LOG_LEVEL, } = process.env; @@ -97,6 +98,9 @@ export const baseFeeMarkup: { export const priorityFeeMarkup: { [chainId: string]: number; } = JSON.parse(PRIORITY_FEE_MARKUP || "{}"); +export const opStackL1DataFeeMarkup: { + [chainId: string]: number; +} = JSON.parse(OP_STACK_L1_DATA_FEE_MARKUP || "{}"); // Default to no markup. export const DEFAULT_GAS_MARKUP = 0; @@ -594,9 +598,14 @@ export const getHubPoolClient = () => { export const getGasMarkup = ( chainId: string | number -): { baseFeeMarkup: BigNumber; priorityFeeMarkup: BigNumber } => { +): { + baseFeeMarkup: BigNumber; + priorityFeeMarkup: BigNumber; + opStackL1DataFeeMarkup: BigNumber; +} => { let _baseFeeMarkup: BigNumber | undefined; let _priorityFeeMarkup: BigNumber | undefined; + let _opStackL1DataFeeMarkup: BigNumber | undefined; if (typeof baseFeeMarkup[chainId] === "number") { _baseFeeMarkup = utils.parseEther((1 + baseFeeMarkup[chainId]).toString()); } @@ -605,6 +614,11 @@ export const getGasMarkup = ( (1 + priorityFeeMarkup[chainId]).toString() ); } + if (typeof opStackL1DataFeeMarkup[chainId] === "number") { + _opStackL1DataFeeMarkup = utils.parseEther( + (1 + opStackL1DataFeeMarkup[chainId]).toString() + ); + } // Otherwise, use default gas markup (or optimism's for OP stack). if (_baseFeeMarkup === undefined) { @@ -627,11 +641,21 @@ export const getGasMarkup = ( ).toString() ); } + if (_opStackL1DataFeeMarkup === undefined) { + _opStackL1DataFeeMarkup = utils.parseEther( + ( + 1 + + (sdk.utils.chainIsOPStack(Number(chainId)) + ? opStackL1DataFeeMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP + : DEFAULT_GAS_MARKUP) + ).toString() + ); + } - // Otherwise, use default gas markup (or optimism's for OP stack). return { baseFeeMarkup: _baseFeeMarkup, priorityFeeMarkup: _priorityFeeMarkup, + opStackL1DataFeeMarkup: _opStackL1DataFeeMarkup, }; }; @@ -709,7 +733,7 @@ export const getRelayerFeeDetails = async ( }, tokenPrice: number, relayerAddress: string, - gasPrice: sdk.utils.BigNumberish, + gasPrice?: sdk.utils.BigNumberish, gasUnits?: sdk.utils.BigNumberish, tokenGasCost?: sdk.utils.BigNumberish ): Promise => { @@ -1948,8 +1972,8 @@ export function isContractCache(chainId: number, address: string) { export function getCachedFillGasUsage( deposit: Parameters[0], + gasPrice?: BigNumber, overrides?: Partial<{ - spokePoolAddress: string; relayerAddress: string; }> ) { @@ -1971,14 +1995,20 @@ export function getCachedFillGasUsage( ); // We don't care about the gas token price or the token gas price, only the raw gas units. In the API // we'll compute the gas price separately. + const markups = getGasMarkup(deposit.destinationChainId); const gasCosts = await relayerFeeCalculatorQueries.getGasCosts( buildDepositForSimulation(deposit), overrides?.relayerAddress, { - // Scale the op stack L1 gas cost component by the base fee multiplier. - // Consider adding a new environment variable OP_STACK_L1_GAS_COST_MARKUP if we want finer-grained control. - opStackL1GasCostMultiplier: getGasMarkup(deposit.destinationChainId) - .baseFeeMarkup, + gasPrice, + // We want the fee multipliers if the gasPrice is undefined: + baseFeeMultiplier: markups.baseFeeMarkup, + priorityFeeMultiplier: markups.priorityFeeMarkup, + opStackL1GasCostMultiplier: sdk.utils.chainIsOPStack( + deposit.destinationChainId + ) + ? getGasMarkup(deposit.destinationChainId).opStackL1DataFeeMarkup + : undefined, } ); return { diff --git a/api/gas-prices.ts b/api/gas-prices.ts index fecf59694..427da081e 100644 --- a/api/gas-prices.ts +++ b/api/gas-prices.ts @@ -15,6 +15,7 @@ import { L2Provider } from "@eth-optimism/sdk/dist/interfaces/l2-provider"; import mainnetChains from "../src/data/chains_1.json"; import { + CHAIN_IDs, DEFAULT_SIMULATED_RECIPIENT_ADDRESS, TOKEN_SYMBOLS_MAP, } from "./_constants"; @@ -70,10 +71,9 @@ const handler = async ( const relayerFeeCalculatorQueries = getRelayerFeeCalculatorQueries( Number(chainId) ); - const opStackL1GasCostMultiplier = getGasMarkup( - Number(chainId) - ).baseFeeMarkup; - const { nativeGasCost, tokenGasCost } = + const { baseFeeMarkup, priorityFeeMarkup, opStackL1DataFeeMarkup } = + getGasMarkup(Number(chainId)); + const { nativeGasCost, tokenGasCost, opStackL1GasCost, gasPrice } = await relayerFeeCalculatorQueries.getGasCosts( deposit, relayerFeeCalculatorQueries.simulatedRelayerAddress, @@ -81,39 +81,22 @@ const handler = async ( // Pass in the already-computed gasPrice into this query so that the tokenGasCost includes // the scaled gas price, // e.g. tokenGasCost = nativeGasCost * (baseFee * baseFeeMultiplier + priorityFee). - gasPrice: gasPrices[i].maxFeePerGas, - opStackL1GasCostMultiplier, + // Except for Linea, where the gas price is dependent on the unsignedTx produced from the deposit, + // so let the SDK compute its gas price here. + gasPrice: + Number(chainId) === CHAIN_IDs.LINEA + ? undefined + : gasPrices[i].maxFeePerGas, + opStackL1GasCostMultiplier: opStackL1DataFeeMarkup, + baseFeeMultiplier: baseFeeMarkup, + priorityFeeMultiplier: priorityFeeMarkup, } ); - // OPStack chains factor in the L1 gas cost of including the L2 transaction in an L1 rollup batch - // into the total gas cost of the L2 transaction. - let opStackL1GasCost: ethers.BigNumber | undefined = undefined; - if (sdk.utils.chainIsOPStack(Number(chainId))) { - const provider = relayerFeeCalculatorQueries.provider; - const _unsignedTx = await sdk.utils.populateV3Relay( - relayerFeeCalculatorQueries.spokePool, - deposit, - relayerFeeCalculatorQueries.simulatedRelayerAddress - ); - const voidSigner = new VoidSigner( - relayerFeeCalculatorQueries.simulatedRelayerAddress, - relayerFeeCalculatorQueries.provider - ); - const unsignedTx = await voidSigner.populateTransaction({ - ..._unsignedTx, - gasLimit: nativeGasCost, // prevents additional gas estimation call - }); - opStackL1GasCost = await ( - provider as L2Provider - ).estimateL1GasCost(unsignedTx); - opStackL1GasCost = opStackL1GasCostMultiplier - .mul(opStackL1GasCost) - .div(sdk.utils.fixedPointAdjustment); - } return { nativeGasCost, tokenGasCost, opStackL1GasCost, + gasPrice, }; } ) @@ -124,12 +107,23 @@ const handler = async ( Object.keys(chainIdsWithToken).map((chainId, i) => [ chainId, { - gasPrice: gasPrices[i].maxFeePerGas.toString(), + gasPrice: + Number(chainId) === CHAIN_IDs.LINEA + ? gasCosts[i].gasPrice.toString() + : gasPrices[i].maxFeePerGas.toString(), gasPriceComponents: { - maxFeePerGas: gasPrices[i].maxFeePerGas - .sub(gasPrices[i].maxPriorityFeePerGas) - .toString(), - priorityFeePerGas: gasPrices[i].maxPriorityFeePerGas.toString(), + // Linea hardcodes base fee at 7 wei so we can always back it out fromthe gasPrice returned by the + // getGasCosts method. + maxFeePerGas: + Number(chainId) === CHAIN_IDs.LINEA + ? gasCosts[i].gasPrice.sub(7).toString() + : gasPrices[i].maxFeePerGas + .sub(gasPrices[i].maxPriorityFeePerGas) + .toString(), + priorityFeePerGas: + Number(chainId) === CHAIN_IDs.LINEA + ? "7" + : gasPrices[i].maxPriorityFeePerGas.toString(), baseFeeMultiplier: ethers.utils.formatEther( getGasMarkup(chainId).baseFeeMarkup ), @@ -139,7 +133,9 @@ const handler = async ( opStackL1GasCostMultiplier: sdk.utils.chainIsOPStack( Number(chainId) ) - ? ethers.utils.formatEther(getGasMarkup(chainId).baseFeeMarkup) + ? ethers.utils.formatEther( + getGasMarkup(chainId).opStackL1DataFeeMarkup + ) : undefined, }, nativeGasCost: gasCosts[i].nativeGasCost.toString(), diff --git a/api/limits.ts b/api/limits.ts index fcfc29a33..06a363b14 100644 --- a/api/limits.ts +++ b/api/limits.ts @@ -164,7 +164,7 @@ const handler = async ( message, }; - const [tokenPriceNative, _tokenPriceUsd, latestBlock, gasCosts, gasPrice] = + const [tokenPriceNative, _tokenPriceUsd, latestBlock, gasPrice] = await Promise.all([ getCachedTokenPrice( l1Token.address, @@ -172,31 +172,26 @@ const handler = async ( ), getCachedTokenPrice(l1Token.address, "usd"), getCachedLatestBlock(HUB_POOL_CHAIN_ID), - // Only use cached gas units if message is not defined, i.e. standard for standard bridges - isMessageDefined + // If Linea, then we will defer gas price estimation to the SDK in getCachedFillGasUsage because + // the priority fee depends upon the fill transaction calldata. + destinationChainId === CHAIN_IDs.LINEA ? undefined - : getCachedFillGasUsage(depositArgs, { - relayerAddress: relayer, - }), - latestGasPriceCache(destinationChainId).get(), + : latestGasPriceCache(destinationChainId).get(), ]); const tokenPriceUsd = ethers.utils.parseUnits(_tokenPriceUsd.toString()); const [ - relayerFeeDetails, + gasCosts, multicallOutput, fullRelayerBalances, transferRestrictedBalances, fullRelayerMainnetBalances, ] = await Promise.all([ - getRelayerFeeDetails( - depositArgs, - tokenPriceNative, - relayer, - gasPrice, - gasCosts?.nativeGasCost, - gasCosts?.tokenGasCost - ), + isMessageDefined + ? undefined // Only use cached gas units if message is not defined, i.e. standard for standard bridges + : getCachedFillGasUsage(depositArgs, gasPrice, { + relayerAddress: relayer, + }), callViaMulticall3(provider, multiCalls, { blockTag: latestBlock.number, }), @@ -226,6 +221,16 @@ const handler = async ( ) ), ]); + // This call should not make any additional RPC queries if gasCosts is defined--for any deposit + // with an empty message. + const relayerFeeDetails = await getRelayerFeeDetails( + depositArgs, + tokenPriceNative, + relayer, + gasPrice, + gasCosts?.nativeGasCost, + gasCosts?.tokenGasCost + ); logger.debug({ at: "Limits", message: "Relayer fee details from SDK", diff --git a/package.json b/package.json index 6ec93dc78..4ff85caf5 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@across-protocol/constants": "^3.1.24", "@across-protocol/contracts": "^3.0.19", "@across-protocol/contracts-v3.0.6": "npm:@across-protocol/contracts@3.0.6", - "@across-protocol/sdk": "^3.4.7", + "@across-protocol/sdk": "^3.4.8", "@amplitude/analytics-browser": "^2.3.5", "@balancer-labs/sdk": "1.1.6-beta.16", "@emotion/react": "^11.13.0", diff --git a/yarn.lock b/yarn.lock index 9b06bf5bf..fd3648c77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -83,10 +83,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^3.4.7": - version "3.4.7" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.4.7.tgz#6ddf9698f918d7b7e0216327d60b54b37fe14f22" - integrity sha512-GeyzDG8EzlN8oddmjXASqND+usZPkWDLpzbdWfAfBfHT3pjIMatntZqZghfCfjy+ICf+rlYrAb8I24H4jlct8Q== +"@across-protocol/sdk@^3.4.8": + version "3.4.8" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.4.8.tgz#070abb97b687cfe22d89349d776f68008af5f6a7" + integrity sha512-m4JnT3Sh+zmTZ/Oi7QrTv3IuNB+myBcbnPnEEssZroyBost/yEPyxXks+EHeU67KrT84t/otGyNb5YpTOvOK0A== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.25"