From 0ba024d44ea9e655e2bda6e8837bb14380314bac Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:12:02 +0200 Subject: [PATCH 1/4] fix(relayer): Invert gasLimit nullification The relayer has had a bug for a while where it is incorrectly dropping the precomputed gas limit for non-message fills. Instead, it was retaining the limit for message fills. This can lead to unnecessary transaction simulation later on, increasing RPC consumption and potentially delaying fills. --- src/relayer/Relayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 7049ebb83..2d8da1728 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -695,7 +695,7 @@ export class Relayer { // Update local balance to account for the enqueued fill. tokenClient.decrementLocalBalance(destinationChainId, outputToken, outputAmount); - const gasLimit = isMessageEmpty(resolveDepositMessage(deposit)) ? undefined : _gasLimit; + const gasLimit = isMessageEmpty(resolveDepositMessage(deposit)) ? _gasLimit : undefined; this.fillRelay(deposit, repaymentChainId, realizedLpFeePct, gasLimit); } } else if (selfRelay) { From 267013cfbd8d05d1a85f724c28aae7ad4c27466c Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:42:47 +0200 Subject: [PATCH 2/4] Bump test gasLimit 100k -> 150k --- test/mocks/MockProfitClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocks/MockProfitClient.ts b/test/mocks/MockProfitClient.ts index e20a31a21..661f540aa 100644 --- a/test/mocks/MockProfitClient.ts +++ b/test/mocks/MockProfitClient.ts @@ -7,7 +7,7 @@ import { MockHubPoolClient } from "./MockHubPoolClient"; type TransactionCostEstimate = sdkUtils.TransactionCostEstimate; -const defaultFillCost = toBN(100_000); // gas +const defaultFillCost = toBN(150_000); // gas const defaultGasPrice = bnOne; // wei per gas export class MockProfitClient extends ProfitClient { From 31e699f79ab06cf84ad4eec871bdac5c6a53a375 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:52:38 +0200 Subject: [PATCH 3/4] Drop override entirely --- src/relayer/Relayer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 2d8da1728..72311727c 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -667,7 +667,7 @@ export class Relayer { l1Token, lpFees ); - const { relayerFeePct, gasCost, gasLimit: _gasLimit, lpFeePct: realizedLpFeePct } = repaymentChainProfitability; + const { relayerFeePct, gasCost, gasLimit, lpFeePct: realizedLpFeePct } = repaymentChainProfitability; if (!isDefined(repaymentChainId)) { profitClient.captureUnprofitableFill(deposit, realizedLpFeePct, relayerFeePct, gasCost); } else { @@ -695,7 +695,6 @@ export class Relayer { // Update local balance to account for the enqueued fill. tokenClient.decrementLocalBalance(destinationChainId, outputToken, outputAmount); - const gasLimit = isMessageEmpty(resolveDepositMessage(deposit)) ? _gasLimit : undefined; this.fillRelay(deposit, repaymentChainId, realizedLpFeePct, gasLimit); } } else if (selfRelay) { From 6bf012a7fa1e3309f8a5aa23519dfda25f892b31 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 25 Oct 2024 17:28:17 +0200 Subject: [PATCH 4/4] Pre-simulate all tokens --- src/clients/ProfitClient.ts | 59 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/clients/ProfitClient.ts b/src/clients/ProfitClient.ts index ec5907e52..aae048d9b 100644 --- a/src/clients/ProfitClient.ts +++ b/src/clients/ProfitClient.ts @@ -91,7 +91,7 @@ export class ProfitClient { private unprofitableFills: { [chainId: number]: UnprofitableFill[] } = {}; // Track total gas costs of a relay on each chain. - protected totalGasCosts: { [chainId: number]: TransactionCostEstimate } = {}; + protected totalGasCosts: { [chainId: number]: { [outputToken: string]: TransactionCostEstimate } } = {}; // Queries needed to fetch relay gas costs. private relayerFeeQueries: { [chainId: number]: relayFeeCalculator.QueryInterface } = {}; @@ -217,12 +217,12 @@ export class ProfitClient { } async getTotalGasCost(deposit: Deposit): Promise { - const { destinationChainId: chainId } = deposit; + const { destinationChainId, outputToken } = deposit; // If there's no attached message, gas consumption from previous fills can be used in most cases. - // @todo: Simulate this per-token in future, because some ERC20s consume more gas. - if (isMessageEmpty(resolveDepositMessage(deposit)) && isDefined(this.totalGasCosts[chainId])) { - return this.totalGasCosts[chainId]; + const gasCost = this.totalGasCosts[destinationChainId]?.[outputToken]; + if (isMessageEmpty(resolveDepositMessage(deposit)) && isDefined(gasCost)) { + return gasCost; } return this._getTotalGasCost(deposit, this.relayerAddress); @@ -582,19 +582,11 @@ export class ProfitClient { const outputAmount = toBN(100); // Avoid rounding to zero but ensure the relayer has sufficient balance to estimate. const currentTime = getCurrentTime(); - // Prefer USDC on mainnet because it's consistent in terms of gas estimation (no unwrap conditional). - // Prefer WETH on testnet because it's more likely to be configured for the destination SpokePool. - // The relayer _cannot_ be the recipient because the SpokePool skips the ERC20 transfer. Instead, use - // the main RL address because it has all supported tokens and approvals in place on all chains. - const testSymbols = { - [CHAIN_IDs.BLAST]: "USDB", - [CHAIN_IDs.LISK]: "USDT", // USDC is not yet supported on Lisk, so revert to USDT. @todo: Update. - [CHAIN_IDs.REDSTONE]: "WETH", // Redstone only supports WETH. - [CHAIN_IDs.WORLD_CHAIN]: "WETH", // USDC deferred on World Chain. - }; + const ignoredTokens = ["BADGER", "BOBA"]; + const l1Tokens = hubPoolClient.getL1Tokens().filter(({ symbol }) => !ignoredTokens.includes(symbol)); + const prodRelayer = process.env.RELAYER_FILL_SIMULATION_ADDRESS ?? PROD_RELAYER; - const [defaultTestSymbol, relayer] = - this.hubPoolClient.chainId === CHAIN_IDs.MAINNET ? ["USDC", prodRelayer] : ["WETH", TEST_RELAYER]; + const relayer = this.hubPoolClient.chainId === CHAIN_IDs.MAINNET ? prodRelayer : TEST_RELAYER; // @dev The relayer _cannot_ be the recipient because the SpokePool skips the ERC20 transfer. Instead, // use the main RL address because it has all supported tokens and approvals in place on all chains. @@ -617,18 +609,31 @@ export class ProfitClient { toLiteChain: false, }; + const spokeTokens = Object.fromEntries( + enabledChainIds.map((destinationChainId) => { + const spokeTokens = + destinationChainId === hubPoolClient.chainId + ? l1Tokens.map(({ address }) => address) + : l1Tokens + .map(({ address }) => { + try { + return hubPoolClient.getL2TokenForL1TokenAtBlock(address, destinationChainId); + } catch { + return undefined; + } + }) + .filter(isDefined); + return [destinationChainId, spokeTokens]; + }) + ); + // Pre-fetch total gas costs for relays on enabled chains. await sdkUtils.mapAsync(enabledChainIds, async (destinationChainId) => { - const symbol = testSymbols[destinationChainId] ?? defaultTestSymbol; - const hubToken = TOKEN_SYMBOLS_MAP[symbol].addresses[this.hubPoolClient.chainId]; - const outputToken = - destinationChainId === hubPoolClient.chainId - ? hubToken - : hubPoolClient.getL2TokenForL1TokenAtBlock(hubToken, destinationChainId); - assert(isDefined(outputToken), `Chain ${destinationChainId} SpokePool is not configured for ${symbol}`); - - const deposit = { ...sampleDeposit, destinationChainId, outputToken }; - this.totalGasCosts[destinationChainId] = await this._getTotalGasCost(deposit, relayer); + this.totalGasCosts[destinationChainId] ??= {}; + await sdkUtils.mapAsync(spokeTokens[destinationChainId], async (outputToken) => { + const deposit = { ...sampleDeposit, destinationChainId, outputToken }; + this.totalGasCosts[destinationChainId][outputToken] = await this._getTotalGasCost(deposit, relayer); + }); }); this.logger.debug({