From f3848d9c0140804c0470cf75cd12b10c52402f95 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:27:32 +0100 Subject: [PATCH 1/6] improve(relayer): Execute destination transactions async This change updates the relayer to submit transactions as soon as they are ready for each destination chain, rather than waiting until all destination transaction queues are ready. This should reduce the overall execution time for the relayer when it has transactions to submit on multiple chains, and should improve relayer performance on chains with a high chainId. --- src/clients/MultiCallerClient.ts | 9 ++++-- src/relayer/Relayer.ts | 10 ++++-- src/relayer/index.ts | 4 +-- test/Relayer.BasicFill.ts | 54 ++++++++++++++------------------ test/Relayer.SlowFill.ts | 7 +---- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/clients/MultiCallerClient.ts b/src/clients/MultiCallerClient.ts index f62628f70..83d2f2c19 100644 --- a/src/clients/MultiCallerClient.ts +++ b/src/clients/MultiCallerClient.ts @@ -1,3 +1,4 @@ +import { utils as sdkUtils } from "@across-protocol/sdk-v2"; import { BigNumber } from "ethers"; import { DEFAULT_MULTICALL_CHUNK_SIZE, DEFAULT_CHAIN_MULTICALL_CHUNK_SIZE, Multicall2Call } from "../common"; import { @@ -120,8 +121,12 @@ export class MultiCallerClient { } // For each chain, collate the enqueued transactions and process them in parallel. - async executeTxnQueues(simulate = false): Promise> { - const chainIds = [...new Set(Object.keys(this.valueTxns).concat(Object.keys(this.txns)))]; + async executeTxnQueues(simulate = false, chainIds: number[] = []): Promise> { + if (chainIds.length === 0) { + chainIds = sdkUtils.dedupArray( + Object.keys(this.valueTxns).map(Number).concat(Object.keys(this.txns).map(Number)) + ); + }; // One promise per chain for parallel execution. const resultsByChain = await Promise.allSettled( diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 596b4b05c..db77cd6a0 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -340,7 +340,7 @@ export class Relayer { } } - async checkForUnfilledDepositsAndFill(sendSlowRelays = true): Promise { + async checkForUnfilledDepositsAndFill(sendSlowRelays = true, simulate = false): Promise { // Fetch all unfilled deposits, order by total earnable fee. const { profitClient, spokePoolClients, tokenClient, multiCallerClient } = this.clients; @@ -369,15 +369,21 @@ export class Relayer { ]) ); - await sdkUtils.forEachAsync(Object.values(unfilledDeposits), async (unfilledDeposits) => { + await sdkUtils.forEachAsync(Object.entries(unfilledDeposits), async ([chainId, unfilledDeposits]) => { if (unfilledDeposits.length === 0) { return; } + await this.evaluateFills( unfilledDeposits.map(({ deposit }) => deposit), maxBlockNumbers, sendSlowRelays ); + + const destinationChainId = Number(chainId); + if (multiCallerClient.getQueuedTransactions(destinationChainId).length > 0) { + await multiCallerClient.executeTxnQueues(simulate, [destinationChainId]); + } }); // If during the execution run we had shortfalls or unprofitable fills then handel it by producing associated logs. diff --git a/src/relayer/index.ts b/src/relayer/index.ts index 402085cc2..72d130e2f 100644 --- a/src/relayer/index.ts +++ b/src/relayer/index.ts @@ -34,8 +34,8 @@ export async function runRelayer(_logger: winston.Logger, baseSigner: Signer): P await updateRelayerClients(relayerClients, config); if (!config.skipRelays) { - await relayer.checkForUnfilledDepositsAndFill(config.sendingSlowRelaysEnabled); - await relayerClients.multiCallerClient.executeTransactionQueue(!config.sendingRelaysEnabled); + const simulate = !config.sendingRelaysEnabled; + await relayer.checkForUnfilledDepositsAndFill(config.sendingSlowRelaysEnabled, simulate); } // Unwrap WETH after filling deposits so we don't mess up slow fill logic, but before rebalancing diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index 86516b01b..1615cef51 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -32,6 +32,7 @@ import { getLastBlockTime, getV3RelayHash, lastSpyLogIncludes, + spyLogIncludes, randomAddress, setupTokensForWallet, sinon, @@ -153,6 +154,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { { relayerTokens: [], minDepositConfirmations: defaultMinDepositConfirmations, + sendingRelaysEnabled: true, } as unknown as RelayerConfig ); @@ -198,11 +200,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { await updateAllClients(); await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(lastSpyLogIncludes(spy, "Filling v3 deposit")).to.be.true; - expect(multiCallerClient.transactionCount()).to.equal(1); // One transaction, filling the one deposit. - - const tx = await multiCallerClient.executeTransactionQueue(); - expect(tx.length).to.equal(1); // There should have been exactly one transaction. + expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; await Promise.all([spokePoolClient_1.update(), spokePoolClient_2.update(), hubPoolClient.update()]); const fills = spokePoolClient_2 @@ -218,7 +216,6 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // Re-run the execution loop and validate that no additional relays are sent. multiCallerClient.clearTransactionQueue(); await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(multiCallerClient.transactionCount()).to.equal(0); // no Transactions to send. expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; }); @@ -227,9 +224,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { await updateAllClients(); 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 multiCallerClient.executeTxnQueues(); + expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; // The first fill is still pending but if we rerun the relayer loop, it shouldn't try to fill a second time. await Promise.all([spokePoolClient_1.update(), spokePoolClient_2.update(), hubPoolClient.update()]); @@ -251,9 +246,10 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { 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. + // Run the relayer in simulation mode so it doesn't fill the relay. + const simulate = true; + await relayerInstance.checkForUnfilledDepositsAndFill(false, simulate); + expect(spyLogIncludes(spy, -2, "Filled v3 deposit")).is.true; // Verify that the deposit is still unfilled (relayer didn't execute it). unfilledDeposits = await getUnfilledDeposits(spokePoolClients, hubPoolClient); @@ -267,7 +263,6 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // 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 () { @@ -311,8 +306,9 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { }); it("Correctly validates self-relays", async function () { - outputAmount = inputAmount.sub(bnOne); - for (const testDepositor of [depositor, relayer]) { + outputAmount = inputAmount.add(bnOne); + const simulate = false; + for (const testDepositor of [relayer, depositor]) { await depositV3( spokePool_1, destinationChainId, @@ -324,9 +320,11 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); - const expectedTransactions = testDepositor.address === relayer.address ? 1 : 0; - expect(multiCallerClient.transactionCount()).to.equal(expectedTransactions); + await relayerInstance.checkForUnfilledDepositsAndFill(false, simulate); + const expectedLog = testDepositor.address === relayer.address + ? "Filled v3 deposit" + : "Not relaying unprofitable deposit"; + expect(lastSpyLogIncludes(spy, expectedLog)).to.be.true; } }); @@ -364,14 +362,17 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { await updateAllClients(); await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(multiCallerClient.transactionCount()).to.equal(1); + expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; + + await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; await spokePool_2.setCurrentTime(exclusivityDeadline + 1); await updateAllClients(); // Relayer can unconditionally fill after the exclusivityDeadline. await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(multiCallerClient.transactionCount()).to.equal(2); + expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; }); it("Ignores deposits older than min deposit confirmation threshold", async function () { @@ -426,7 +427,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // If we reset the timestamp, the relayer will fill the deposit: hubPoolClient.currentTime = quoteTimestamp; await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(multiCallerClient.transactionCount()).to.equal(1); + expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; }); it("Ignores deposit with non-empty message", async function () { @@ -509,14 +510,9 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { expect( spy.getCalls().find(({ lastArg }) => lastArg.message.includes("Skipping fill for deposit with message")) ).to.not.be.undefined; - expect(multiCallerClient.transactionCount()).to.equal(0); } else { // Now speed up deposit again with a higher fee and a message of 0x. This should be filled. - expect(lastSpyLogIncludes(spy, "Filling v3 deposit")).to.be.true; - expect(multiCallerClient.transactionCount()).to.equal(1); // One transaction, filling the one deposit. - - const tx = await multiCallerClient.executeTransactionQueue(); - expect(tx.length).to.equal(1); // There should have been exactly one transaction. + expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; await spokePoolClient_2.update(); const fills = spokePoolClient_2 @@ -545,7 +541,6 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { multiCallerClient.clearTransactionQueue(); await Promise.all([spokePoolClient_1.update(), spokePoolClient_2.update(), hubPoolClient.update()]); await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(multiCallerClient.transactionCount()).to.equal(0); // no Transactions to send. expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; }); @@ -588,8 +583,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { await updateAllClients(); await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(lastSpyLogIncludes(spy, "Filling v3 deposit")).to.be.true; - expect(multiCallerClient.transactionCount()).to.equal(1); + expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; }); }); }); diff --git a/test/Relayer.SlowFill.ts b/test/Relayer.SlowFill.ts index 9d70952c5..c22d0cf1b 100644 --- a/test/Relayer.SlowFill.ts +++ b/test/Relayer.SlowFill.ts @@ -193,13 +193,9 @@ describe("Relayer: Initiates slow fill requests", async function () { await updateAllClients(); await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(multiCallerClient.transactionCount()).to.equal(1); // Should be requestV3SlowFill() - expect(spyLogIncludes(spy, -2, "Enqueuing slow fill request.")).to.be.true; + expect(spyLogIncludes(spy, -2, "Requested slow fill for deposit.")).to.be.true; expect(lastSpyLogIncludes(spy, "Insufficient balance to fill all deposits")).to.be.true; - const tx = await multiCallerClient.executeTransactionQueue(); - expect(tx.length).to.equal(1); - // Verify that the slowFill request was received by the destination SpokePoolClient. await Promise.all([spokePoolClient_1.update(), spokePoolClient_2.update(), hubPoolClient.update()]); let slowFillRequest = spokePoolClient_2.getSlowFillRequest(deposit); @@ -211,7 +207,6 @@ describe("Relayer: Initiates slow fill requests", async function () { ); await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(multiCallerClient.transactionCount()).to.equal(0); // no Transactions to send. expect(lastSpyLogIncludes(spy, "Insufficient balance to fill all deposits")).to.be.true; }); }); From 7eb326e0e349753553f572d6ad8b6b635e47a9d4 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:37:27 +0100 Subject: [PATCH 2/6] Improve multicallerclient readability --- src/clients/MultiCallerClient.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clients/MultiCallerClient.ts b/src/clients/MultiCallerClient.ts index 83d2f2c19..74f23670a 100644 --- a/src/clients/MultiCallerClient.ts +++ b/src/clients/MultiCallerClient.ts @@ -124,7 +124,8 @@ export class MultiCallerClient { async executeTxnQueues(simulate = false, chainIds: number[] = []): Promise> { if (chainIds.length === 0) { chainIds = sdkUtils.dedupArray( - Object.keys(this.valueTxns).map(Number).concat(Object.keys(this.txns).map(Number)) + ...(Object.keys(this.valueTxns).map(Number)), + ...(Object.keys(this.txns).map(Number)), ); }; From 3ea53ecd192c794476d5b6c96382d9653484f172 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:42:37 +0100 Subject: [PATCH 3/6] Fix & improve --- src/clients/MultiCallerClient.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/clients/MultiCallerClient.ts b/src/clients/MultiCallerClient.ts index 74f23670a..167f27817 100644 --- a/src/clients/MultiCallerClient.ts +++ b/src/clients/MultiCallerClient.ts @@ -123,16 +123,15 @@ export class MultiCallerClient { // For each chain, collate the enqueued transactions and process them in parallel. async executeTxnQueues(simulate = false, chainIds: number[] = []): Promise> { if (chainIds.length === 0) { - chainIds = sdkUtils.dedupArray( + chainIds = sdkUtils.dedupArray([ ...(Object.keys(this.valueTxns).map(Number)), ...(Object.keys(this.txns).map(Number)), - ); + ]); }; // One promise per chain for parallel execution. const resultsByChain = await Promise.allSettled( - chainIds.map((_chainId) => { - const chainId = Number(_chainId); + chainIds.map((chainId) => { const txns: AugmentedTransaction[] | undefined = this.txns[chainId]; const valueTxns: AugmentedTransaction[] | undefined = this.valueTxns[chainId]; From fa3b867df7de4b51f12d48e1694f29820fd1f82e Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 25 Mar 2024 23:04:03 +0100 Subject: [PATCH 4/6] lint & fix --- src/clients/MultiCallerClient.ts | 6 +++--- test/Relayer.BasicFill.ts | 8 +++----- test/Relayer.TokenShortfall.ts | 6 ++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/clients/MultiCallerClient.ts b/src/clients/MultiCallerClient.ts index 167f27817..342692f11 100644 --- a/src/clients/MultiCallerClient.ts +++ b/src/clients/MultiCallerClient.ts @@ -124,10 +124,10 @@ export class MultiCallerClient { async executeTxnQueues(simulate = false, chainIds: number[] = []): Promise> { if (chainIds.length === 0) { chainIds = sdkUtils.dedupArray([ - ...(Object.keys(this.valueTxns).map(Number)), - ...(Object.keys(this.txns).map(Number)), + ...Object.keys(this.valueTxns).map(Number), + ...Object.keys(this.txns).map(Number), ]); - }; + } // One promise per chain for parallel execution. const resultsByChain = await Promise.allSettled( diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index 1615cef51..3331fc0c2 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -307,7 +307,6 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { it("Correctly validates self-relays", async function () { outputAmount = inputAmount.add(bnOne); - const simulate = false; for (const testDepositor of [relayer, depositor]) { await depositV3( spokePool_1, @@ -320,10 +319,9 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(false, simulate); - const expectedLog = testDepositor.address === relayer.address - ? "Filled v3 deposit" - : "Not relaying unprofitable deposit"; + await relayerInstance.checkForUnfilledDepositsAndFill(false); + const expectedLog = + testDepositor.address === relayer.address ? "Filled v3 deposit" : "Not relaying unprofitable deposit"; expect(lastSpyLogIncludes(spy, expectedLog)).to.be.true; } }); diff --git a/test/Relayer.TokenShortfall.ts b/test/Relayer.TokenShortfall.ts index 783df2209..d75a93b36 100644 --- a/test/Relayer.TokenShortfall.ts +++ b/test/Relayer.TokenShortfall.ts @@ -35,6 +35,7 @@ import { expect, getLastBlockTime, lastSpyLogIncludes, + spyLogIncludes, setupTokensForWallet, sinon, toBN, @@ -207,12 +208,9 @@ describe("Relayer: Token balance shortfall", async function () { await erc20_2.mint(relayer.address, toBN(60).mul(bn10.pow(inputTokenDecimals))); await updateAllClients(); await relayerInstance.checkForUnfilledDepositsAndFill(noSlowRelays); + expect(spyLogIncludes(spy, -2, "Relayed depositId 0")).to.be.true; expect(lastSpyLogIncludes(spy, `${await l1Token.symbol()} cumulative shortfall of 190.00`)).to.be.true; expect(lastSpyLogIncludes(spy, "blocking deposits: 1,2")).to.be.true; - - const tx = await multiCallerClient.executeTransactionQueue(); - expect(lastSpyLogIncludes(spy, "Relayed depositId 0")).to.be.true; - expect(tx.length).to.equal(1); // There should have been exactly one transaction. }); it("Produces expected logs based on insufficient multiple token balance", async function () { From 7d61b8171adf4f874da778c6de1a4dd653bef29b Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Tue, 2 Apr 2024 22:59:42 +0200 Subject: [PATCH 5/6] Improve test --- src/relayer/Relayer.ts | 16 +++++++-- test/Relayer.BasicFill.ts | 74 +++++++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 6fccba831..6006b11dd 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -340,12 +340,18 @@ export class Relayer { } } - async checkForUnfilledDepositsAndFill(sendSlowRelays = true, simulate = false): Promise { + async checkForUnfilledDepositsAndFill( + sendSlowRelays = true, + simulate = false + ): Promise<{ [chainId: number]: string[] }> { // 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(); + const txnReceipts: { [chainId: number]: string[] } = Object.fromEntries( + Object.values(spokePoolClients).map(({ chainId }) => [chainId, []]) + ); // 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. @@ -353,12 +359,13 @@ export class Relayer { const allUnfilledDeposits = Object.values(unfilledDeposits) .flat() .map(({ deposit }) => deposit); + this.logger.debug({ at: "Relayer#checkForUnfilledDepositsAndFill", message: `${allUnfilledDeposits.length} unfilled deposits found.`, }); if (allUnfilledDeposits.length === 0) { - return; + return txnReceipts } const mdcPerChain = this.computeRequiredDepositConfirmations(allUnfilledDeposits); @@ -382,7 +389,8 @@ export class Relayer { const destinationChainId = Number(chainId); if (multiCallerClient.getQueuedTransactions(destinationChainId).length > 0) { - await multiCallerClient.executeTxnQueues(simulate, [destinationChainId]); + const receipts = await multiCallerClient.executeTxnQueues(simulate, [destinationChainId]); + txnReceipts[destinationChainId] = receipts[destinationChainId]; } }); @@ -393,6 +401,8 @@ export class Relayer { if (profitClient.anyCapturedUnprofitableFills()) { this.handleUnprofitableFill(); } + + return txnReceipts; } requestSlowFill(deposit: V3Deposit): void { diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index e9c28c442..a80c92e8f 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -198,7 +198,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(txnReceipts[destinationChainId].length).to.equal(1); expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; await Promise.all([spokePoolClient_1.update(), spokePoolClient_2.update(), hubPoolClient.update()]); @@ -212,7 +213,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // Re-run the execution loop and validate that no additional relays are sent. multiCallerClient.clearTransactionQueue(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; }); @@ -220,13 +222,14 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { await depositV3(spokePool_1, destinationChainId, depositor, inputToken, inputAmount, outputToken, outputAmount); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(txnReceipts[destinationChainId].length).to.equal(1); expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; // The first fill is still pending but if we rerun the relayer loop, it shouldn't try to fill a second time. await Promise.all([spokePoolClient_1.update(), spokePoolClient_2.update(), hubPoolClient.update()]); - await relayerInstance.checkForUnfilledDepositsAndFill(); - expect(multiCallerClient.transactionCount()).to.equal(0); // no new transactions were enqueued. + txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); }); it("Queries the latest onchain fill status for all deposits", async function () { @@ -245,7 +248,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // Run the relayer in simulation mode so it doesn't fill the relay. const simulate = true; - await relayerInstance.checkForUnfilledDepositsAndFill(false, simulate); + let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(false, simulate); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(spyLogIncludes(spy, -2, "Filled v3 deposit")).is.true; // Verify that the deposit is still unfilled (relayer didn't execute it). @@ -258,7 +262,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { expect(Object.values(unfilledDeposits).flat().length).to.equal(0); // Verify that the relayer now sees that the deposit has been filled. - await relayerInstance.checkForUnfilledDepositsAndFill(); + txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; }); @@ -296,7 +301,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // Deposit on originChainId, destined for destinationChainId => expect ignored. await depositV3(spokePool_1, destinationChainId, depositor, inputToken, inputAmount, outputToken, outputAmount); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + const txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect( spy.getCalls().find(({ lastArg }) => lastArg.message.includes("Skipping deposit from or to disabled chains")) ).to.not.be.undefined; @@ -316,9 +322,12 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(false); - const expectedLog = - testDepositor.address === relayer.address ? "Filled v3 deposit" : "Not relaying unprofitable deposit"; + const txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(false); + const selfRelay = testDepositor.address === relayer.address; + const [expectedLog, expectedReceipts] = selfRelay + ? ["Filled v3 deposit", 1] + : ["Not relaying unprofitable deposit", 0]; + expect(txnReceipts[destinationChainId].length).to.equal(expectedReceipts); expect(lastSpyLogIncludes(spy, expectedLog)).to.be.true; } }); @@ -332,7 +341,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { fillDeadline, }); await spokePool_2.setCurrentTime(fillDeadline); - await relayerInstance.checkForUnfilledDepositsAndFill(); + const txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(multiCallerClient.transactionCount()).to.equal(0); }); @@ -356,17 +366,20 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { } await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(txnReceipts[destinationChainId].length).to.equal(1); expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; - await relayerInstance.checkForUnfilledDepositsAndFill(); + txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(txnReceipts[destinationChainId].length).to.equal(0); expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; await spokePool_2.setCurrentTime(exclusivityDeadline + 1); await updateAllClients(); // Relayer can unconditionally fill after the exclusivityDeadline. - await relayerInstance.checkForUnfilledDepositsAndFill(); + txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(txnReceipts[destinationChainId].length).to.equal(1); expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; }); @@ -398,7 +411,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + const txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(lastSpyLogIncludes(spy, "due to insufficient deposit confirmations")).to.be.true; }); @@ -416,12 +430,14 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // Override hub pool client timestamp to make deposit look like its in the future await updateAllClients(); hubPoolClient.currentTime = quoteTimestamp - 1; - await relayerInstance.checkForUnfilledDepositsAndFill(); + let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; // If we reset the timestamp, the relayer will fill the deposit: hubPoolClient.currentTime = quoteTimestamp; - await relayerInstance.checkForUnfilledDepositsAndFill(); + txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(txnReceipts[destinationChainId].length).to.equal(1); expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; }); @@ -445,14 +461,14 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + const txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); // Dynamic fill simulation fails in test, so the deposit will // appear as unprofitable when message filling is enabled. expect(spy.getCalls().find(({ lastArg }) => lastArg.message.includes("Skipping fill for deposit with message"))) .to.not.be.undefined; expect(profitClient.anyCapturedUnprofitableFills()).to.equal(sendingMessageRelaysEnabled); - expect(multiCallerClient.transactionCount()).to.equal(0); } }); @@ -461,10 +477,10 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { await depositV3(spokePool_1, destinationChainId, depositor, inputToken, inputAmount, outputToken, outputAmount); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(spy.getCalls().find(({ lastArg }) => lastArg.message.includes("Ignoring deposit"))).to.not.be.undefined; - expect(multiCallerClient.transactionCount()).to.equal(0); }); it("Uses lowest outputAmount on updated deposits", async function () { @@ -500,13 +516,15 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); if (update.ignored) { + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect( spy.getCalls().find(({ lastArg }) => lastArg.message.includes("Skipping fill for deposit with message")) ).to.not.be.undefined; } else { // Now speed up deposit again with a higher fee and a message of 0x. This should be filled. + expect(txnReceipts[destinationChainId].length).to.equal(1); expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; await spokePoolClient_2.update(); @@ -532,7 +550,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // Re-run the execution loop and validate that no additional relays are sent. multiCallerClient.clearTransactionQueue(); await Promise.all([spokePoolClient_1.update(), spokePoolClient_2.update(), hubPoolClient.update()]); - await relayerInstance.checkForUnfilledDepositsAndFill(); + const txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(lastSpyLogIncludes(spy, "0 unfilled deposits")).to.be.true; }); @@ -559,10 +578,10 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(spy.getCalls().find(({ lastArg }) => lastArg.message.includes("Skipping fill for deposit with message"))) .to.not.be.undefined; - expect(multiCallerClient.transactionCount()).to.equal(0); // Deposit is updated again with a nullified message. updatedOutputAmount = updatedOutputAmount.sub(bnOne); @@ -574,7 +593,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - await relayerInstance.checkForUnfilledDepositsAndFill(); + txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + expect(txnReceipts[destinationChainId].length).to.equal(1); expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; }); }); From f03d467467a5f84ee9ddc9ccc84afcdcfda97d9a Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Tue, 2 Apr 2024 23:03:15 +0200 Subject: [PATCH 6/6] lint --- src/relayer/Relayer.ts | 2 +- test/Relayer.BasicFill.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 6006b11dd..2a29862cd 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -365,7 +365,7 @@ export class Relayer { message: `${allUnfilledDeposits.length} unfilled deposits found.`, }); if (allUnfilledDeposits.length === 0) { - return txnReceipts + return txnReceipts; } const mdcPerChain = this.computeRequiredDepositConfirmations(allUnfilledDeposits); diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index a80c92e8f..1fd60930c 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -478,7 +478,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { await depositV3(spokePool_1, destinationChainId, depositor, inputToken, inputAmount, outputToken, outputAmount); await updateAllClients(); - let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + const txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(spy.getCalls().find(({ lastArg }) => lastArg.message.includes("Ignoring deposit"))).to.not.be.undefined; }); @@ -516,7 +516,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { ); await updateAllClients(); - let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); + const txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); if (update.ignored) { Object.values(txnReceipts).forEach((receipts) => expect(receipts.length).to.equal(0)); expect(