From 5150ff885b0f524ff7b1bdd28613354b5101d753 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:26:06 +0100 Subject: [PATCH 01/16] refactor(relayer): Factor out MDC computation Making room for follow-up changes to improve relayer performance. --- src/relayer/Relayer.ts | 62 +++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index a3dca7052..be0b7774d 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -208,30 +208,19 @@ export class Relayer { return true; } - async checkForUnfilledDepositsAndFill(sendSlowRelays = true): Promise { - // Fetch all unfilled deposits, order by total earnable fee. - const { config } = this; - const { hubPoolClient, profitClient, spokePoolClients, tokenClient, multiCallerClient } = this.clients; - - // Flush any pre-existing enqueued transactions that might not have been executed. - multiCallerClient.clearTransactionQueue(); - - // Fetch unfilled deposits and filter out deposits upfront before we compute the minimum deposit confirmation - // per chain, which is based on the deposit volume we could fill. - const unfilledDeposits = await this._getUnfilledDeposits(); + computeRequiredDepositConfirmations(deposits: V3Deposit[]): { [chainId: number]: number } { + const { profitClient } = this.clients; + const { minDepositConfirmations } = this.config; // Sum the total unfilled deposit amount per origin chain and set a MDC for that chain. - const unfilledDepositAmountsPerChain: { [chainId: number]: BigNumber } = unfilledDeposits.reduce( - (agg, { deposit }) => { - const unfilledAmountUsd = profitClient.getFillAmountInUsd(deposit, deposit.outputAmount); - agg[deposit.originChainId] = (agg[deposit.originChainId] ?? bnZero).add(unfilledAmountUsd); - return agg; - }, - {} - ); + const unfilledDepositAmountsPerChain: { [chainId: number]: BigNumber } = deposits.reduce((agg, deposit) => { + const unfilledAmountUsd = profitClient.getFillAmountInUsd(deposit, deposit.outputAmount); + agg[deposit.originChainId] = (agg[deposit.originChainId] ?? bnZero).add(unfilledAmountUsd); + return agg; + }, {}); // Sort thresholds in ascending order. - const minimumDepositConfirmationThresholds = Object.keys(config.minDepositConfirmations) + const minimumDepositConfirmationThresholds = Object.keys(minDepositConfirmations) .filter((x) => x !== "default") .sort((x, y) => Number(x) - Number(y)); @@ -239,14 +228,13 @@ export class Relayer { // If we can't find a threshold greater than the USD amount, then use the default. const mdcPerChain = Object.fromEntries( Object.entries(unfilledDepositAmountsPerChain).map(([chainId, unfilledAmount]) => { - const usdThreshold = minimumDepositConfirmationThresholds.find((_usdThreshold) => { - return ( - toBNWei(_usdThreshold).gte(unfilledAmount) && - isDefined(config.minDepositConfirmations[_usdThreshold][chainId]) - ); - }); + const usdThreshold = minimumDepositConfirmationThresholds.find( + (usdThreshold) => + toBNWei(usdThreshold).gte(unfilledAmount) && isDefined(minDepositConfirmations[usdThreshold][chainId]) + ); + // If no thresholds are greater than unfilled amount, then use fallback which should have largest MDCs. - return [chainId, config.minDepositConfirmations[usdThreshold ?? "default"][chainId]]; + return [chainId, minDepositConfirmations[usdThreshold ?? "default"][chainId]]; }) ); this.logger.debug({ @@ -254,8 +242,26 @@ export class Relayer { message: "Setting minimum deposit confirmation based on origin chain aggregate deposit amount", unfilledDepositAmountsPerChain, mdcPerChain, - minDepositConfirmations: config.minDepositConfirmations, + minDepositConfirmations, }); + return mdcPerChain; + } + + async checkForUnfilledDepositsAndFill(sendSlowRelays = true): Promise { + // Fetch all unfilled deposits, order by total earnable fee. + const { config } = this; + const { hubPoolClient, profitClient, spokePoolClients, tokenClient, multiCallerClient } = this.clients; + + // Flush any pre-existing enqueued transactions that might not have been executed. + multiCallerClient.clearTransactionQueue(); + + // Fetch unfilled deposits and filter out deposits upfront before we compute the minimum deposit confirmation + // per chain, which is based on the deposit volume we could fill. + const unfilledDeposits = await this._getUnfilledDeposits(); + + const mdcPerChain = this.computeRequiredDepositConfirmations( + Object.values(unfilledDeposits.map(({ deposit }) => deposit)) + ); // Filter out deposits whose block time does not meet the minimum number of confirmations for the origin chain. const confirmedUnfilledDeposits = unfilledDeposits From 44bdb9a172231051e6072007801b30fa96e65fce Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:38:17 +0100 Subject: [PATCH 02/16] refactor(relayer): Defer deposit MDCs check Rather than duplicating the array of unfilled deposits, just check it as the first conditional in the subsequent loop. This allows the entire function to be subsequently factored away, which will occur in a follow-up commit. --- src/relayer/Relayer.ts | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index be0b7774d..753b5e829 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -263,27 +263,20 @@ export class Relayer { Object.values(unfilledDeposits.map(({ deposit }) => deposit)) ); - // Filter out deposits whose block time does not meet the minimum number of confirmations for the origin chain. - const confirmedUnfilledDeposits = unfilledDeposits - .filter( - ({ deposit: { originChainId, blockNumber } }) => - blockNumber <= spokePoolClients[originChainId].latestBlockSearched - mdcPerChain[originChainId] - ) - .map(({ deposit }) => deposit); - this.logger.debug({ - at: "Relayer::checkForUnfilledDepositsAndFill", - message: `${confirmedUnfilledDeposits.length} unfilled deposits found`, - }); - - // Iterate over all unfilled deposits. For each unfilled deposit: a) check that the token balance client has enough - // balance to fill the unfilled amount. b) the fill is profitable. If both hold true then fill the unfilled amount. - // If not enough ballance add the shortfall to the shortfall tracker to produce an appropriate log. If the deposit - // is has no other fills then send a 0 sized fill to initiate a slow relay. If unprofitable then add the - // unprofitable tx to the unprofitable tx tracker to produce an appropriate log. + // Iterate over all unfilled deposits. For each unfilled deposit, check that: + // a) it exceeds the minimum number of required block confirmations, + // b) the token balance client has enough tokens to fill it, + // c) the fill is profitable. + // If all hold true then complete the fill. Otherwise, if slow fills are enabled, request a slow fill. const { slowDepositors } = config; - for (const deposit of confirmedUnfilledDeposits) { + for (const deposit of unfilledDeposits.map(({ deposit }) => deposit)) { const { depositor, recipient, destinationChainId, originChainId, inputToken, outputAmount } = deposit; + // If the deposit does not meet the minimum number of block confirmations, skip it. + if (deposit.blockNumber > spokePoolClients[originChainId].latestBlockSearched - mdcPerChain[originChainId]) { + continue; + } + // If depositor is on the slow deposit list, then send a zero fill to initiate a slow relay and return early. if (slowDepositors?.includes(depositor)) { if (sendSlowRelays) { From bfef865a7149a9ed7e75fbee8619910b0a304fc0 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 20 Mar 2024 23:17:57 +0100 Subject: [PATCH 03/16] refactor(relayer): Relocate profitability & fill execution Factoring out this functionality permits it to be executed in parallel per destination chain. --- src/relayer/Relayer.ts | 77 +++++++++++++++++++++++++-------------- test/Relayer.BasicFill.ts | 2 +- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 753b5e829..f88d2712a 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -247,34 +247,30 @@ export class Relayer { return mdcPerChain; } - async checkForUnfilledDepositsAndFill(sendSlowRelays = true): Promise { - // Fetch all unfilled deposits, order by total earnable fee. - const { config } = this; - const { hubPoolClient, profitClient, spokePoolClients, tokenClient, multiCallerClient } = this.clients; - - // Flush any pre-existing enqueued transactions that might not have been executed. - multiCallerClient.clearTransactionQueue(); - - // Fetch unfilled deposits and filter out deposits upfront before we compute the minimum deposit confirmation - // per chain, which is based on the deposit volume we could fill. - const unfilledDeposits = await this._getUnfilledDeposits(); - - const mdcPerChain = this.computeRequiredDepositConfirmations( - Object.values(unfilledDeposits.map(({ deposit }) => deposit)) - ); - - // Iterate over all unfilled deposits. For each unfilled deposit, check that: - // a) it exceeds the minimum number of required block confirmations, - // b) the token balance client has enough tokens to fill it, - // c) the fill is profitable. - // If all hold true then complete the fill. Otherwise, if slow fills are enabled, request a slow fill. - const { slowDepositors } = config; - for (const deposit of unfilledDeposits.map(({ deposit }) => deposit)) { - const { depositor, recipient, destinationChainId, originChainId, inputToken, outputAmount } = deposit; + // Iterate over all unfilled deposits. For each unfilled deposit, check that: + // a) it exceeds the minimum number of required block confirmations, + // b) the token balance client has enough tokens to fill it, + // c) the fill is profitable. + // If all hold true then complete the fill. Otherwise, if slow fills are enabled, request a slow fill. + async evaluateFill( + deposit: V3DepositWithBlock, + maxBlockNumber: number, + sendSlowRelays: boolean + ): Promise { + const { depositId, depositor, recipient, destinationChainId, originChainId, inputToken, outputAmount } = deposit; + const { hubPoolClient, profitClient, tokenClient } = this.clients; + const { slowDepositors } = this.config; // If the deposit does not meet the minimum number of block confirmations, skip it. - if (deposit.blockNumber > spokePoolClients[originChainId].latestBlockSearched - mdcPerChain[originChainId]) { - continue; + if (deposit.blockNumber > maxBlockNumber) { + const chain = getNetworkName(originChainId); + this.logger.debug({ + at: "Relayer", + message: `Skipping ${chain} deposit ${depositId} due to insufficient deposit confirmations.`, + depositId, + transactionHash: deposit.transactionHash, + }); + return; } // If depositor is on the slow deposit list, then send a zero fill to initiate a slow relay and return early. @@ -289,7 +285,7 @@ export class Relayer { } // Regardless of whether we should send a slow fill or not for this depositor, exit early at this point // so we don't fast fill an already slow filled deposit from the slow fill-only list. - continue; + return; } const l1Token = hubPoolClient.getL1TokenInfoForL2Token(inputToken, originChainId); @@ -326,7 +322,34 @@ export class Relayer { this.requestSlowFill(deposit); } } + } + + async checkForUnfilledDepositsAndFill(sendSlowRelays = true): Promise { + // Fetch all unfilled deposits, order by total earnable fee. + const { profitClient, spokePoolClients, tokenClient, multiCallerClient } = this.clients; + + // Flush any pre-existing enqueued transactions that might not have been executed. + multiCallerClient.clearTransactionQueue(); + + // Fetch unfilled deposits and filter out deposits upfront before we compute the minimum deposit confirmation + // per chain, which is based on the deposit volume we could fill. + const unfilledDeposits = await this._getUnfilledDeposits(); + const allUnfilledDeposits = Object.values(unfilledDeposits.map(({ deposit }) => deposit)); + this.logger.debug({ + at: "Relayer#checkForUnfilledDepositsAndFill", + message: `${allUnfilledDeposits.length} unfilled deposits found.`, + }); + if (allUnfilledDeposits.length === 0) { + return; + } + + const mdcPerChain = this.computeRequiredDepositConfirmations(allUnfilledDeposits); + for (const deposit of allUnfilledDeposits) { + const { originChainId } = deposit; + const maxBlockNumber = spokePoolClients[originChainId].latestBlockSearched - mdcPerChain[originChainId]; + await this.evaluateFill(deposit, maxBlockNumber, sendSlowRelays); } + // If during the execution run we had shortfalls or unprofitable fills then handel it by producing associated logs. if (tokenClient.anyCapturedShortFallFills()) { this.handleTokenShortfall(); diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index b99ee3e8c..742e9d25e 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -372,7 +372,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { await updateAllClients(); await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; + expect(lastSpyLogIncludes(spy, "due to insufficient deposit confirmations")).to.be.true; }); it("Ignores deposits with quote times in future", async function () { From 489bc6f70021d10dc5de1807e7ff8dbc1fcb6259 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 20 Mar 2024 23:21:50 +0100 Subject: [PATCH 04/16] simplify --- src/relayer/Relayer.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 753b5e829..1657c084e 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -258,10 +258,9 @@ export class Relayer { // Fetch unfilled deposits and filter out deposits upfront before we compute the minimum deposit confirmation // per chain, which is based on the deposit volume we could fill. const unfilledDeposits = await this._getUnfilledDeposits(); + const allUnfilledDeposits = Object.values(unfilledDeposits.map(({ deposit }) => deposit)); - const mdcPerChain = this.computeRequiredDepositConfirmations( - Object.values(unfilledDeposits.map(({ deposit }) => deposit)) - ); + const mdcPerChain = this.computeRequiredDepositConfirmations(allUnfilledDeposits); // Iterate over all unfilled deposits. For each unfilled deposit, check that: // a) it exceeds the minimum number of required block confirmations, @@ -269,7 +268,7 @@ export class Relayer { // c) the fill is profitable. // If all hold true then complete the fill. Otherwise, if slow fills are enabled, request a slow fill. const { slowDepositors } = config; - for (const deposit of unfilledDeposits.map(({ deposit }) => deposit)) { + for (const deposit of allUnfilledDeposits) { const { depositor, recipient, destinationChainId, originChainId, inputToken, outputAmount } = deposit; // If the deposit does not meet the minimum number of block confirmations, skip it. From 78eb55b53c5b70eb8a365ac013bf169ecb16a004 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 20 Mar 2024 23:28:05 +0100 Subject: [PATCH 05/16] lint --- src/relayer/Relayer.ts | 120 ++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index f88d2712a..7fdf1b079 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -252,76 +252,72 @@ export class Relayer { // b) the token balance client has enough tokens to fill it, // c) the fill is profitable. // If all hold true then complete the fill. Otherwise, if slow fills are enabled, request a slow fill. - async evaluateFill( - deposit: V3DepositWithBlock, - maxBlockNumber: number, - sendSlowRelays: boolean - ): Promise { - const { depositId, depositor, recipient, destinationChainId, originChainId, inputToken, outputAmount } = deposit; - const { hubPoolClient, profitClient, tokenClient } = this.clients; - const { slowDepositors } = this.config; - - // If the deposit does not meet the minimum number of block confirmations, skip it. - if (deposit.blockNumber > maxBlockNumber) { - const chain = getNetworkName(originChainId); + async evaluateFill(deposit: V3DepositWithBlock, maxBlockNumber: number, sendSlowRelays: boolean): Promise { + const { depositId, depositor, recipient, destinationChainId, originChainId, inputToken, outputAmount } = deposit; + const { hubPoolClient, profitClient, tokenClient } = this.clients; + const { slowDepositors } = this.config; + + // If the deposit does not meet the minimum number of block confirmations, skip it. + if (deposit.blockNumber > maxBlockNumber) { + const chain = getNetworkName(originChainId); + this.logger.debug({ + at: "Relayer", + message: `Skipping ${chain} deposit ${depositId} due to insufficient deposit confirmations.`, + depositId, + transactionHash: deposit.transactionHash, + }); + return; + } + + // If depositor is on the slow deposit list, then send a zero fill to initiate a slow relay and return early. + if (slowDepositors?.includes(depositor)) { + if (sendSlowRelays) { this.logger.debug({ at: "Relayer", - message: `Skipping ${chain} deposit ${depositId} due to insufficient deposit confirmations.`, - depositId, - transactionHash: deposit.transactionHash, + message: "Initiating slow fill for grey listed depositor", + depositor, }); - return; + this.requestSlowFill(deposit); } + // Regardless of whether we should send a slow fill or not for this depositor, exit early at this point + // so we don't fast fill an already slow filled deposit from the slow fill-only list. + return; + } - // If depositor is on the slow deposit list, then send a zero fill to initiate a slow relay and return early. - if (slowDepositors?.includes(depositor)) { - if (sendSlowRelays) { - this.logger.debug({ - at: "Relayer", - message: "Initiating slow fill for grey listed depositor", - depositor, - }); - this.requestSlowFill(deposit); - } - // Regardless of whether we should send a slow fill or not for this depositor, exit early at this point - // so we don't fast fill an already slow filled deposit from the slow fill-only list. - return; + const l1Token = hubPoolClient.getL1TokenInfoForL2Token(inputToken, originChainId); + const selfRelay = [depositor, recipient].every((address) => address === this.relayerAddress); + if (tokenClient.hasBalanceForFill(deposit, outputAmount) && !selfRelay) { + const { + repaymentChainId, + realizedLpFeePct, + relayerFeePct, + gasLimit: _gasLimit, + gasCost, + } = await this.resolveRepaymentChain(deposit, l1Token); + if (isDefined(repaymentChainId)) { + const gasLimit = isMessageEmpty(resolveDepositMessage(deposit)) ? undefined : _gasLimit; + this.fillRelay(deposit, repaymentChainId, realizedLpFeePct, gasLimit); + } else { + profitClient.captureUnprofitableFill(deposit, realizedLpFeePct, relayerFeePct, gasCost); } + } else if (selfRelay) { + const { realizedLpFeePct } = await hubPoolClient.computeRealizedLpFeePct({ + ...deposit, + paymentChainId: destinationChainId, + }); - const l1Token = hubPoolClient.getL1TokenInfoForL2Token(inputToken, originChainId); - const selfRelay = [depositor, recipient].every((address) => address === this.relayerAddress); - if (tokenClient.hasBalanceForFill(deposit, outputAmount) && !selfRelay) { - const { - repaymentChainId, - realizedLpFeePct, - relayerFeePct, - gasLimit: _gasLimit, - gasCost, - } = await this.resolveRepaymentChain(deposit, l1Token); - if (isDefined(repaymentChainId)) { - const gasLimit = isMessageEmpty(resolveDepositMessage(deposit)) ? undefined : _gasLimit; - this.fillRelay(deposit, repaymentChainId, realizedLpFeePct, gasLimit); - } else { - profitClient.captureUnprofitableFill(deposit, realizedLpFeePct, relayerFeePct, gasCost); - } - } else if (selfRelay) { - const { realizedLpFeePct } = await hubPoolClient.computeRealizedLpFeePct({ - ...deposit, - paymentChainId: destinationChainId, - }); - - // A relayer can fill its own deposit without an ERC20 transfer. Only bypass profitability requirements if the - // relayer is both the depositor and the recipient, because a deposit on a cheap SpokePool chain could cause - // expensive fills on (for example) mainnet. - this.fillRelay(deposit, destinationChainId, realizedLpFeePct); - } else { - // TokenClient.getBalance returns that we don't have enough balance to submit the fast fill. - // At this point, capture the shortfall so that the inventory manager can rebalance the token inventory. - tokenClient.captureTokenShortfallForFill(deposit, outputAmount); - if (sendSlowRelays) { - this.requestSlowFill(deposit); - } + // A relayer can fill its own deposit without an ERC20 transfer. Only bypass profitability requirements if the + // relayer is both the depositor and the recipient, because a deposit on a cheap SpokePool chain could cause + // expensive fills on (for example) mainnet. + this.fillRelay(deposit, destinationChainId, realizedLpFeePct); + } else { + // TokenClient.getBalance returns that we don't have enough balance to submit the fast fill. + // At this point, capture the shortfall so that the inventory manager can rebalance the token inventory. + tokenClient.captureTokenShortfallForFill(deposit, outputAmount); + if (sendSlowRelays) { + this.requestSlowFill(deposit); } + } } async checkForUnfilledDepositsAndFill(sendSlowRelays = true): Promise { From 4e618bb67273a8634bed184df7c94c61724e7621 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 20 Mar 2024 23:43:38 +0100 Subject: [PATCH 06/16] Exit early --- src/relayer/Relayer.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 1657c084e..ec1ce53fc 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -259,6 +259,13 @@ export class Relayer { // per chain, which is based on the deposit volume we could fill. const unfilledDeposits = await this._getUnfilledDeposits(); const allUnfilledDeposits = Object.values(unfilledDeposits.map(({ deposit }) => deposit)); + this.logger.debug({ + at: "Relayer#checkForUnfilledDepositsAndFill", + message: `${allUnfilledDeposits.length} unfilled deposits found.`, + }); + if (allUnfilledDeposits.length === 0) { + return; + } const mdcPerChain = this.computeRequiredDepositConfirmations(allUnfilledDeposits); From 9e138030cff3c117866010b994269e8fe1799e4a Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 20 Mar 2024 23:46:56 +0100 Subject: [PATCH 07/16] fixes --- src/relayer/Relayer.ts | 9 ++++++++- test/Relayer.BasicFill.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index ec1ce53fc..72bc83257 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -276,10 +276,17 @@ export class Relayer { // If all hold true then complete the fill. Otherwise, if slow fills are enabled, request a slow fill. const { slowDepositors } = config; for (const deposit of allUnfilledDeposits) { - const { depositor, recipient, destinationChainId, originChainId, inputToken, outputAmount } = deposit; + const { depositId, depositor, recipient, destinationChainId, originChainId, inputToken, outputAmount } = deposit; // If the deposit does not meet the minimum number of block confirmations, skip it. if (deposit.blockNumber > spokePoolClients[originChainId].latestBlockSearched - mdcPerChain[originChainId]) { + const chain = getNetworkName(originChainId); + this.logger.debug({ + at: "Relayer", + message: `Skipping ${chain} deposit ${depositId} due to insufficient deposit confirmations.`, + depositId, + transactionHash: deposit.transactionHash, + }); continue; } diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index b99ee3e8c..742e9d25e 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -372,7 +372,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { await updateAllClients(); await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; + expect(lastSpyLogIncludes(spy, "due to insufficient deposit confirmations")).to.be.true; }); it("Ignores deposits with quote times in future", async function () { From 0b3ccf8ccae254049f2d203706b162c6b2347c6b Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:34:29 +0100 Subject: [PATCH 08/16] Restore logging --- src/relayer/Relayer.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index e23fe5c0c..1ace7cc28 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -265,6 +265,8 @@ export class Relayer { at: "Relayer", message: `Skipping ${chain} deposit ${depositId} due to insufficient deposit confirmations.`, depositId, + blockNumber: deposit.blockNumber, + maxBlockNumber, transactionHash: deposit.transactionHash, }); return; From 3eb1877f1b8e1389cc80c10b138197124c94f697 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:39:13 +0100 Subject: [PATCH 09/16] improve(relayer): Batch query deposit fill status The relayer currently implements late-stage checking of the fill status on a per-deposit basis. This is fairly slow and inefficient, but does successfully protect against an avalanche of failed fill simulations that result from overlapping bot runs. This change implements batched querying of all deposits for each destination chain. This is significantly more efficient in terms of RPC consumption, and should allow for subsequent "already filled" filtering to occur much faster. It's also a key enabler for some subsequent efficiency improvements. --- src/relayer/Relayer.ts | 13 +-------- src/utils/FillUtils.ts | 61 ++++++++++++++++++++++----------------- test/Relayer.BasicFill.ts | 31 +++++++++++++++++++- 3 files changed, 66 insertions(+), 39 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 1ace7cc28..93a553478 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -1,6 +1,6 @@ import { utils as sdkUtils } from "@across-protocol/sdk-v2"; import { utils as ethersUtils } from "ethers"; -import { FillStatus, L1Token, V3Deposit, V3DepositWithBlock } from "../interfaces"; +import { L1Token, V3Deposit, V3DepositWithBlock } from "../interfaces"; import { BigNumber, bnZero, @@ -128,17 +128,6 @@ export class Relayer { return false; } - const destSpokePool = this.clients.spokePoolClients[destinationChainId].spokePool; - const fillStatus = await sdkUtils.relayFillStatus(destSpokePool, deposit, "latest", destinationChainId); - if (fillStatus === FillStatus.Filled) { - this.logger.debug({ - at: "Relayer::getUnfilledDeposits", - message: "Skipping deposit that was already filled.", - deposit, - }); - return false; - } - // Skip deposit with message if sending fills with messages is not supported. if (!this.config.sendingMessageRelaysEnabled && !isMessageEmpty(resolveDepositMessage(deposit))) { this.logger.warn({ diff --git a/src/utils/FillUtils.ts b/src/utils/FillUtils.ts index 48079bea8..4e8f2ac41 100644 --- a/src/utils/FillUtils.ts +++ b/src/utils/FillUtils.ts @@ -4,6 +4,7 @@ import { HubPoolClient, SpokePoolClient } from "../clients"; import { Fill, FillsToRefund, + FillStatus, FillWithBlock, SpokePoolClientsByChain, V2DepositWithBlock, @@ -258,11 +259,12 @@ export function getFillsInRange( // @todo Better alignment with the upstream UnfilledDeposit type. export type RelayerUnfilledDeposit = { deposit: V3DepositWithBlock; + fillStatus: number; version: number; invalidFills: Fill[]; }; -// @description Returns an array of unfilled deposits over all spokePoolClients. +// @description Returns all unfilled deposits, indexed by destination chain. // @param spokePoolClients Mapping of chainIds to SpokePoolClient objects. // @param configStoreClient ConfigStoreClient instance. // @param depositLookBack Deposit lookback (in seconds) since SpokePoolClient time as at last update. @@ -291,34 +293,41 @@ export async function getUnfilledDeposits( } // Iterate over each chainId and check for unfilled deposits. - chainIds.forEach((destinationChainId) => { - const destinationClient = spokePoolClients[destinationChainId]; + await sdkUtils.mapAsync( + chainIds, + async (destinationChainId) => { + const destinationClient = spokePoolClients[destinationChainId]; - unfilledDeposits[destinationChainId] = chainIds - .filter((chainId) => chainId !== destinationChainId) - .map((originChainId) => { - const originClient = spokePoolClients[originChainId]; - const earliestBlockNumber = originFromBlocks[originChainId]; + // For each destination chain, query each _other_ SpokePool for deposits within the lookback. + const deposits = chainIds + .filter((chainId) => chainId !== destinationChainId) + .map((originChainId) => { + const originClient = spokePoolClients[originChainId]; + const earliestBlockNumber = originFromBlocks[originChainId]; + const { deploymentBlock, latestBlockSearched } = originClient; - // Basic sanity check... - assert( - earliestBlockNumber >= originClient.deploymentBlock && earliestBlockNumber <= originClient.latestBlockSearched - ); + // Basic sanity check... + assert(earliestBlockNumber >= deploymentBlock && earliestBlockNumber <= latestBlockSearched); - // Find all unfilled deposits for the current loops originChain -> destinationChain. Note that this also - // validates that the deposit is filled "correctly" for the given deposit information. This includes validation - // of the all deposit -> relay props, the realizedLpFeePct and the origin->destination token mapping. - return originClient - .getDepositsForDestinationChain(destinationChainId) - .filter((deposit) => deposit.blockNumber >= earliestBlockNumber) - .filter(sdkUtils.isV3Deposit) // @todo: Remove after v2 deprecated. - .map((deposit) => { - const version = hubPoolClient.configStoreClient.getConfigStoreVersionForTimestamp(deposit.quoteTimestamp); - return { ...destinationClient.getValidUnfilledAmountForDeposit(deposit), deposit, version }; - }) - .filter(({ unfilledAmount }) => unfilledAmount.gt(bnZero)); - }) - .flat(); + // Find all unfilled deposits for the current loops originChain -> destinationChain. + return originClient + .getDepositsForDestinationChain(destinationChainId) + .filter((deposit) => deposit.blockNumber >= earliestBlockNumber) + .filter(sdkUtils.isV3Deposit); // @todo: Remove after v2 deprecated. + }) + .flat(); + + // Resolve the latest fill status for each deposit and filter out any that are now filled. + const { spokePool } = destinationClient; + const fillStatus = await sdkUtils.fillStatusArray(spokePool, deposits); + unfilledDeposits[destinationChainId] = deposits + .map((deposit, idx) => ({ deposit, fillStatus: fillStatus[idx] })) + .filter((_, idx) => fillStatus[idx] !== FillStatus.Filled) + .map(({ deposit, fillStatus }) => { + const version = hubPoolClient.configStoreClient.getConfigStoreVersionForTimestamp(deposit.quoteTimestamp); + const { invalidFills } = destinationClient.getValidUnfilledAmountForDeposit(deposit); + return { deposit, version, fillStatus, invalidFills }; + }); }); return unfilledDeposits; diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index 742e9d25e..0238f15cd 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -2,7 +2,7 @@ import { clients, constants, utils as sdkUtils } from "@across-protocol/sdk-v2"; import { AcrossApiClient, ConfigStoreClient, MultiCallerClient, TokenClient } from "../src/clients"; import { V2FillWithBlock, V3FillWithBlock } from "../src/interfaces"; import { CONFIG_STORE_VERSION } from "../src/common"; -import { bnOne } from "../src/utils"; +import { bnOne, getUnfilledDeposits } from "../src/utils"; import { Relayer } from "../src/relayer/Relayer"; import { RelayerConfig } from "../src/relayer/RelayerConfig"; // Tested import { @@ -28,6 +28,7 @@ import { enableRoutesOnHubPool, ethers, expect, + fillV3Relay, getLastBlockTime, getV3RelayHash, lastSpyLogIncludes, @@ -235,6 +236,34 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { expect(multiCallerClient.transactionCount()).to.equal(0); // no new transactions were enqueued. }); + it("Queries the latest onchain fill status for all deposits", async function () { + const deposit = await depositV3( + spokePool_1, + destinationChainId, + depositor, + inputToken, + inputAmount, + outputToken, + outputAmount + ); + await updateAllClients(); + let unfilledDeposits = await getUnfilledDeposits(spokePoolClients, hubPoolClient); + expect(Object.values(unfilledDeposits).flat().length).to.equal(1); + + await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(lastSpyLogIncludes(spy, "Filling v3 deposit")).to.be.true; + expect(multiCallerClient.transactionCount()).to.equal(1); // One transaction, filling the one deposit. + + await fillV3Relay(spokePool_2, deposit, relayer); + unfilledDeposits = await getUnfilledDeposits(spokePoolClients, hubPoolClient); + expect(Object.values(unfilledDeposits).flat().length).to.equal(0); + + // Verify that the relayer now sees that the deposit has been filled. + await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; + expect(multiCallerClient.transactionCount()).to.equal(0); + }); + it("Respects configured relayer routes", async function () { relayerInstance = new Relayer( relayer.address, From bf892e2d1f26c608dd6ae59d297fca209d914cd2 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 21 Mar 2024 22:39:34 +0100 Subject: [PATCH 10/16] Improve test --- test/Relayer.BasicFill.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index 0238f15cd..32f9e8f65 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -254,6 +254,11 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { expect(lastSpyLogIncludes(spy, "Filling v3 deposit")).to.be.true; expect(multiCallerClient.transactionCount()).to.equal(1); // One transaction, filling the one deposit. + // Verify that the deposit is still unfilled (relayer didn't execute it). + unfilledDeposits = await getUnfilledDeposits(spokePoolClients, hubPoolClient); + expect(Object.values(unfilledDeposits).flat().length).to.equal(1); + + // Fill the deposit and immediately check for unfilled deposits (without SpokePoolClient update). await fillV3Relay(spokePool_2, deposit, relayer); unfilledDeposits = await getUnfilledDeposits(spokePoolClients, hubPoolClient); expect(Object.values(unfilledDeposits).flat().length).to.equal(0); From 05c7bdd070525727baaa4f3c48e8ec6acb888c53 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 21 Mar 2024 22:55:21 +0100 Subject: [PATCH 11/16] lint --- src/utils/FillUtils.ts | 60 ++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/utils/FillUtils.ts b/src/utils/FillUtils.ts index 4e8f2ac41..4d944ed8a 100644 --- a/src/utils/FillUtils.ts +++ b/src/utils/FillUtils.ts @@ -293,41 +293,39 @@ export async function getUnfilledDeposits( } // Iterate over each chainId and check for unfilled deposits. - await sdkUtils.mapAsync( - chainIds, - async (destinationChainId) => { - const destinationClient = spokePoolClients[destinationChainId]; + await sdkUtils.mapAsync(chainIds, async (destinationChainId) => { + const destinationClient = spokePoolClients[destinationChainId]; - // For each destination chain, query each _other_ SpokePool for deposits within the lookback. - const deposits = chainIds - .filter((chainId) => chainId !== destinationChainId) - .map((originChainId) => { - const originClient = spokePoolClients[originChainId]; - const earliestBlockNumber = originFromBlocks[originChainId]; - const { deploymentBlock, latestBlockSearched } = originClient; + // For each destination chain, query each _other_ SpokePool for deposits within the lookback. + const deposits = chainIds + .filter((chainId) => chainId !== destinationChainId) + .map((originChainId) => { + const originClient = spokePoolClients[originChainId]; + const earliestBlockNumber = originFromBlocks[originChainId]; + const { deploymentBlock, latestBlockSearched } = originClient; - // Basic sanity check... - assert(earliestBlockNumber >= deploymentBlock && earliestBlockNumber <= latestBlockSearched); + // Basic sanity check... + assert(earliestBlockNumber >= deploymentBlock && earliestBlockNumber <= latestBlockSearched); - // Find all unfilled deposits for the current loops originChain -> destinationChain. - return originClient - .getDepositsForDestinationChain(destinationChainId) - .filter((deposit) => deposit.blockNumber >= earliestBlockNumber) - .filter(sdkUtils.isV3Deposit); // @todo: Remove after v2 deprecated. - }) - .flat(); + // Find all unfilled deposits for the current loops originChain -> destinationChain. + return originClient + .getDepositsForDestinationChain(destinationChainId) + .filter((deposit) => deposit.blockNumber >= earliestBlockNumber) + .filter(sdkUtils.isV3Deposit); // @todo: Remove after v2 deprecated. + }) + .flat(); - // Resolve the latest fill status for each deposit and filter out any that are now filled. - const { spokePool } = destinationClient; - const fillStatus = await sdkUtils.fillStatusArray(spokePool, deposits); - unfilledDeposits[destinationChainId] = deposits - .map((deposit, idx) => ({ deposit, fillStatus: fillStatus[idx] })) - .filter((_, idx) => fillStatus[idx] !== FillStatus.Filled) - .map(({ deposit, fillStatus }) => { - const version = hubPoolClient.configStoreClient.getConfigStoreVersionForTimestamp(deposit.quoteTimestamp); - const { invalidFills } = destinationClient.getValidUnfilledAmountForDeposit(deposit); - return { deposit, version, fillStatus, invalidFills }; - }); + // Resolve the latest fill status for each deposit and filter out any that are now filled. + const { spokePool } = destinationClient; + const fillStatus = await sdkUtils.fillStatusArray(spokePool, deposits); + unfilledDeposits[destinationChainId] = deposits + .map((deposit, idx) => ({ deposit, fillStatus: fillStatus[idx] })) + .filter((_, idx) => fillStatus[idx] !== FillStatus.Filled) + .map(({ deposit, fillStatus }) => { + const version = hubPoolClient.configStoreClient.getConfigStoreVersionForTimestamp(deposit.quoteTimestamp); + const { invalidFills } = destinationClient.getValidUnfilledAmountForDeposit(deposit); + return { deposit, version, fillStatus, invalidFills }; + }); }); return unfilledDeposits; From 8c7b76dfa2d4acd757bc6567836b1500b1b520c6 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 21 Mar 2024 22:57:12 +0100 Subject: [PATCH 12/16] Tweak filter --- src/utils/FillUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/FillUtils.ts b/src/utils/FillUtils.ts index 4d944ed8a..32d250893 100644 --- a/src/utils/FillUtils.ts +++ b/src/utils/FillUtils.ts @@ -320,7 +320,7 @@ export async function getUnfilledDeposits( const fillStatus = await sdkUtils.fillStatusArray(spokePool, deposits); unfilledDeposits[destinationChainId] = deposits .map((deposit, idx) => ({ deposit, fillStatus: fillStatus[idx] })) - .filter((_, idx) => fillStatus[idx] !== FillStatus.Filled) + .filter(({ fillStatus }) => fillStatus !== FillStatus.Filled) .map(({ deposit, fillStatus }) => { const version = hubPoolClient.configStoreClient.getConfigStoreVersionForTimestamp(deposit.quoteTimestamp); const { invalidFills } = destinationClient.getValidUnfilledAmountForDeposit(deposit); From da509bbe957aa880a535f6bf52b8b2ab82dbe288 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:00:45 +0100 Subject: [PATCH 13/16] Restore comment --- src/relayer/Relayer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 1ace7cc28..17c4900ce 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -252,7 +252,8 @@ export class Relayer { // a) it exceeds the minimum number of required block confirmations, // b) the token balance client has enough tokens to fill it, // c) the fill is profitable. - // If all hold true then complete the fill. Otherwise, if slow fills are enabled, request a slow fill. + // If all hold true then complete the fill. If there is insufficient balance to complete the fill and slow fills are + // enabled then request a slow fill instead. async evaluateFill(deposit: V3DepositWithBlock, maxBlockNumber: number, sendSlowRelays: boolean): Promise { const { depositId, depositor, recipient, destinationChainId, originChainId, inputToken, outputAmount } = deposit; const { hubPoolClient, profitClient, tokenClient } = this.clients; From b72b5508f6800190348481d7400107f8f0d68ed6 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:41:20 +0100 Subject: [PATCH 14/16] Fix test --- test/Relayer.UnfilledDeposits.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/Relayer.UnfilledDeposits.ts b/test/Relayer.UnfilledDeposits.ts index 6502d9415..88bc585f0 100644 --- a/test/Relayer.UnfilledDeposits.ts +++ b/test/Relayer.UnfilledDeposits.ts @@ -1,6 +1,7 @@ import * as contracts from "@across-protocol/contracts-v2/dist/test-utils"; import { clients, utils as sdkUtils } from "@across-protocol/sdk-v2"; import { AcrossApiClient, ConfigStoreClient, MultiCallerClient, TokenClient } from "../src/clients"; +import { FillStatus } from "../src/interfaces"; import { CHAIN_ID_TEST_LIST, amountToLp, @@ -183,9 +184,8 @@ describe("Relayer: Unfilled Deposits", async function () { [...deposits] .sort((a, b) => (a.destinationChainId > b.destinationChainId ? 1 : -1)) .map((deposit) => ({ - unfilledAmount: deposit.outputAmount, deposit, - fillCount: 0, + fillStatus: FillStatus.Unfilled, invalidFills: [], version: configStoreClient.configStoreVersion, })) @@ -216,9 +216,8 @@ describe("Relayer: Unfilled Deposits", async function () { .excludingEvery(["realizedLpFeePct", "quoteBlockNumber"]) .to.deep.equal([ { - unfilledAmount: deposit.outputAmount, - deposit: deposit, - fillCount: 0, + deposit, + fillStatus: FillStatus.Unfilled, invalidFills: [invalidFill], version: configStoreClient.configStoreVersion, }, @@ -372,9 +371,8 @@ describe("Relayer: Unfilled Deposits", async function () { .excludingEvery(["realizedLpFeePct", "quoteBlockNumber"]) .to.deep.equal([ { - unfilledAmount: deposit.outputAmount, - deposit: deposit, - fillCount: 0, + deposit, + fillStatus: FillStatus.Unfilled, invalidFills: [invalidFill], version: configStoreClient.configStoreVersion, }, From 9d9225f719ddc39d6a11fb51c2bc3c45530bc964 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:56:58 +0100 Subject: [PATCH 15/16] Remove redundant Object.values() --- 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 17c4900ce..11bfe3b34 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -334,7 +334,7 @@ export class Relayer { // Fetch unfilled deposits and filter out deposits upfront before we compute the minimum deposit confirmation // per chain, which is based on the deposit volume we could fill. const unfilledDeposits = await this._getUnfilledDeposits(); - const allUnfilledDeposits = Object.values(unfilledDeposits.map(({ deposit }) => deposit)); + const allUnfilledDeposits = unfilledDeposits.map(({ deposit }) => deposit); this.logger.debug({ at: "Relayer#checkForUnfilledDepositsAndFill", message: `${allUnfilledDeposits.length} unfilled deposits found.`, From 2a79c3a6fa2317c16e1251ab5850c4bb922516c6 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 22 Mar 2024 14:30:32 +0100 Subject: [PATCH 16/16] Bump sdk & contracts --- package.json | 4 ++-- yarn.lock | 34 ++++++++++++---------------------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index af7b63b13..a793fbcdf 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ }, "dependencies": { "@across-protocol/constants-v2": "1.0.14", - "@across-protocol/contracts-v2": "2.5.3", - "@across-protocol/sdk-v2": "0.22.16", + "@across-protocol/contracts-v2": "2.5.4", + "@across-protocol/sdk-v2": "0.22.19", "@arbitrum/sdk": "^3.1.3", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 19dd265b5..db96e1b87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,27 +11,17 @@ "@uma/common" "^2.17.0" hardhat "^2.9.3" -"@across-protocol/constants-v2@1.0.14": +"@across-protocol/constants-v2@1.0.14", "@across-protocol/constants-v2@^1.0.14": version "1.0.14" resolved "https://registry.yarnpkg.com/@across-protocol/constants-v2/-/constants-v2-1.0.14.tgz#2eb6624c306db3f184293d8abb023d2354abadce" integrity sha512-7C8hyH/7aDh8AOd5DLQrtcey6Ip4Q6o8FmSiDrBnxcD/aVeH+L8cZhh/hRMLm8CA5Olx4usEarfEfZ2rN0AZfg== -"@across-protocol/constants-v2@^1.0.11": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@across-protocol/constants-v2/-/constants-v2-1.0.11.tgz#58d34b5cb50351d097f2ca43c5a30b5908faed7c" - integrity sha512-RpseYB2QxGyfyrfXtUeFxUSCUW1zqu442QFzsdD1LBZtymuzdHuL2MwtTdmRRnJSvzRTFTtlRh4bYDoExSb5zQ== - -"@across-protocol/constants-v2@^1.0.12": - version "1.0.12" - resolved "https://registry.yarnpkg.com/@across-protocol/constants-v2/-/constants-v2-1.0.12.tgz#a85e8d39efa9c5294a368e229eab65357f00645e" - integrity sha512-UrrPOxV/+FjCHmlMnezviAMXDiGDzUNCwKC2ifQ0U8PGa+lNulb7KCGNIEIFAFLHNn+/CMFST5Vwf+aAqbumvQ== - -"@across-protocol/contracts-v2@2.5.3": - version "2.5.3" - resolved "https://registry.yarnpkg.com/@across-protocol/contracts-v2/-/contracts-v2-2.5.3.tgz#6e4d656e155a5b6ce0202dad5f3bc132e867f484" - integrity sha512-eIOX7K7NFS0KhVCZDU+ka+UPWq4UNf6Nht5YoutgrtMYC4tiubBAJN69auE+zClVniixKf0uz5klkDs988XhgA== +"@across-protocol/contracts-v2@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@across-protocol/contracts-v2/-/contracts-v2-2.5.4.tgz#7e1b6ff26d159abdad3a0ac51991f09df0f33f74" + integrity sha512-LdHN2XQIrzj3CyzVlYY69ppeLxohwjwzsAaRtZ7nbB/HsVwUuJJoCjoUAV+ePGjqKhxN2lojFFlni7uMnSUeRw== dependencies: - "@across-protocol/constants-v2" "^1.0.11" + "@across-protocol/constants-v2" "^1.0.14" "@defi-wonderland/smock" "^2.3.4" "@eth-optimism/contracts" "^0.5.40" "@ethersproject/abstract-provider" "5.7.0" @@ -55,14 +45,14 @@ "@openzeppelin/contracts" "4.1.0" "@uma/core" "^2.18.0" -"@across-protocol/sdk-v2@0.22.16": - version "0.22.16" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk-v2/-/sdk-v2-0.22.16.tgz#5e898dfcd98f7805e8deea350aef1fcc591ac458" - integrity sha512-HgtcoF1m7SFETy5gThCGm1tPJdYpgCTYMDNtIl2c2CiN1LsJ9i29+VeEzLA13/rcEwDMZIQpDahOODc02e1yaQ== +"@across-protocol/sdk-v2@0.22.19": + version "0.22.19" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk-v2/-/sdk-v2-0.22.19.tgz#65531294f3ccaacf3b2323f400e530a1e2ab6210" + integrity sha512-7b6eT9nKKnrBZVxzB+h3QsSNonsQbCfnbN3vSME/ZPjCmJGgWJj1O8LVwLn/6xNpOoLg13TD6aOkZQAjG+Oeyg== dependencies: "@across-protocol/across-token" "^1.0.0" - "@across-protocol/constants-v2" "^1.0.12" - "@across-protocol/contracts-v2" "2.5.3" + "@across-protocol/constants-v2" "^1.0.14" + "@across-protocol/contracts-v2" "2.5.4" "@eth-optimism/sdk" "^3.2.2" "@pinata/sdk" "^2.1.0" "@types/mocha" "^10.0.1"