From 5cdec8800539f04985f31d009e6477b6333f926e Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 9 Jan 2025 14:23:23 -0500 Subject: [PATCH 1/7] improve(API): Pass gasPrice into relayerFeeDetails call We should pass the pre-computed gas price into this call so that we don't query the gas price twice. Additionally, we can use the scaled up gas price value to derive the gas fee totals, which currently are not using the scaled up values --- api/_utils.ts | 2 ++ api/limits.ts | 33 +++++++++++++++++---------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/api/_utils.ts b/api/_utils.ts index 4b94d2296..2947acba7 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -1948,6 +1948,7 @@ export function isContractCache(chainId: number, address: string) { export function getCachedFillGasUsage( deposit: Parameters[0], + gasPrice: BigNumber, overrides?: Partial<{ spokePoolAddress: string; relayerAddress: string; @@ -1975,6 +1976,7 @@ export function getCachedFillGasUsage( buildDepositForSimulation(deposit), overrides?.relayerAddress, { + gasPrice, // 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) diff --git a/api/limits.ts b/api/limits.ts index fcfc29a33..bfe34c59b 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,22 @@ 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 - ? undefined - : getCachedFillGasUsage(depositArgs, { - relayerAddress: relayer, - }), 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 +217,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", From 3db3ac13f3d2c1a8c3d397a4cd882deb29a2e420 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 9 Jan 2025 17:22:34 -0500 Subject: [PATCH 2/7] Add cache prefix --- api/_cache.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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( From b9e4640d9eb1b6110a0ff98408b2393947f7544c Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 9 Jan 2025 17:31:26 -0500 Subject: [PATCH 3/7] Add op stack multiplier env var --- api/_utils.ts | 32 +++++++++++++++++++++++++++----- api/gas-prices.ts | 2 +- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/api/_utils.ts b/api/_utils.ts index 2947acba7..01d08a8ec 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, }; }; @@ -1977,10 +2001,8 @@ export function getCachedFillGasUsage( overrides?.relayerAddress, { gasPrice, - // 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, + .opStackL1DataFeeMarkup, } ); return { diff --git a/api/gas-prices.ts b/api/gas-prices.ts index fecf59694..4430252cf 100644 --- a/api/gas-prices.ts +++ b/api/gas-prices.ts @@ -72,7 +72,7 @@ const handler = async ( ); const opStackL1GasCostMultiplier = getGasMarkup( Number(chainId) - ).baseFeeMarkup; + ).opStackL1DataFeeMarkup; const { nativeGasCost, tokenGasCost } = await relayerFeeCalculatorQueries.getGasCosts( deposit, From 866ddda03eca7beaacc8f971541a63892cbb0ed0 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 9 Jan 2025 18:19:52 -0500 Subject: [PATCH 4/7] Defer gas price computation for Linea which depends on the unsignedTx --- api/_utils.ts | 5 ++--- api/limits.ts | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/api/_utils.ts b/api/_utils.ts index 01d08a8ec..18c1c1dc1 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -733,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 => { @@ -1972,9 +1972,8 @@ export function isContractCache(chainId: number, address: string) { export function getCachedFillGasUsage( deposit: Parameters[0], - gasPrice: BigNumber, + gasPrice?: BigNumber, overrides?: Partial<{ - spokePoolAddress: string; relayerAddress: string; }> ) { diff --git a/api/limits.ts b/api/limits.ts index bfe34c59b..06a363b14 100644 --- a/api/limits.ts +++ b/api/limits.ts @@ -172,7 +172,11 @@ const handler = async ( ), getCachedTokenPrice(l1Token.address, "usd"), getCachedLatestBlock(HUB_POOL_CHAIN_ID), - latestGasPriceCache(destinationChainId).get(), + // 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 + : latestGasPriceCache(destinationChainId).get(), ]); const tokenPriceUsd = ethers.utils.parseUnits(_tokenPriceUsd.toString()); From 5c78f581dab3a0e18006a0360e3e6ce9969d0cbf Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 9 Jan 2025 18:58:11 -0500 Subject: [PATCH 5/7] import 3.4.8, smplify gas-prices, and don't pass in Linea chain ID into gas-prices --- api/_utils.ts | 7 +++++-- api/gas-prices.ts | 33 ++++++--------------------------- package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 16 insertions(+), 34 deletions(-) diff --git a/api/_utils.ts b/api/_utils.ts index 18c1c1dc1..ecfdf61e8 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -2000,8 +2000,11 @@ export function getCachedFillGasUsage( overrides?.relayerAddress, { gasPrice, - opStackL1GasCostMultiplier: getGasMarkup(deposit.destinationChainId) - .opStackL1DataFeeMarkup, + 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 4430252cf..2fb63a32b 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"; @@ -73,7 +74,7 @@ const handler = async ( const opStackL1GasCostMultiplier = getGasMarkup( Number(chainId) ).opStackL1DataFeeMarkup; - const { nativeGasCost, tokenGasCost } = + const { nativeGasCost, tokenGasCost, opStackL1GasCost } = await relayerFeeCalculatorQueries.getGasCosts( deposit, relayerFeeCalculatorQueries.simulatedRelayerAddress, @@ -81,35 +82,13 @@ 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, + gasPrice: + Number(chainId) === CHAIN_IDs.LINEA + ? undefined + : gasPrices[i].maxFeePerGas, opStackL1GasCostMultiplier, } ); - // 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, 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" From 380ea4e470ce4923ebb68b6502add2eb47f7dd4d Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 9 Jan 2025 19:09:02 -0500 Subject: [PATCH 6/7] Use correct linea price in gas-prices and pass markups correctly in getCachedFillGasUsage --- api/_utils.ts | 3 +++ api/gas-prices.ts | 39 ++++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/api/_utils.ts b/api/_utils.ts index ecfdf61e8..2f686b0a4 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -1995,11 +1995,14 @@ 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, { gasPrice, + baseFeeMultiplier: markups.baseFeeMarkup, + priorityFeeMultiplier: markups.priorityFeeMarkup, opStackL1GasCostMultiplier: sdk.utils.chainIsOPStack( deposit.destinationChainId ) diff --git a/api/gas-prices.ts b/api/gas-prices.ts index 2fb63a32b..427da081e 100644 --- a/api/gas-prices.ts +++ b/api/gas-prices.ts @@ -71,10 +71,9 @@ const handler = async ( const relayerFeeCalculatorQueries = getRelayerFeeCalculatorQueries( Number(chainId) ); - const opStackL1GasCostMultiplier = getGasMarkup( - Number(chainId) - ).opStackL1DataFeeMarkup; - const { nativeGasCost, tokenGasCost, opStackL1GasCost } = + const { baseFeeMarkup, priorityFeeMarkup, opStackL1DataFeeMarkup } = + getGasMarkup(Number(chainId)); + const { nativeGasCost, tokenGasCost, opStackL1GasCost, gasPrice } = await relayerFeeCalculatorQueries.getGasCosts( deposit, relayerFeeCalculatorQueries.simulatedRelayerAddress, @@ -82,17 +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). + // 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, + opStackL1GasCostMultiplier: opStackL1DataFeeMarkup, + baseFeeMultiplier: baseFeeMarkup, + priorityFeeMultiplier: priorityFeeMarkup, } ); return { nativeGasCost, tokenGasCost, opStackL1GasCost, + gasPrice, }; } ) @@ -103,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 ), @@ -118,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(), From dc6becb1b4c358ddf96e0dc90cacd3bb098d4bdb Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 9 Jan 2025 19:10:46 -0500 Subject: [PATCH 7/7] Update _utils.ts --- api/_utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/_utils.ts b/api/_utils.ts index 2f686b0a4..20e004d7a 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -2001,6 +2001,7 @@ export function getCachedFillGasUsage( overrides?.relayerAddress, { gasPrice, + // We want the fee multipliers if the gasPrice is undefined: baseFeeMultiplier: markups.baseFeeMarkup, priorityFeeMultiplier: markups.priorityFeeMarkup, opStackL1GasCostMultiplier: sdk.utils.chainIsOPStack(