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",