From e4d82753649ead7939613c06a164eefb8f3cfc45 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 23 Jan 2025 11:39:24 -0500 Subject: [PATCH 01/62] feat(Dataworker): Support pre-fill refunds This PR should be backwards compatible and deployable to production today to be paired with https://github.com/across-protocol/sdk/pull/835 --- test/Dataworker.loadData.fill.ts | 29 ++- test/Dataworker.loadData.prefill.ts | 331 +++++++++++++++++++++++++++ test/Dataworker.loadData.slowFill.ts | 22 +- 3 files changed, 370 insertions(+), 12 deletions(-) create mode 100644 test/Dataworker.loadData.prefill.ts diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index a78a704bcd..1b8127ceed 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -590,9 +590,18 @@ describe("Dataworker: Load data used in all functions", async function () { expect(data1.bundleDepositsV3).to.deep.equal({}); }); it("Filters fills out of block range", async function () { - generateV3Deposit({ outputToken: randomAddress() }); - generateV3Deposit({ outputToken: randomAddress() }); - generateV3Deposit({ outputToken: randomAddress() }); + generateV3Deposit({ + outputToken: randomAddress(), + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1, + }); + generateV3Deposit({ + outputToken: randomAddress(), + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, + }); + generateV3Deposit({ + outputToken: randomAddress(), + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 21, + }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); @@ -607,13 +616,16 @@ describe("Dataworker: Load data used in all functions", async function () { blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber + 21, }), ]; - // Create a block range that contains only the middle event. + // Create a block range that contains only the middle events. const destinationChainBlockRange = [fills[1].blockNumber - 1, fills[1].blockNumber + 1]; - // Substitute destination chain bundle block range. + const originChainBlockRange = [deposits[1].blockNumber - 1, deposits[1].blockNumber + 1]; + // Substitute bundle block ranges. const bundleBlockRanges = getDefaultBlockRange(5); const destinationChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + bundleBlockRanges[originChainIndex] = originChainBlockRange; await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); expect(mockDestinationSpokePoolClient.getFills().length).to.equal(fills.length); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); @@ -725,8 +737,11 @@ describe("Dataworker: Load data used in all functions", async function () { const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(blockRanges, spokePoolClients); expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(spyLogIncludes(spy, -2, "invalid V3 fills in range")).to.be.false; + + // Fill should not be included since we cannot validate fills when the deposit is in a following bundle. + // This fill is considered a "pre-fill" and will be validated when the deposit is included in a bundle. + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spyLogIncludes(spy, -2, "invalid V3 fills in range")).to.be.true; }); it("Does not count prior bundle expired deposits that were filled", async function () { // Send deposit that expires in this bundle. diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts new file mode 100644 index 0000000000..b671a0eadf --- /dev/null +++ b/test/Dataworker.loadData.prefill.ts @@ -0,0 +1,331 @@ +import { BundleDataClient, ConfigStoreClient, HubPoolClient, SpokePoolClient } from "../src/clients"; +import { destinationChainId, originChainId, repaymentChainId } from "./constants"; +import { setupDataworker } from "./fixtures/Dataworker.Fixture"; +import { + Contract, + FakeContract, + SignerWithAddress, + V3FillFromDeposit, + ethers, + expect, + getDefaultBlockRange, + randomAddress, + smock, +} from "./utils"; + +import { Dataworker } from "../src/dataworker/Dataworker"; // Tested +import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS } from "../src/utils"; +import { MockHubPoolClient, MockSpokePoolClient } from "./mocks"; +import { interfaces } from "@across-protocol/sdk"; + +let erc20_1: Contract, erc20_2: Contract; +let l1Token_1: Contract; +let relayer: SignerWithAddress; + +let spokePoolClient_1: SpokePoolClient, spokePoolClient_2: SpokePoolClient; +let hubPoolClient: HubPoolClient, configStoreClient: ConfigStoreClient; +let dataworkerInstance: Dataworker; +let spokePoolClients: { [chainId: number]: SpokePoolClient }; + +let updateAllClients: () => Promise; + +describe("BundleDataClient: Pre-fill logic", async function () { + beforeEach(async function () { + ({ + erc20_1, + erc20_2, + hubPoolClient, + configStoreClient, + l1Token_1, + relayer, + dataworkerInstance, + spokePoolClient_1, + spokePoolClient_2, + spokePoolClients, + updateAllClients, + } = await setupDataworker(ethers, 25, 25, 0)); + }); + + describe("loadDataFromScratch", function () { + let mockOriginSpokePoolClient: MockSpokePoolClient, mockDestinationSpokePoolClient: MockSpokePoolClient; + let mockHubPoolClient: MockHubPoolClient; + let mockDestinationSpokePool: FakeContract; + const lpFeePct = toBNWei("0.01"); + + beforeEach(async function () { + await updateAllClients(); + mockHubPoolClient = new MockHubPoolClient( + hubPoolClient.logger, + hubPoolClient.hubPool, + configStoreClient, + hubPoolClient.deploymentBlock, + hubPoolClient.chainId + ); + // Mock a realized lp fee pct for each deposit so we can check refund amounts and bundle lp fees. + mockHubPoolClient.setDefaultRealizedLpFeePct(lpFeePct); + mockOriginSpokePoolClient = new MockSpokePoolClient( + spokePoolClient_1.logger, + spokePoolClient_1.spokePool, + spokePoolClient_1.chainId, + spokePoolClient_1.deploymentBlock + ); + mockDestinationSpokePool = await smock.fake(spokePoolClient_2.spokePool.interface); + mockDestinationSpokePoolClient = new MockSpokePoolClient( + spokePoolClient_2.logger, + mockDestinationSpokePool as Contract, + spokePoolClient_2.chainId, + spokePoolClient_2.deploymentBlock + ); + spokePoolClients = { + ...spokePoolClients, + [originChainId]: mockOriginSpokePoolClient, + [destinationChainId]: mockDestinationSpokePoolClient, + }; + await mockHubPoolClient.update(); + await mockOriginSpokePoolClient.update(); + await mockDestinationSpokePoolClient.update(); + mockHubPoolClient.setTokenMapping(l1Token_1.address, originChainId, erc20_1.address); + mockHubPoolClient.setTokenMapping(l1Token_1.address, destinationChainId, erc20_2.address); + mockHubPoolClient.setTokenMapping(l1Token_1.address, repaymentChainId, l1Token_1.address); + const bundleDataClient = new BundleDataClient( + dataworkerInstance.logger, + { + ...dataworkerInstance.clients.bundleDataClient.clients, + hubPoolClient: mockHubPoolClient as unknown as HubPoolClient, + }, + dataworkerInstance.clients.bundleDataClient.spokePoolClients, + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers + ); + dataworkerInstance = new Dataworker( + dataworkerInstance.logger, + { ...dataworkerInstance.clients, bundleDataClient }, + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers, + dataworkerInstance.maxRefundCountOverride, + dataworkerInstance.maxL1TokenCountOverride, + dataworkerInstance.blockRangeEndBlockBuffer + ); + }); + + function generateV3Deposit(eventOverride?: Partial): Event { + return mockOriginSpokePoolClient.depositV3({ + inputToken: erc20_1.address, + outputToken: eventOverride?.outputToken ?? erc20_2.address, + message: "0x", + quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, + fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, + destinationChainId, + blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestBlockSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestBlockSearched gets set to the same value. + } as interfaces.DepositWithBlock); + } + + function generateV3FillFromDeposit( + deposit: interfaces.DepositWithBlock, + fillEventOverride?: Partial, + _relayer = relayer.address, + _repaymentChainId = repaymentChainId, + fillType = interfaces.FillType.FastFill + ): Event { + const fillObject = V3FillFromDeposit(deposit, _relayer, _repaymentChainId); + return mockDestinationSpokePoolClient.fillV3Relay({ + ...fillObject, + relayExecutionInfo: { + updatedRecipient: fillObject.updatedRecipient, + updatedMessage: fillObject.updatedMessage, + updatedOutputAmount: fillObject.updatedOutputAmount, + fillType, + }, + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestBlockSearched gets set to the same value. + } as interfaces.FillWithBlock); + } + + function generateSlowFillRequestFromDeposit( + deposit: interfaces.DepositWithBlock, + fillEventOverride?: Partial + ): Event { + const fillObject = V3FillFromDeposit(deposit, ZERO_ADDRESS); + const { relayer, repaymentChainId, relayExecutionInfo, ...relayData } = fillObject; + return mockDestinationSpokePoolClient.requestV3SlowFill({ + ...relayData, + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestBlockSearched gets set to the same value. + } as interfaces.SlowFillRequest); + } + + describe("Pre-fills", function () { + it("Refunds fill if fill is in-memory and in older bundle", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit fill that we won't include in the bundle block range. + const fill = generateV3FillFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, + }); + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = [fill.blockNumber + 1, fill.blockNumber + 2]; + + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); + + // So, one of the fills is a pre-fill because its earlier than the bundle block range. Because its corresponding + // deposit is in the block range, we should refund it. + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( + fill.args.depositId + ); + }); + + it("Does not refund fill if fill is in-memory but in a future bundle", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit fill that we won't include in the bundle block range but is in a future bundle + const futureFill = generateV3FillFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber + 11, + }); + + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = [futureFill.blockNumber - 2, futureFill.blockNumber - 1]; + + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3).to.deep.equal({}); + }); + + it("Does not refund fill if fill is in-memory but its a SlowFill", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit fill in an older bundle but its a Slow Fill execution. + const slowFill = generateV3FillFromDeposit( + deposits[0], + { blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber }, + undefined, + undefined, + interfaces.FillType.SlowFill + ); + + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = [slowFill.blockNumber + 1, slowFill.blockNumber + 2]; + + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3).to.deep.equal({}); + }); + + it("Refunds fill if fill status is Filled", async function () { + // Checks relayStatuses() for fill status + // Loads old event and sets refund address and repayment chain correctly. + }); + + it("Does not refund if fill status is not Filled", async function () {}); + }); + + describe.only("Pre-slow-fill-requests", function () { + it("Creates slow fill leaf if slow fill request is in-memory and in older bundle", async function () { + generateV3Deposit({ outputToken: erc20_2.address }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit request that we won't include in the bundle block range. + const request = generateSlowFillRequestFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, + }); + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = [request.blockNumber + 1, request.blockNumber + 2]; + + await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); + expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(1); + + // So, one of the fills is a pre-fill because its earlier than the bundle block range. Because its corresponding + // deposit is in the block range, we should refund it. + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address].length).to.equal(1); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].depositId).to.equal( + request.args.depositId + ); + }); + + it("Creates slow fill leaf if fill status is RequestedSlowFill", async function () { + // Checks relayStatuses() for fill status + // Creates slow fill leaf. + }); + + it("Does not create slow fill leaf if slow fill request is in-memory but in a future bundle", async function () { + generateV3Deposit({ outputToken: erc20_2.address }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit request that we won't include in the bundle block range but is in a future bundle + const request = generateSlowFillRequestFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber + 11, + }); + + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = [request.blockNumber - 2, request.blockNumber - 1]; + + await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); + expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(1); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleSlowFillsV3).to.deep.equal({}); + }); + + it("Does not create slow fill leaf if slow fill request is in-memory but an invalid request", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + generateV3Deposit({ outputToken: erc20_2.address, fillDeadline: 0 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit request that that is in a previous bundle but is invalid. + generateSlowFillRequestFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, + }); + const expiredDepositRequest = generateSlowFillRequestFromDeposit(deposits[1], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, + }); + + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = [ + expiredDepositRequest.blockNumber + 1, + expiredDepositRequest.blockNumber + 2, + ]; + + await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); + expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(2); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleSlowFillsV3).to.deep.equal({}); + }); + + it("Does not create slow fill leaf if fill status is not RequestedSlowFill", async function () {}); + }); + }); +}); diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index 39635325c5..8e037eb739 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -463,9 +463,18 @@ describe("BundleDataClient: Slow fill handling & validation", async function () }); it("Handles slow fill requests out of block range", async function () { - generateV3Deposit({ outputToken: erc20_2.address }); - generateV3Deposit({ outputToken: erc20_2.address }); - generateV3Deposit({ outputToken: erc20_2.address }); + generateV3Deposit({ + outputToken: erc20_2.address, + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1, + }); + generateV3Deposit({ + outputToken: erc20_2.address, + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, + }); + generateV3Deposit({ + outputToken: erc20_2.address, + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 21, + }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); @@ -480,13 +489,16 @@ describe("BundleDataClient: Slow fill handling & validation", async function () blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber + 21, }), ]; - // Create a block range that contains only the middle event. + // Create a block range that contains only the middle events. const destinationChainBlockRange = [events[1].blockNumber - 1, events[1].blockNumber + 1]; - // Substitute destination chain bundle block range. + const originChainBlockRange = [deposits[1].blockNumber - 1, deposits[1].blockNumber + 1]; + // Substitute bundle block ranges. const bundleBlockRanges = getDefaultBlockRange(5); const destinationChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + bundleBlockRanges[originChainIndex] = originChainBlockRange; await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(3); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); From fbb614775913d0bb9fac52537bca3d6979b6ed68 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 23 Jan 2025 15:27:16 -0500 Subject: [PATCH 02/62] Finish new tests --- test/Dataworker.loadData.prefill.ts | 96 +++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index b671a0eadf..df77ef9f41 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -1,13 +1,15 @@ import { BundleDataClient, ConfigStoreClient, HubPoolClient, SpokePoolClient } from "../src/clients"; -import { destinationChainId, originChainId, repaymentChainId } from "./constants"; +import { amountToDeposit, destinationChainId, originChainId, repaymentChainId } from "./constants"; import { setupDataworker } from "./fixtures/Dataworker.Fixture"; import { Contract, FakeContract, SignerWithAddress, V3FillFromDeposit, + depositV3, ethers, expect, + fillV3, getDefaultBlockRange, randomAddress, smock, @@ -16,11 +18,11 @@ import { import { Dataworker } from "../src/dataworker/Dataworker"; // Tested import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS } from "../src/utils"; import { MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces } from "@across-protocol/sdk"; +import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; let erc20_1: Contract, erc20_2: Contract; let l1Token_1: Contract; -let relayer: SignerWithAddress; +let relayer: SignerWithAddress, depositor: SignerWithAddress; let spokePoolClient_1: SpokePoolClient, spokePoolClient_2: SpokePoolClient; let hubPoolClient: HubPoolClient, configStoreClient: ConfigStoreClient; @@ -38,22 +40,55 @@ describe("BundleDataClient: Pre-fill logic", async function () { configStoreClient, l1Token_1, relayer, + depositor, dataworkerInstance, spokePoolClient_1, spokePoolClient_2, spokePoolClients, updateAllClients, } = await setupDataworker(ethers, 25, 25, 0)); + await updateAllClients(); }); - describe("loadDataFromScratch", function () { + describe("Tests with real events", function () { + describe("Pre-fills", function () { + it("Fetches and refunds fill if fill status is Filled", async function () { + // In this test, there is no fill in the SpokePoolClient's memory so the BundleDataClient + // should query its fill status on-chain and then fetch it to create a refund. + const deposit = await depositV3( + spokePoolClient_1.spokePool, + destinationChainId, + depositor, + erc20_1.address, + amountToDeposit, + erc20_2.address, + amountToDeposit + ); + await spokePoolClient_1.update(["V3FundsDeposited"]); + + // Mine fill, update spoke pool client to advance its latest block searched far enough such that the + // fillStatuses() call and findFillEvent() queries work but ignore the fill event to force the Bundle Client + // to query fresh for the fill status. + await fillV3(spokePoolClient_2.spokePool, relayer, deposit); + await spokePoolClient_2.update([]); + expect(spokePoolClient_2.getFills().length).to.equal(0); + + // The bundle data client should create a refund for the pre fill + const bundleBlockRanges = getDefaultBlockRange(5); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal(deposit.depositId); + }); + }); + }); + + describe("Tests with mocked events", function () { let mockOriginSpokePoolClient: MockSpokePoolClient, mockDestinationSpokePoolClient: MockSpokePoolClient; let mockHubPoolClient: MockHubPoolClient; let mockDestinationSpokePool: FakeContract; const lpFeePct = toBNWei("0.01"); beforeEach(async function () { - await updateAllClients(); mockHubPoolClient = new MockHubPoolClient( hubPoolClient.logger, hubPoolClient.hubPool, @@ -230,16 +265,9 @@ describe("BundleDataClient: Pre-fill logic", async function () { const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); expect(data1.bundleFillsV3).to.deep.equal({}); }); - - it("Refunds fill if fill status is Filled", async function () { - // Checks relayStatuses() for fill status - // Loads old event and sets refund address and repayment chain correctly. - }); - - it("Does not refund if fill status is not Filled", async function () {}); }); - describe.only("Pre-slow-fill-requests", function () { + describe("Pre-slow-fill-requests", function () { it("Creates slow fill leaf if slow fill request is in-memory and in older bundle", async function () { generateV3Deposit({ outputToken: erc20_2.address }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); @@ -267,11 +295,6 @@ describe("BundleDataClient: Pre-fill logic", async function () { ); }); - it("Creates slow fill leaf if fill status is RequestedSlowFill", async function () { - // Checks relayStatuses() for fill status - // Creates slow fill leaf. - }); - it("Does not create slow fill leaf if slow fill request is in-memory but in a future bundle", async function () { generateV3Deposit({ outputToken: erc20_2.address }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); @@ -325,7 +348,42 @@ describe("BundleDataClient: Pre-fill logic", async function () { expect(data1.bundleSlowFillsV3).to.deep.equal({}); }); - it("Does not create slow fill leaf if fill status is not RequestedSlowFill", async function () {}); + it("Creates slow fill leaf if fill status is RequestedSlowFill", async function () { + const deposit = generateV3Deposit({ outputToken: erc20_2.address }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + const depositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); + + // Can smock the fillStatuses() value here since the BundleDataClient doesn't need the fill event + // to create a slow fill leaf. + mockDestinationSpokePool.fillStatuses + .whenCalledWith(depositHash) + .returns(interfaces.FillStatus.RequestedSlowFill); + + // Check that bundle slow fills includes leaf. + const bundleBlockRanges = getDefaultBlockRange(5); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address].length).to.equal(1); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].depositId).to.equal( + deposit.args.depositId + ); + }); + + it("Does not create slow fill leaf if fill status is RequestedSlowFill but slow fill request is invalid", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + const depositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); + + mockDestinationSpokePool.fillStatuses + .whenCalledWith(depositHash) + .returns(interfaces.FillStatus.RequestedSlowFill); + + // The deposit is for a token swap so the slow fill request is invalid. + const bundleBlockRanges = getDefaultBlockRange(5); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleSlowFillsV3).to.deep.equal({}); + }); }); }); }); From 40ad15f108dfd297a45817e4893fb87e825653f1 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 23 Jan 2025 15:33:46 -0500 Subject: [PATCH 03/62] Update Dataworker.buildRoots.ts --- test/Dataworker.buildRoots.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/Dataworker.buildRoots.ts b/test/Dataworker.buildRoots.ts index 3af6f3c13d..2cadae32ae 100644 --- a/test/Dataworker.buildRoots.ts +++ b/test/Dataworker.buildRoots.ts @@ -275,13 +275,7 @@ describe("Dataworker: Build merkle roots", async function () { // Origin chain running balance is incremented by refunded deposit which cancels out the subtraction for // bundle deposit. - const expectedRunningBalances: RunningBalances = { - // Note: There should be no origin chain entry here since there were no deposits. - [originChainId]: { - [l1Token_1.address]: bnZero, - }, - }; - expect(expectedRunningBalances).to.deep.equal(merkleRoot1.runningBalances); + expect(bnZero).to.equal(merkleRoot1.runningBalances[originChainId][l1Token_1.address]); expect({}).to.deep.equal(merkleRoot1.realizedLpFees); }); it("Adds fills to relayer refund root", async function () { From cc956ac9ef21f69e0ce90665596f2f95b8aa2ce1 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 23 Jan 2025 15:43:45 -0500 Subject: [PATCH 04/62] Revert "Update Dataworker.buildRoots.ts" This reverts commit 40ad15f108dfd297a45817e4893fb87e825653f1. --- test/Dataworker.buildRoots.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/Dataworker.buildRoots.ts b/test/Dataworker.buildRoots.ts index 2cadae32ae..3af6f3c13d 100644 --- a/test/Dataworker.buildRoots.ts +++ b/test/Dataworker.buildRoots.ts @@ -275,7 +275,13 @@ describe("Dataworker: Build merkle roots", async function () { // Origin chain running balance is incremented by refunded deposit which cancels out the subtraction for // bundle deposit. - expect(bnZero).to.equal(merkleRoot1.runningBalances[originChainId][l1Token_1.address]); + const expectedRunningBalances: RunningBalances = { + // Note: There should be no origin chain entry here since there were no deposits. + [originChainId]: { + [l1Token_1.address]: bnZero, + }, + }; + expect(expectedRunningBalances).to.deep.equal(merkleRoot1.runningBalances); expect({}).to.deep.equal(merkleRoot1.realizedLpFees); }); it("Adds fills to relayer refund root", async function () { From b099cf0e6f910152b60c7dffd0618eae25a039fa Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 23 Jan 2025 18:11:00 -0500 Subject: [PATCH 05/62] Add test against duplicate deposits --- test/Dataworker.loadData.prefill.ts | 63 +++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index df77ef9f41..6319ceec33 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -207,7 +207,7 @@ describe("BundleDataClient: Pre-fill logic", async function () { await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); - // So, one of the fills is a pre-fill because its earlier than the bundle block range. Because its corresponding + // The fill is a pre-fill because its earlier than the bundle block range. Because its corresponding // deposit is in the block range, we should refund it. const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); @@ -216,6 +216,35 @@ describe("BundleDataClient: Pre-fill logic", async function () { ); }); + it("Ignores duplicate deposits", async function () { + // In this test, we send one fill per deposit. We assume you cannot send more than one fill per deposit. + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(2); + + // Submit fill that we won't include in the bundle block range. + const fill = generateV3FillFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, + }); + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = [fill.blockNumber + 1, fill.blockNumber + 2]; + + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( + fill.args.depositId + ); + }); + it("Does not refund fill if fill is in-memory but in a future bundle", async function () { generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); @@ -286,8 +315,36 @@ describe("BundleDataClient: Pre-fill logic", async function () { await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(1); - // So, one of the fills is a pre-fill because its earlier than the bundle block range. Because its corresponding - // deposit is in the block range, we should refund it. + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address].length).to.equal(1); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].depositId).to.equal( + request.args.depositId + ); + }); + + it("Ignores duplicate deposits", async function () { + // In this test, we should create one leaf per deposit. + // We assume you cannot send more than one request per deposit. + generateV3Deposit({ outputToken: erc20_2.address }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(2); + + // Submit request that we won't include in the bundle block range. + const request = generateSlowFillRequestFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, + }); + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = [request.blockNumber + 1, request.blockNumber + 2]; + + await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); + expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(1); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address].length).to.equal(1); expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].depositId).to.equal( From 3d4097d30250d2700bd01cf443f3e9b4c4883d15 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 23 Jan 2025 22:12:35 -0500 Subject: [PATCH 06/62] Add unit tests for duplicate deposits --- test/Dataworker.loadData.fill.ts | 65 ++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 1b8127ceed..583b732a9f 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -287,6 +287,40 @@ describe("Dataworker: Load data used in all functions", async function () { expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); expect(data1.bundleDepositsV3[originChainId][erc20_1.address][0].depositId).to.equal(deposits[1].args.depositId); }); + it("Includes duplicate deposits in bundle data", async function () { + generateV3Deposit(); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const depositToDuplicate = mockOriginSpokePoolClient.getDeposits()[0]; + mockOriginSpokePoolClient.depositV3(depositToDuplicate); + mockOriginSpokePoolClient.depositV3(depositToDuplicate); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + expect( + mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId).length + ).to.equal(3); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + }); + it("Filters duplicate deposits out of block range", async function () { + const deposit = generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const depositToDuplicate = mockOriginSpokePoolClient.getDeposits()[0]; + const duplicateDeposit = mockOriginSpokePoolClient.depositV3({ + ...depositToDuplicate, + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + expect( + mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId).length + ).to.equal(2); + const originChainBlockRange = [deposit.blockNumber, duplicateDeposit.blockNumber - 1]; + // Substitute origin chain bundle block range. + const bundleBlockRanges = [originChainBlockRange].concat(getDefaultBlockRange(5).slice(1)); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); + }); it("Ignores expired deposits that were filled in same bundle", async function () { const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( [originChainId, destinationChainId], @@ -723,6 +757,37 @@ describe("Dataworker: Load data used in all functions", async function () { expect(data2.bundleDepositsV3).to.deep.equal({}); expect(data2.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); }); + it("Includes duplicate prior bundle expired deposits", async function () { + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + + // Send deposit that expires in this bundle and send duplicate at same block. + const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + + // Now, load a bundle that doesn't include the deposits in its range. + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data2 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + // Now, there is no bundle deposit but still two expired deposits to refund. + expect(data2.bundleDepositsV3).to.deep.equal({}); + expect(data2.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + }); it("Handles when deposit is greater than origin bundle end block but fill is within range", async function () { // Send deposit after origin chain block range. const blockRanges = getDefaultBlockRange(5); From 12673d8774492537de1873b6017b69618ff1fb99 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 10:55:26 -0500 Subject: [PATCH 07/62] Import beta sdk --- package.json | 2 +- test/Dataworker.loadData.fill.ts | 6 +-- test/Dataworker.loadData.prefill.ts | 64 +++++++++++------------------ yarn.lock | 8 ++-- 4 files changed, 31 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index b5af58c3ad..05a6881a50 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^3.4.12", + "@across-protocol/sdk": "^4.0.0-beta.0", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 583b732a9f..3299e43e3b 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -228,19 +228,15 @@ describe("Dataworker: Load data used in all functions", async function () { spokePoolClients ); // Send unexpired deposit - const unexpiredDeposits = [generateV3Deposit()]; + generateV3Deposit(); // Send expired deposit const expiredDeposits = [generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 })]; - const depositEvents = [...unexpiredDeposits, ...expiredDeposits]; await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( getDefaultBlockRange(5), spokePoolClients ); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].map((deposit) => deposit.depositId)).to.deep.equal( - depositEvents.map((event) => event.args.depositId) - ); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); expect( data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].map((deposit) => deposit.depositId) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 6319ceec33..8531cd5c21 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -216,9 +216,9 @@ describe("BundleDataClient: Pre-fill logic", async function () { ); }); - it("Ignores duplicate deposits", async function () { - // In this test, we send one fill per deposit. We assume you cannot send more than one fill per deposit. - generateV3Deposit({ outputToken: randomAddress() }); + it("Refunds pre-fills for duplicate deposits", async function () { + // In this test, we send multiple fills per deposit. We assume you cannot send more than one fill per deposit. + const deposit = generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); @@ -238,10 +238,14 @@ describe("BundleDataClient: Pre-fill logic", async function () { await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); + // This should return two refunds for the two deposits. const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(2); expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( - fill.args.depositId + deposit.args.depositId + ); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[1].depositId).to.equal( + deposit.args.depositId ); }); @@ -302,10 +306,11 @@ describe("BundleDataClient: Pre-fill logic", async function () { await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); - // Submit request that we won't include in the bundle block range. + // Submit request that is older than the bundle block range. const request = generateSlowFillRequestFromDeposit(deposits[0], { blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, }); + // Substitute bundle block ranges. const bundleBlockRanges = getDefaultBlockRange(5); const destinationChainIndex = @@ -322,17 +327,17 @@ describe("BundleDataClient: Pre-fill logic", async function () { ); }); - it("Ignores duplicate deposits", async function () { - // In this test, we should create one leaf per deposit. + it("Creates leaves for duplicate deposits", async function () { + // In this test, we should create multiple leaves per duplicate deposit. // We assume you cannot send more than one request per deposit. - generateV3Deposit({ outputToken: erc20_2.address }); + const deposit = generateV3Deposit({ outputToken: erc20_2.address }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); expect(deposits.length).to.equal(2); - // Submit request that we won't include in the bundle block range. + // Submit request for prior bundle. const request = generateSlowFillRequestFromDeposit(deposits[0], { blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, }); @@ -346,42 +351,22 @@ describe("BundleDataClient: Pre-fill logic", async function () { expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(1); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address].length).to.equal(1); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address].length).to.equal(2); expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].depositId).to.equal( - request.args.depositId + deposit.args.depositId + ); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][1].depositId).to.equal( + deposit.args.depositId ); - }); - - it("Does not create slow fill leaf if slow fill request is in-memory but in a future bundle", async function () { - generateV3Deposit({ outputToken: erc20_2.address }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDeposits(); - - // Submit request that we won't include in the bundle block range but is in a future bundle - const request = generateSlowFillRequestFromDeposit(deposits[0], { - blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber + 11, - }); - - // Substitute bundle block ranges. - const bundleBlockRanges = getDefaultBlockRange(5); - const destinationChainIndex = - dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); - bundleBlockRanges[destinationChainIndex] = [request.blockNumber - 2, request.blockNumber - 1]; - - await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); - expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(1); - - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleSlowFillsV3).to.deep.equal({}); }); it("Does not create slow fill leaf if slow fill request is in-memory but an invalid request", async function () { - generateV3Deposit({ outputToken: randomAddress() }); - generateV3Deposit({ outputToken: erc20_2.address, fillDeadline: 0 }); + generateV3Deposit({ outputToken: randomAddress() }); // Token swap + generateV3Deposit({ outputToken: erc20_2.address, fillDeadline: 0 }); // Fill deadline expired await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); - // Submit request that that is in a previous bundle but is invalid. + // Submit request that is in a previous bundle but is invalid. generateSlowFillRequestFromDeposit(deposits[0], { blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, }); @@ -427,7 +412,8 @@ describe("BundleDataClient: Pre-fill logic", async function () { }); it("Does not create slow fill leaf if fill status is RequestedSlowFill but slow fill request is invalid", async function () { - generateV3Deposit({ outputToken: randomAddress() }); + generateV3Deposit({ outputToken: randomAddress() }); // Token swap + generateV3Deposit({ outputToken: erc20_2.address, fillDeadline: 0 }); // Fill deadline expired await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const depositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); diff --git a/yarn.lock b/yarn.lock index b5a50db9c1..360b5087d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^3.4.12": - version "3.4.12" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.4.12.tgz#2944f41d51a2cd889a4d1882435149a32e5441c8" - integrity sha512-Bc4hpvl0CysisOLk+WQLbSUwhRhJ/NPyHwocA/Qo5ERne+zmN8P8dsmVQLgsIxay3Nu+VLVcNLoSKWewiom46w== +"@across-protocol/sdk@^4.0.0-beta.0": + version "4.0.0-beta.0" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.0.tgz#e58cdb77e69131c0ba6732d00acaae7469f632ab" + integrity sha512-nDF34YkdQgwLTDzg3/8H0aoPIA0wtpI4/DpH5D2Lord8SGVEe8K+Vy1HVyGucf3+CTvQkFxXT2YswHgiWKMR/w== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From b7bf412c41227cac187a826021ff687bddd0e460 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 13:35:22 -0500 Subject: [PATCH 08/62] import sdk --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 05a6881a50..5d753702fe 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.0", + "@across-protocol/sdk": "^4.0.0-beta.1", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 360b5087d0..cf8284469d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.0": - version "4.0.0-beta.0" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.0.tgz#e58cdb77e69131c0ba6732d00acaae7469f632ab" - integrity sha512-nDF34YkdQgwLTDzg3/8H0aoPIA0wtpI4/DpH5D2Lord8SGVEe8K+Vy1HVyGucf3+CTvQkFxXT2YswHgiWKMR/w== +"@across-protocol/sdk@^4.0.0-beta.1": + version "4.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.1.tgz#deed8acdc524d1c92b2f75ead76b38477c2c5c80" + integrity sha512-dgUMuAM0HhoqvCOfIbJk8qSvjkzu9Hy+sm6T2IbSjCZfJ0Iktc3dfWOPvkvHXN2FebpWvtdRz7gqZeBRTwY6Qw== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 1454f1051eb20f324817732939825f9e8b2ccf84 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 13:38:47 -0500 Subject: [PATCH 09/62] update import --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5d753702fe..e0cb67a417 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.1", + "@across-protocol/sdk": "^4.0.0-beta.2", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index cf8284469d..31fda8ed97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.1": - version "4.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.1.tgz#deed8acdc524d1c92b2f75ead76b38477c2c5c80" - integrity sha512-dgUMuAM0HhoqvCOfIbJk8qSvjkzu9Hy+sm6T2IbSjCZfJ0Iktc3dfWOPvkvHXN2FebpWvtdRz7gqZeBRTwY6Qw== +"@across-protocol/sdk@^4.0.0-beta.2": + version "4.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.2.tgz#3b82f5e7ecd6e244b8bad60a594325fbeb389970" + integrity sha512-S82gcrA4hT8PQ9nUDL8uNC2x+2pMwEob7xjDBrFqSu5UFQP751liyWOBcV0TKAis6jVZ1EJxI8E8pvEOMI3bEg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 887912484591119d737a8935b7d3632ad5d7c3fd Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 13:40:00 -0500 Subject: [PATCH 10/62] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e0cb67a417..523e8b7b77 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "license": "AGPL-3.0-only", "private": true, "engines": { - "node": ">=20" + "node": ">=20.18.0" }, "dependencies": { "@across-protocol/constants": "^3.1.30", From 6ffc9504dc7a894fab3adcdeac5761f502c3b26e Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 13:41:34 -0500 Subject: [PATCH 11/62] Update config.yml --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ef6730d1e4..d6bf682a2d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 jobs: install: docker: - - image: cimg/node:20.14.0 + - image: cimg/node:20.18.0 working_directory: ~/relayer-v2 resource_class: medium+ steps: @@ -21,7 +21,7 @@ jobs: - node_modules test: docker: - - image: cimg/node:20.14.0 + - image: cimg/node:20.18.0 working_directory: ~/relayer-v2 resource_class: medium+ parallelism: 20 @@ -41,7 +41,7 @@ jobs: yarn test --bail $(cat /tmp/test-files) lint: docker: - - image: cimg/node:20.14.0 + - image: cimg/node:20.18.0 working_directory: ~/relayer-v2 resource_class: medium+ steps: From 6e78182f126af0e58ea69e8b1d0dfa46cb87a561 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 13:53:26 -0500 Subject: [PATCH 12/62] wip --- src/clients/ProfitClient.ts | 2 +- src/clients/TokenClient.ts | 6 +++--- src/dataworker/DataworkerUtils.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/clients/ProfitClient.ts b/src/clients/ProfitClient.ts index b04e608b0d..6ba301b4f9 100644 --- a/src/clients/ProfitClient.ts +++ b/src/clients/ProfitClient.ts @@ -630,7 +630,7 @@ export class ProfitClient { // @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. const sampleDeposit = { - depositId: 0, + depositId: bnZero, depositor: TEST_RECIPIENT, recipient: TEST_RECIPIENT, inputToken: ZERO_ADDRESS, // Not verified by the SpokePool. diff --git a/src/clients/TokenClient.ts b/src/clients/TokenClient.ts index 9db5679734..dc4bbe4d84 100644 --- a/src/clients/TokenClient.ts +++ b/src/clients/TokenClient.ts @@ -23,7 +23,7 @@ import { export type TokenDataType = { [chainId: number]: { [token: string]: { balance: BigNumber; allowance: BigNumber } } }; type TokenShortfallType = { - [chainId: number]: { [token: string]: { deposits: number[]; totalRequirement: BigNumber } }; + [chainId: number]: { [token: string]: { deposits: BigNumber[]; totalRequirement: BigNumber } }; }; export class TokenClient { @@ -63,7 +63,7 @@ export class TokenClient { return this.getShortfallTotalRequirement(chainId, token).sub(this.getBalance(chainId, token)); } - getShortfallDeposits(chainId: number, token: string): number[] { + getShortfallDeposits(chainId: number, token: string): BigNumber[] { return this.tokenShortfall?.[chainId]?.[token]?.deposits || []; } @@ -73,7 +73,7 @@ export class TokenClient { // If the relayer tries to execute a relay but does not have enough tokens to fully fill it will capture the // shortfall by calling this method. This will track the information for logging purposes and use in other clients. - captureTokenShortfall(chainId: number, token: string, depositId: number, unfilledAmount: BigNumber): void { + captureTokenShortfall(chainId: number, token: string, depositId: BigNumber, unfilledAmount: BigNumber): void { // Shortfall is the previous shortfall + the current unfilledAmount from this deposit. const totalRequirement = this.getShortfallTotalRequirement(chainId, token).add(unfilledAmount); diff --git a/src/dataworker/DataworkerUtils.ts b/src/dataworker/DataworkerUtils.ts index 813926843e..f53313fd24 100644 --- a/src/dataworker/DataworkerUtils.ts +++ b/src/dataworker/DataworkerUtils.ts @@ -176,7 +176,7 @@ export function _buildSlowRelayRoot(bundleSlowFillsV3: BundleSlowFills): { const sortedLeaves = [...slowRelayLeaves].sort((relayA, relayB) => { // Note: Smaller ID numbers will come first if (relayA.relayData.originChainId === relayB.relayData.originChainId) { - return relayA.relayData.depositId - relayB.relayData.depositId; + return relayA.relayData.depositId.lt(relayB.relayData.depositId) ? -1 : 1; } else { return relayA.relayData.originChainId - relayB.relayData.originChainId; } From bc1669d5c1c1daa1d68b2995b8f5df677afe037e Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 14:06:48 -0500 Subject: [PATCH 13/62] Fix tests --- test/DataworkerUtils.ts | 2 +- test/Relayer.UnfilledDeposits.ts | 2 +- test/utils/utils.ts | 21 +-------------------- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/test/DataworkerUtils.ts b/test/DataworkerUtils.ts index 9e83826a2d..2c5367f5e8 100644 --- a/test/DataworkerUtils.ts +++ b/test/DataworkerUtils.ts @@ -96,7 +96,7 @@ describe("SlowFill utils", function () { outputAmount: bnOne, outputToken: randomAddress(), depositor: randomAddress(), - depositId, + depositId: BigNumber.from(depositId), originChainId: 1, recipient: randomAddress(), exclusiveRelayer: ZERO_ADDRESS, diff --git a/test/Relayer.UnfilledDeposits.ts b/test/Relayer.UnfilledDeposits.ts index 91c8460b2c..07d02a17e4 100644 --- a/test/Relayer.UnfilledDeposits.ts +++ b/test/Relayer.UnfilledDeposits.ts @@ -258,7 +258,7 @@ describe("Relayer: Unfilled Deposits", async function () { // Make an invalid fills by tweaking outputAmount and depositId, respectively. const fakeDeposit = { ...deposit, outputAmount: deposit.outputAmount.sub(bnOne) }; const invalidFill = await fillV3Relay(spokePool_2, fakeDeposit, relayer); - const wrongDepositId = { ...deposit, depositId: deposit.depositId + 1 }; + const wrongDepositId = { ...deposit, depositId: deposit.depositId.add(1) }; await fillV3Relay(spokePool_2, wrongDepositId, relayer); // The deposit should show up as unfilled, since the fill was incorrectly applied to the wrong deposit. diff --git a/test/utils/utils.ts b/test/utils/utils.ts index d07a63c905..9271b72c6c 100644 --- a/test/utils/utils.ts +++ b/test/utils/utils.ts @@ -398,31 +398,12 @@ export async function fillV3Relay( const { blockNumber, transactionHash, transactionIndex, logIndex } = lastEvent!; return { - depositId: args.depositId, - originChainId: Number(args.originChainId), destinationChainId, - depositor: args.depositor, - recipient: args.recipient, - inputToken: args.inputToken, - inputAmount: args.inputAmount, - outputToken: args.outputToken, - outputAmount: args.outputAmount, - message: args.message, - fillDeadline: args.fillDeadline, - exclusivityDeadline: args.exclusivityDeadline, - exclusiveRelayer: args.exclusiveRelayer, - relayer: args.relayer, - repaymentChainId: Number(args.repaymentChainId), - relayExecutionInfo: { - updatedRecipient: args.relayExecutionInfo.updatedRecipient, - updatedMessage: args.relayExecutionInfo.updatedMessage, - updatedOutputAmount: args.relayExecutionInfo.updatedOutputAmount, - fillType: args.relayExecutionInfo.fillType, - }, blockNumber, transactionHash, transactionIndex, logIndex, + ...spreadEvent(args), }; } From 7f32c08f72a0666247de498b5c9e8cd241da71d9 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 14:14:32 -0500 Subject: [PATCH 14/62] Update SpokePoolUtils.ts --- test/utils/SpokePoolUtils.ts | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/test/utils/SpokePoolUtils.ts b/test/utils/SpokePoolUtils.ts index 76b13eac68..a31e45fd89 100644 --- a/test/utils/SpokePoolUtils.ts +++ b/test/utils/SpokePoolUtils.ts @@ -1,4 +1,4 @@ -import { Contract, bnZero } from "../../src/utils"; +import { Contract, bnZero, spreadEvent } from "../../src/utils"; import { interfaces } from "@across-protocol/sdk"; import { repaymentChainId } from "../constants"; import { SlowFillRequestWithBlock } from "../../src/interfaces"; @@ -57,31 +57,12 @@ export async function fillV3( ]); const lastEvent = events[events.length - 1]; const fillObject: interfaces.FillWithBlock = { - inputToken: lastEvent.args?.inputToken, - outputToken: lastEvent.args?.outputToken, - inputAmount: lastEvent.args?.inputAmount, - outputAmount: lastEvent.args?.outputAmount, - originChainId: lastEvent.args?.originChainId, - repaymentChainId: lastEvent.args?.repaymentChainId, - relayer: lastEvent.args?.relayer, - depositId: lastEvent.args?.depositId, - fillDeadline: lastEvent.args?.fillDeadline, - exclusivityDeadline: lastEvent.args?.exclusivityDeadline, - depositor: lastEvent.args?.depositor, - recipient: lastEvent.args?.recipient, - exclusiveRelayer: lastEvent.args?.exclusiveRelayer, - message: lastEvent.args?.message, - relayExecutionInfo: { - updatedRecipient: lastEvent.args?.updatedRecipient, - updatedMessage: lastEvent.args?.updatedMessage, - updatedOutputAmount: lastEvent.args?.updatedOutputAmount, - fillType: lastEvent.args?.fillType, - }, destinationChainId, blockNumber: lastEvent.blockNumber, transactionHash: lastEvent.transactionHash, logIndex: lastEvent.logIndex, transactionIndex: lastEvent.transactionIndex, + ...spreadEvent(lastEvent.args!) }; return fillObject; } @@ -113,23 +94,12 @@ export async function requestSlowFill( ]); const lastEvent = events[events.length - 1]; const requestObject: interfaces.SlowFillRequestWithBlock = { - inputToken: lastEvent.args?.inputToken, - outputToken: lastEvent.args?.outputToken, - inputAmount: lastEvent.args?.inputAmount, - outputAmount: lastEvent.args?.outputAmount, - originChainId: lastEvent.args?.originChainId, - depositId: lastEvent.args?.depositId, - fillDeadline: lastEvent.args?.fillDeadline, - exclusivityDeadline: lastEvent.args?.exclusivityDeadline, - depositor: lastEvent.args?.depositor, - recipient: lastEvent.args?.recipient, - exclusiveRelayer: lastEvent.args?.exclusiveRelayer, - message: lastEvent.args?.message, destinationChainId, blockNumber: lastEvent.blockNumber, transactionHash: lastEvent.transactionHash, logIndex: lastEvent.logIndex, transactionIndex: lastEvent.transactionIndex, + ...spreadEvent(lastEvent.args!) }; return requestObject; } From 9cd900722e20ad15a17d064d1358a85cb82c7dab Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 14:17:05 -0500 Subject: [PATCH 15/62] Update SpokePoolUtils.ts --- test/utils/SpokePoolUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/utils/SpokePoolUtils.ts b/test/utils/SpokePoolUtils.ts index a31e45fd89..b0c83f23d6 100644 --- a/test/utils/SpokePoolUtils.ts +++ b/test/utils/SpokePoolUtils.ts @@ -62,7 +62,7 @@ export async function fillV3( transactionHash: lastEvent.transactionHash, logIndex: lastEvent.logIndex, transactionIndex: lastEvent.transactionIndex, - ...spreadEvent(lastEvent.args!) + ...spreadEvent(lastEvent.args!), }; return fillObject; } @@ -99,7 +99,7 @@ export async function requestSlowFill( transactionHash: lastEvent.transactionHash, logIndex: lastEvent.logIndex, transactionIndex: lastEvent.transactionIndex, - ...spreadEvent(lastEvent.args!) + ...spreadEvent(lastEvent.args!), }; return requestObject; } From 284b24379dc3e34fb4b0658b8eee791f4b315b26 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 24 Jan 2025 15:52:08 -0500 Subject: [PATCH 16/62] Update Dataworker.loadData.prefill.ts --- test/Dataworker.loadData.prefill.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 8531cd5c21..f8a91c39f1 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -9,7 +9,7 @@ import { depositV3, ethers, expect, - fillV3, + fillV3Relay, getDefaultBlockRange, randomAddress, smock, @@ -69,7 +69,7 @@ describe("BundleDataClient: Pre-fill logic", async function () { // Mine fill, update spoke pool client to advance its latest block searched far enough such that the // fillStatuses() call and findFillEvent() queries work but ignore the fill event to force the Bundle Client // to query fresh for the fill status. - await fillV3(spokePoolClient_2.spokePool, relayer, deposit); + await fillV3Relay(spokePoolClient_2.spokePool, deposit, relayer, repaymentChainId); await spokePoolClient_2.update([]); expect(spokePoolClient_2.getFills().length).to.equal(0); From 68890bdd89b211e59769db5aeb024801f864e851 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Sun, 26 Jan 2025 00:39:18 -0500 Subject: [PATCH 17/62] Add tests for zero value deposits --- package.json | 2 +- test/Dataworker.loadData.fill.ts | 136 ++++++++++++++++++++++++++- test/Dataworker.loadData.slowFill.ts | 30 +++++- yarn.lock | 8 +- 4 files changed, 165 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 523e8b7b77..960467f382 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.2", + "@across-protocol/sdk": "^4.0.0-beta.3", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 8d8576f5d3..b9591e2861 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -26,9 +26,18 @@ import { } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { getCurrentTime, toBN, Event, toBNWei, fixedPointAdjustment, ZERO_ADDRESS, BigNumber } from "../src/utils"; +import { + getCurrentTime, + toBN, + Event, + toBNWei, + fixedPointAdjustment, + ZERO_ADDRESS, + BigNumber, + bnZero, +} from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; +import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; import { cloneDeep } from "lodash"; import { CombinedRefunds } from "../src/dataworker/DataworkerUtils"; import { INFINITE_FILL_DEADLINE } from "../src/common"; @@ -164,8 +173,9 @@ describe("Dataworker: Load data used in all functions", async function () { function generateV3Deposit(eventOverride?: Partial): Event { return mockOriginSpokePoolClient.depositV3({ inputToken: erc20_1.address, + inputAmount: eventOverride?.inputAmount ?? undefined, outputToken: eventOverride?.outputToken ?? erc20_2.address, - message: "0x", + message: eventOverride?.message ?? "0x", quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, destinationChainId, @@ -265,6 +275,126 @@ describe("Dataworker: Load data used in all functions", async function () { expect(emptyData.expiredDepositsToRefundV3).to.deep.equal({}); }); + it("Does not consider zero value deposits", async function () { + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + // Send unexpired and expired deposits in bundle block range. + generateV3Deposit({ + inputAmount: bnZero, + message: "0x", + }); + generateV3Deposit({ + fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1, + inputAmount: bnZero, + message: "0x", + }); + + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); + }); + + it("Does not consider expired zero value deposits from prior bundle", async function () { + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + // Send expired deposits + const priorBundleDeposit = generateV3Deposit({ + fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1, + inputAmount: bnZero, + message: "0x", + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [priorBundleDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); + }); + + it("Does not refund fills for zero value deposits", async function () { + generateV3Deposit({ + inputAmount: bnZero, + message: "0x", + }); + generateV3Deposit({ + inputAmount: bnZero, + message: "0x", + }); + + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + generateV3FillFromDeposit(deposits[0]); + generateV3FillFromDeposit({ + ...deposits[1], + message: sdkConstants.EMPTY_MESSAGE_HASH, + }); + + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(0); + }); + + it("Does not create unexecutable slow fill for zero value deposit", async function () { + generateV3Deposit({ + inputAmount: bnZero, + message: "0x", + }); + generateV3Deposit({ + inputAmount: bnZero, + message: "0x", + }); + + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + generateV3FillFromDeposit( + { + ...deposits[0], + }, + undefined, + undefined, + undefined, + interfaces.FillType.ReplacedSlowFill + ); + generateV3FillFromDeposit( + { + ...deposits[1], + message: sdkConstants.EMPTY_MESSAGE_HASH, + }, + undefined, + undefined, + undefined, + interfaces.FillType.ReplacedSlowFill + ); + + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.unexecutableSlowFills).to.deep.equal({}); + }); + it("Filters unexpired deposit out of block range", async function () { // Send deposit behind and after origin chain block range. Should not be included in bundleDeposits. // First generate mock deposit events with some block time between events. diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index cbda162fee..011633531a 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -27,9 +27,9 @@ import { } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { getCurrentTime, Event, toBNWei, assert, ZERO_ADDRESS } from "../src/utils"; +import { getCurrentTime, Event, toBNWei, assert, ZERO_ADDRESS, bnZero } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; +import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; import { cloneDeep } from "lodash"; import { INFINITE_FILL_DEADLINE } from "../src/common"; @@ -56,8 +56,9 @@ describe("BundleDataClient: Slow fill handling & validation", async function () function generateV3Deposit(eventOverride?: Partial): Event { return mockOriginSpokePoolClient.depositV3({ inputToken: erc20_1.address, + inputAmount: eventOverride?.inputAmount ?? undefined, outputToken: eventOverride?.outputToken ?? erc20_2.address, - message: "0x", + message: eventOverride?.message ?? "0x", quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, destinationChainId, @@ -1032,4 +1033,27 @@ describe("BundleDataClient: Slow fill handling & validation", async function () expect(data1.unexecutableSlowFills).to.deep.equal({}); expect(data1.bundleSlowFillsV3).to.deep.equal({}); }); + + it("Does not create slow fill for zero value deposit", async function () { + generateV3Deposit({ + inputAmount: bnZero, + message: "0x", + }); + generateV3Deposit({ + inputAmount: bnZero, + message: "0x", + }); + + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + generateSlowFillRequestFromDeposit(deposits[0]); + generateSlowFillRequestFromDeposit({ + ...deposits[1], + message: sdkConstants.EMPTY_MESSAGE_HASH, + }); + await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(getDefaultBlockRange(5), spokePoolClients); + + expect(data1.bundleSlowFillsV3).to.deep.equal({}); + }); }); diff --git a/yarn.lock b/yarn.lock index 31fda8ed97..3f5919bfe2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.2": - version "4.0.0-beta.2" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.2.tgz#3b82f5e7ecd6e244b8bad60a594325fbeb389970" - integrity sha512-S82gcrA4hT8PQ9nUDL8uNC2x+2pMwEob7xjDBrFqSu5UFQP751liyWOBcV0TKAis6jVZ1EJxI8E8pvEOMI3bEg== +"@across-protocol/sdk@^4.0.0-beta.3": + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.3.tgz#26411b7d82f490c902db6d968ce39ffdcc986fec" + integrity sha512-jzSc39hulNxwbXi16247+h1CYgpjRFIZwNgVa/oqsbeCs7yRLwOmYWCXpJ/pnATbhAGNJojQJyz14Hm742g0cg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 75e35bc1ed75c92c4a56010f8f2a6f3170b7448b Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 27 Jan 2025 13:35:59 -0500 Subject: [PATCH 18/62] Split up tests to speed up CI --- test/Dataworker.loadData.deposit.ts | 554 ++++++++++++++++++ test/Dataworker.loadData.fill.ts | 372 +----------- test/Dataworker.loadData.slowFill.ts | 367 +----------- ...ataworker.loadData.unexecutableSlowFill.ts | 537 +++++++++++++++++ 4 files changed, 1094 insertions(+), 736 deletions(-) create mode 100644 test/Dataworker.loadData.deposit.ts create mode 100644 test/Dataworker.loadData.unexecutableSlowFill.ts diff --git a/test/Dataworker.loadData.deposit.ts b/test/Dataworker.loadData.deposit.ts new file mode 100644 index 0000000000..61931ec532 --- /dev/null +++ b/test/Dataworker.loadData.deposit.ts @@ -0,0 +1,554 @@ +import { BundleDataClient, ConfigStoreClient, HubPoolClient, SpokePoolClient } from "../src/clients"; +import { amountToDeposit, destinationChainId, originChainId, repaymentChainId } from "./constants"; +import { setupDataworker } from "./fixtures/Dataworker.Fixture"; +import { + Contract, + FakeContract, + SignerWithAddress, + V3FillFromDeposit, + depositV3, + ethers, + expect, + getDefaultBlockRange, + getDisabledBlockRanges, + sinon, + smock, + spyLogIncludes, +} from "./utils"; + +import { Dataworker } from "../src/dataworker/Dataworker"; // Tested +import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS, BigNumber, bnZero } from "../src/utils"; +import { MockHubPoolClient, MockSpokePoolClient } from "./mocks"; +import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; + +let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; +let l1Token_1: Contract; +let depositor: SignerWithAddress, relayer: SignerWithAddress; + +let spokePoolClient_1: SpokePoolClient, spokePoolClient_2: SpokePoolClient, bundleDataClient: BundleDataClient; +let hubPoolClient: HubPoolClient, configStoreClient: ConfigStoreClient; +let dataworkerInstance: Dataworker; +let spokePoolClients: { [chainId: number]: SpokePoolClient }; + +let spy: sinon.SinonSpy; + +let updateAllClients: () => Promise; + +// TODO: Rename this file to BundleDataClient +describe("Dataworker: Load data used in all functions", async function () { + beforeEach(async function () { + ({ + spokePool_1, + erc20_1, + spokePool_2, + erc20_2, + configStoreClient, + hubPoolClient, + l1Token_1, + depositor, + relayer, + dataworkerInstance, + spokePoolClient_1, + spokePoolClient_2, + spokePoolClients, + updateAllClients, + spy, + } = await setupDataworker(ethers, 25, 25, 0)); + bundleDataClient = dataworkerInstance.clients.bundleDataClient; + }); + + describe("V3 Events", function () { + let mockOriginSpokePoolClient: MockSpokePoolClient, mockDestinationSpokePoolClient: MockSpokePoolClient; + let mockHubPoolClient: MockHubPoolClient; + let mockDestinationSpokePool: FakeContract; + const lpFeePct = toBNWei("0.01"); + + beforeEach(async function () { + await updateAllClients(); + mockHubPoolClient = new MockHubPoolClient( + hubPoolClient.logger, + hubPoolClient.hubPool, + configStoreClient, + hubPoolClient.deploymentBlock, + hubPoolClient.chainId + ); + // Mock a realized lp fee pct for each deposit so we can check refund amounts and bundle lp fees. + mockHubPoolClient.setDefaultRealizedLpFeePct(lpFeePct); + mockOriginSpokePoolClient = new MockSpokePoolClient( + spokePoolClient_1.logger, + spokePoolClient_1.spokePool, + spokePoolClient_1.chainId, + spokePoolClient_1.deploymentBlock + ); + mockDestinationSpokePool = await smock.fake(spokePoolClient_2.spokePool.interface); + mockDestinationSpokePoolClient = new MockSpokePoolClient( + spokePoolClient_2.logger, + mockDestinationSpokePool as Contract, + spokePoolClient_2.chainId, + spokePoolClient_2.deploymentBlock + ); + spokePoolClients = { + ...spokePoolClients, + [originChainId]: mockOriginSpokePoolClient, + [destinationChainId]: mockDestinationSpokePoolClient, + }; + await mockHubPoolClient.update(); + await mockOriginSpokePoolClient.update(); + await mockDestinationSpokePoolClient.update(); + mockHubPoolClient.setTokenMapping(l1Token_1.address, originChainId, erc20_1.address); + mockHubPoolClient.setTokenMapping(l1Token_1.address, destinationChainId, erc20_2.address); + mockHubPoolClient.setTokenMapping(l1Token_1.address, repaymentChainId, l1Token_1.address); + const bundleDataClient = new BundleDataClient( + dataworkerInstance.logger, + { + ...dataworkerInstance.clients.bundleDataClient.clients, + hubPoolClient: mockHubPoolClient as unknown as HubPoolClient, + }, + dataworkerInstance.clients.bundleDataClient.spokePoolClients, + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers + ); + dataworkerInstance = new Dataworker( + dataworkerInstance.logger, + { ...dataworkerInstance.clients, bundleDataClient }, + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers, + dataworkerInstance.maxRefundCountOverride, + dataworkerInstance.maxL1TokenCountOverride, + dataworkerInstance.blockRangeEndBlockBuffer + ); + }); + + function generateV3Deposit(eventOverride?: Partial): Event { + return mockOriginSpokePoolClient.depositV3({ + inputToken: erc20_1.address, + inputAmount: eventOverride?.inputAmount ?? undefined, + outputToken: eventOverride?.outputToken ?? erc20_2.address, + message: eventOverride?.message ?? "0x", + quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, + fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, + destinationChainId, + blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestBlockSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestBlockSearched gets set to the same value. + } as interfaces.DepositWithBlock); + } + + function generateV3FillFromDeposit( + deposit: interfaces.DepositWithBlock, + fillEventOverride?: Partial, + _relayer = relayer.address, + _repaymentChainId = repaymentChainId, + fillType = interfaces.FillType.FastFill + ): Event { + const fillObject = V3FillFromDeposit(deposit, _relayer, _repaymentChainId); + return mockDestinationSpokePoolClient.fillV3Relay({ + ...fillObject, + relayExecutionInfo: { + updatedRecipient: fillObject.updatedRecipient, + updatedMessage: fillObject.updatedMessage, + updatedOutputAmount: fillObject.updatedOutputAmount, + fillType, + }, + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestBlockSearched gets set to the same value. + } as interfaces.FillWithBlock); + } + + function generateV3FillFromDepositEvent( + depositEvent: Event, + fillEventOverride?: Partial, + _relayer = relayer.address, + _repaymentChainId = repaymentChainId, + fillType = interfaces.FillType.FastFill, + outputAmount: BigNumber = depositEvent.args.outputAmount, + updatedOutputAmount: BigNumber = depositEvent.args.outputAmount + ): Event { + const { args } = depositEvent; + return mockDestinationSpokePoolClient.fillV3Relay({ + ...args, + relayer: _relayer, + outputAmount, + repaymentChainId: _repaymentChainId, + relayExecutionInfo: { + updatedRecipient: depositEvent.updatedRecipient, + updatedMessage: depositEvent.updatedMessage, + updatedOutputAmount: updatedOutputAmount, + fillType, + }, + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestBlockSearched gets set to the same value. + } as interfaces.FillWithBlock); + } + + it("Filters expired deposits", async function () { + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + // Send unexpired deposit + generateV3Deposit(); + // Send expired deposit + const expiredDeposits = [generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 })]; + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); + expect( + data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].map((deposit) => deposit.depositId) + ).to.deep.equal(expiredDeposits.map((event) => event.args.depositId)); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + }); + + it("Ignores disabled chains", async function () { + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + // Send unexpired deposit + generateV3Deposit(); + // Send expired deposit + generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + // Returns no data if block range is undefined + const emptyData = await dataworkerInstance.clients.bundleDataClient.loadData( + getDisabledBlockRanges(), + spokePoolClients + ); + expect(emptyData.bundleDepositsV3).to.deep.equal({}); + expect(emptyData.expiredDepositsToRefundV3).to.deep.equal({}); + }); + + it("Does not consider zero value deposits", async function () { + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + // Send unexpired and expired deposits in bundle block range. + generateV3Deposit({ + inputAmount: bnZero, + message: "0x", + }); + generateV3Deposit({ + fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1, + inputAmount: bnZero, + message: "0x", + }); + + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); + }); + + it("Does not consider expired zero value deposits from prior bundle", async function () { + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + // Send expired deposits + const priorBundleDeposit = generateV3Deposit({ + fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1, + inputAmount: bnZero, + message: "0x", + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [priorBundleDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); + }); + + it("Filters unexpired deposit out of block range", async function () { + // Send deposit behind and after origin chain block range. Should not be included in bundleDeposits. + // First generate mock deposit events with some block time between events. + const deposits = [ + generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1 }), + generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11 }), + generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 21 }), + ]; + // Create a block range that contains only the middle deposit. + const originChainBlockRange = [deposits[1].blockNumber - 1, deposits[1].blockNumber + 1]; + // Substitute origin chain bundle block range. + const bundleBlockRanges = [originChainBlockRange].concat(getDefaultBlockRange(5).slice(1)); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + expect(mockOriginSpokePoolClient.getDeposits().length).to.equal(deposits.length); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address][0].depositId).to.equal(deposits[1].args.depositId); + }); + it("Includes duplicate deposits in bundle data", async function () { + generateV3Deposit(); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const depositToDuplicate = mockOriginSpokePoolClient.getDeposits()[0]; + mockOriginSpokePoolClient.depositV3(depositToDuplicate); + mockOriginSpokePoolClient.depositV3(depositToDuplicate); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + expect( + mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId).length + ).to.equal(3); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + }); + it("Filters duplicate deposits out of block range", async function () { + const deposit = generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const depositToDuplicate = mockOriginSpokePoolClient.getDeposits()[0]; + const duplicateDeposit = mockOriginSpokePoolClient.depositV3({ + ...depositToDuplicate, + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + expect( + mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId).length + ).to.equal(2); + const originChainBlockRange = [deposit.blockNumber, duplicateDeposit.blockNumber - 1]; + // Substitute origin chain bundle block range. + const bundleBlockRanges = [originChainBlockRange].concat(getDefaultBlockRange(5).slice(1)); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); + }); + it("Ignores expired deposits that were filled in same bundle", async function () { + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + // Send deposit that expires in this bundle. + const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + + // Now, send a fill for the deposit that would be in the same bundle. This should eliminate the expired + // deposit from a refund. + generateV3FillFromDepositEvent(expiredDeposit); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + const data2 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(6), + spokePoolClients + ); + expect(data2.expiredDepositsToRefundV3).to.deep.equal({}); + expect(data2.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + }); + + it("Returns prior bundle expired deposits", async function () { + // Send deposit that expires in this bundle. + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + // Send deposit that expires in this bundle. + const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + + // Now, load a bundle that doesn't include the deposit in its range. + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data2 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + // Now, there is no bundle deposit but still an expired deposit to refund. + expect(data2.bundleDepositsV3).to.deep.equal({}); + expect(data2.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + }); + it("Includes duplicate prior bundle expired deposits", async function () { + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + + // Send deposit that expires in this bundle and send duplicate at same block. + const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + + // Now, load a bundle that doesn't include the deposits in its range. + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data2 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + // Now, there is no bundle deposit but still two expired deposits to refund. + expect(data2.bundleDepositsV3).to.deep.equal({}); + expect(data2.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + }); + it("Handles when deposit is greater than origin bundle end block but fill is within range", async function () { + // Send deposit after origin chain block range. + const blockRanges = getDefaultBlockRange(5); + const futureDeposit = generateV3Deposit(); + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + blockRanges[originChainIndex] = [blockRanges[0][0], futureDeposit.blockNumber - 1]; + + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + generateV3FillFromDepositEvent(futureDeposit); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(blockRanges, spokePoolClients); + expect(data1.bundleDepositsV3).to.deep.equal({}); + + // Fill should not be included since we cannot validate fills when the deposit is in a following bundle. + // This fill is considered a "pre-fill" and will be validated when the deposit is included in a bundle. + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spyLogIncludes(spy, -2, "invalid V3 fills in range")).to.be.true; + }); + it("Does not count prior bundle expired deposits that were filled", async function () { + // Send deposit that expires in this bundle. + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + // Let's make fill status for the relay hash always return Filled. + const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); + mockDestinationSpokePool.fillStatuses.whenCalledWith(expiredDepositHash).returns(interfaces.FillStatus.Filled); + + // Now, load a bundle that doesn't include the deposit in its range. + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + // There should be no expired deposit to refund because its fill status is Filled. + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); + }); + it("Does not count prior bundle expired deposits that we queried a fill for", async function () { + // Send deposit that expires in this bundle. + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Unlike previous test, we send a fill that the spoke pool client should query which also eliminates this + // expired deposit from being refunded. + generateV3FillFromDeposit(deposits[0]); + await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill", "FilledV3Relay"]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); + + // Now, load a bundle that doesn't include the deposit in its range. + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + // There should be no expired deposit to refund. + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + }); + }); + + describe("Miscellaneous functions", function () { + it("getUpcomingDepositAmount", async function () { + // Send two deposits on different chains + await depositV3( + spokePool_1, + destinationChainId, + depositor, + erc20_1.address, + amountToDeposit, + ZERO_ADDRESS, + amountToDeposit + ); + await depositV3( + spokePool_2, + originChainId, + depositor, + erc20_2.address, + amountToDeposit, + ZERO_ADDRESS, + amountToDeposit + ); + await updateAllClients(); + expect(await bundleDataClient.getUpcomingDepositAmount(originChainId, erc20_1.address, 0)).to.equal( + amountToDeposit + ); + expect(await bundleDataClient.getUpcomingDepositAmount(destinationChainId, erc20_2.address, 0)).to.equal( + amountToDeposit + ); + + // Removes deposits using block, token, and chain filters. + expect( + await bundleDataClient.getUpcomingDepositAmount( + originChainId, + erc20_1.address, + spokePoolClient_1.latestBlockSearched // block higher than the deposit + ) + ).to.equal(0); + expect( + await bundleDataClient.getUpcomingDepositAmount( + originChainId, + erc20_2.address, // diff token + 0 + ) + ).to.equal(0); + expect( + await bundleDataClient.getUpcomingDepositAmount( + destinationChainId, // diff chain + erc20_1.address, + 0 + ) + ).to.equal(0); + + // spoke pool client for chain not defined + expect( + await bundleDataClient.getUpcomingDepositAmount( + originChainId + destinationChainId + repaymentChainId + 1, // spoke pool client for chain is not defined in BundleDataClient + erc20_1.address, + 0 + ) + ).to.equal(0); + }); + }); +}); diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index f305c4fcd9..067e804acb 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -37,7 +37,7 @@ import { bnZero, } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; +import { interfaces, constants as sdkConstants } from "@across-protocol/sdk"; import { cloneDeep } from "lodash"; import { CombinedRefunds } from "../src/dataworker/DataworkerUtils"; import { INFINITE_FILL_DEADLINE } from "../src/common"; @@ -231,101 +231,6 @@ describe("Dataworker: Load data used in all functions", async function () { } as interfaces.FillWithBlock); } - it("Filters expired deposits", async function () { - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - // Send unexpired deposit - generateV3Deposit(); - // Send expired deposit - const expiredDeposits = [generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 })]; - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); - expect( - data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].map((deposit) => deposit.depositId) - ).to.deep.equal(expiredDeposits.map((event) => event.args.depositId)); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - }); - - it("Ignores disabled chains", async function () { - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - // Send unexpired deposit - generateV3Deposit(); - // Send expired deposit - generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - // Returns no data if block range is undefined - const emptyData = await dataworkerInstance.clients.bundleDataClient.loadData( - getDisabledBlockRanges(), - spokePoolClients - ); - expect(emptyData.bundleDepositsV3).to.deep.equal({}); - expect(emptyData.expiredDepositsToRefundV3).to.deep.equal({}); - }); - - it("Does not consider zero value deposits", async function () { - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - // Send unexpired and expired deposits in bundle block range. - generateV3Deposit({ - inputAmount: bnZero, - message: "0x", - }); - generateV3Deposit({ - fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1, - inputAmount: bnZero, - message: "0x", - }); - - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - - expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); - }); - - it("Does not consider expired zero value deposits from prior bundle", async function () { - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - // Send expired deposits - const priorBundleDeposit = generateV3Deposit({ - fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1, - inputAmount: bnZero, - message: "0x", - }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; - const bundleBlockRanges = getDefaultBlockRange(5); - bundleBlockRanges[originChainIndex] = [priorBundleDeposit.blockNumber + 1, oldOriginChainToBlock]; - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - - expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); - }); - it("Does not refund fills for zero value deposits", async function () { generateV3Deposit({ inputAmount: bnZero, @@ -394,86 +299,6 @@ describe("Dataworker: Load data used in all functions", async function () { expect(data1.unexecutableSlowFills).to.deep.equal({}); }); - - it("Filters unexpired deposit out of block range", async function () { - // Send deposit behind and after origin chain block range. Should not be included in bundleDeposits. - // First generate mock deposit events with some block time between events. - const deposits = [ - generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1 }), - generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11 }), - generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 21 }), - ]; - // Create a block range that contains only the middle deposit. - const originChainBlockRange = [deposits[1].blockNumber - 1, deposits[1].blockNumber + 1]; - // Substitute origin chain bundle block range. - const bundleBlockRanges = [originChainBlockRange].concat(getDefaultBlockRange(5).slice(1)); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - expect(mockOriginSpokePoolClient.getDeposits().length).to.equal(deposits.length); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address][0].depositId).to.equal(deposits[1].args.depositId); - }); - it("Includes duplicate deposits in bundle data", async function () { - generateV3Deposit(); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const depositToDuplicate = mockOriginSpokePoolClient.getDeposits()[0]; - mockOriginSpokePoolClient.depositV3(depositToDuplicate); - mockOriginSpokePoolClient.depositV3(depositToDuplicate); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - expect( - mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId).length - ).to.equal(3); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); - }); - it("Filters duplicate deposits out of block range", async function () { - const deposit = generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const depositToDuplicate = mockOriginSpokePoolClient.getDeposits()[0]; - const duplicateDeposit = mockOriginSpokePoolClient.depositV3({ - ...depositToDuplicate, - blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, - }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - expect( - mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId).length - ).to.equal(2); - const originChainBlockRange = [deposit.blockNumber, duplicateDeposit.blockNumber - 1]; - // Substitute origin chain bundle block range. - const bundleBlockRanges = [originChainBlockRange].concat(getDefaultBlockRange(5).slice(1)); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); - }); - it("Ignores expired deposits that were filled in same bundle", async function () { - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - // Send deposit that expires in this bundle. - const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - - // Now, send a fill for the deposit that would be in the same bundle. This should eliminate the expired - // deposit from a refund. - generateV3FillFromDepositEvent(expiredDeposit); - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); - const data2 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(6), - spokePoolClients - ); - expect(data2.expiredDepositsToRefundV3).to.deep.equal({}); - expect(data2.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - }); it("Saves V3 fast fill under correct repayment chain and repayment token", async function () { const depositV3Events: Event[] = []; const fillV3Events: Event[] = []; @@ -854,141 +679,6 @@ describe("Dataworker: Load data used in all functions", async function () { expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); }); - it("Returns prior bundle expired deposits", async function () { - // Send deposit that expires in this bundle. - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - // Send deposit that expires in this bundle. - const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - - // Now, load a bundle that doesn't include the deposit in its range. - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; - const bundleBlockRanges = getDefaultBlockRange(5); - bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; - const data2 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - - // Now, there is no bundle deposit but still an expired deposit to refund. - expect(data2.bundleDepositsV3).to.deep.equal({}); - expect(data2.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - }); - it("Includes duplicate prior bundle expired deposits", async function () { - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - - // Send deposit that expires in this bundle and send duplicate at same block. - const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); - - // Now, load a bundle that doesn't include the deposits in its range. - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; - const bundleBlockRanges = getDefaultBlockRange(5); - bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; - const data2 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - - // Now, there is no bundle deposit but still two expired deposits to refund. - expect(data2.bundleDepositsV3).to.deep.equal({}); - expect(data2.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); - }); - it("Handles when deposit is greater than origin bundle end block but fill is within range", async function () { - // Send deposit after origin chain block range. - const blockRanges = getDefaultBlockRange(5); - const futureDeposit = generateV3Deposit(); - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - blockRanges[originChainIndex] = [blockRanges[0][0], futureDeposit.blockNumber - 1]; - - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - generateV3FillFromDepositEvent(futureDeposit); - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); - - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(blockRanges, spokePoolClients); - expect(data1.bundleDepositsV3).to.deep.equal({}); - - // Fill should not be included since we cannot validate fills when the deposit is in a following bundle. - // This fill is considered a "pre-fill" and will be validated when the deposit is included in a bundle. - expect(data1.bundleFillsV3).to.deep.equal({}); - expect(spyLogIncludes(spy, -2, "invalid V3 fills in range")).to.be.true; - }); - it("Does not count prior bundle expired deposits that were filled", async function () { - // Send deposit that expires in this bundle. - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - // Let's make fill status for the relay hash always return Filled. - const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); - mockDestinationSpokePool.fillStatuses.whenCalledWith(expiredDepositHash).returns(interfaces.FillStatus.Filled); - - // Now, load a bundle that doesn't include the deposit in its range. - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; - const bundleBlockRanges = getDefaultBlockRange(5); - bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - - // There should be no expired deposit to refund because its fill status is Filled. - expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); - }); - it("Does not count prior bundle expired deposits that we queried a fill for", async function () { - // Send deposit that expires in this bundle. - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDeposits(); - - // Unlike previous test, we send a fill that the spoke pool client should query which also eliminates this - // expired deposit from being refunded. - generateV3FillFromDeposit(deposits[0]); - await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill", "FilledV3Relay"]); - expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); - - // Now, load a bundle that doesn't include the deposit in its range. - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; - const bundleBlockRanges = getDefaultBlockRange(5); - bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - - // There should be no expired deposit to refund. - expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - }); - it("getBundleTimestampsFromCache and setBundleTimestampsInCache", async function () { // Unit test await dataworkerInstance.clients.bundleDataClient.loadData(getDefaultBlockRange(5), spokePoolClients); @@ -1010,66 +700,6 @@ describe("Dataworker: Load data used in all functions", async function () { }); describe("Miscellaneous functions", function () { - it("getUpcomingDepositAmount", async function () { - // Send two deposits on different chains - await depositV3( - spokePool_1, - destinationChainId, - depositor, - erc20_1.address, - amountToDeposit, - ZERO_ADDRESS, - amountToDeposit - ); - await depositV3( - spokePool_2, - originChainId, - depositor, - erc20_2.address, - amountToDeposit, - ZERO_ADDRESS, - amountToDeposit - ); - await updateAllClients(); - expect(await bundleDataClient.getUpcomingDepositAmount(originChainId, erc20_1.address, 0)).to.equal( - amountToDeposit - ); - expect(await bundleDataClient.getUpcomingDepositAmount(destinationChainId, erc20_2.address, 0)).to.equal( - amountToDeposit - ); - - // Removes deposits using block, token, and chain filters. - expect( - await bundleDataClient.getUpcomingDepositAmount( - originChainId, - erc20_1.address, - spokePoolClient_1.latestBlockSearched // block higher than the deposit - ) - ).to.equal(0); - expect( - await bundleDataClient.getUpcomingDepositAmount( - originChainId, - erc20_2.address, // diff token - 0 - ) - ).to.equal(0); - expect( - await bundleDataClient.getUpcomingDepositAmount( - destinationChainId, // diff chain - erc20_1.address, - 0 - ) - ).to.equal(0); - - // spoke pool client for chain not defined - expect( - await bundleDataClient.getUpcomingDepositAmount( - originChainId + destinationChainId + repaymentChainId + 1, // spoke pool client for chain is not defined in BundleDataClient - erc20_1.address, - 0 - ) - ).to.equal(0); - }); it("getApproximateRefundsForBlockRange", async function () { // Send two deposits on different chains // Fill both deposits and request repayment on same chain diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index 6addbdca6c..b4c57ccd4c 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -15,10 +15,8 @@ import { depositV3, ethers, expect, - fillV3Relay, getDefaultBlockRange, getDisabledBlockRanges, - mineRandomBlocks, randomAddress, requestSlowFill, sinon, @@ -27,7 +25,7 @@ import { } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { getCurrentTime, Event, toBNWei, assert, ZERO_ADDRESS, bnZero } from "../src/utils"; +import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS, bnZero } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; import { cloneDeep } from "lodash"; @@ -182,194 +180,6 @@ describe("BundleDataClient: Slow fill handling & validation", async function () ); }); - it("Filters for fast fills replacing slow fills from older bundles", async function () { - // Generate a deposit that cannot be slow filled, to test that its ignored as a slow fill excess. - // Generate a second deposit that can be slow filled but will be slow filled in an older bundle - // Generate a third deposit that does get slow filled but the slow fill is not "seen" by the client. - const depositWithMissingSlowFillRequest = await depositV3( - spokePool_1, - destinationChainId, - depositor, - erc20_1.address, - amountToDeposit, - erc20_2.address, - amountToDeposit - ); - await requestSlowFill(spokePool_2, relayer, depositWithMissingSlowFillRequest); - const missingSlowFillRequestBlock = await spokePool_2.provider.getBlockNumber(); - await mineRandomBlocks(); - - const depositsWithSlowFillRequests = [ - await depositV3( - spokePool_1, - destinationChainId, - depositor, - erc20_1.address, - amountToDeposit, - erc20_1.address, - amountToDeposit - ), - await depositV3( - spokePool_1, - destinationChainId, - depositor, - erc20_1.address, - amountToDeposit, - erc20_2.address, - amountToDeposit - ), - ]; - - await spokePoolClient_1.update(); - const deposits = spokePoolClient_1.getDeposits(); - expect(deposits.length).to.equal(3); - const eligibleSlowFills = depositsWithSlowFillRequests.filter((x) => erc20_2.address === x.outputToken); - const ineligibleSlowFills = depositsWithSlowFillRequests.filter((x) => erc20_2.address !== x.outputToken); - - // Generate slow fill requests for the slow fill-eligible deposits - await requestSlowFill(spokePool_2, relayer, eligibleSlowFills[0]); - await requestSlowFill(spokePool_2, relayer, ineligibleSlowFills[0]); - const lastSlowFillRequestBlock = await spokePool_2.provider.getBlockNumber(); - await mineRandomBlocks(); - - // Now, generate fast fills replacing slow fills for all deposits. - await fillV3Relay(spokePool_2, deposits[0], relayer, repaymentChainId); - await fillV3Relay(spokePool_2, deposits[1], relayer, repaymentChainId); - await fillV3Relay(spokePool_2, deposits[2], relayer, repaymentChainId); - - // Construct a spoke pool client with a small search range that would not include the first fill. - spokePoolClient_2.firstBlockToSearch = missingSlowFillRequestBlock + 1; - spokePoolClient_2.eventSearchConfig.fromBlock = spokePoolClient_2.firstBlockToSearch; - - // There should be one "missing" slow fill request. - await spokePoolClient_2.update(); - const fills = spokePoolClient_2.getFills(); - expect(fills.length).to.equal(3); - const slowFillRequests = spokePoolClient_2.getSlowFillRequestsForOriginChain(originChainId); - expect(slowFillRequests.length).to.equal(2); - assert( - fills.every((x) => x.relayExecutionInfo.fillType === interfaces.FillType.ReplacedSlowFill), - "All fills should be replaced slow fills" - ); - assert( - fills.every((x) => x.blockNumber > lastSlowFillRequestBlock), - "Fills should be later than slow fill request" - ); - - // Create a block range that would make the slow fill requests appear to be in an "older" bundle. - const destinationChainBlockRange = [lastSlowFillRequestBlock + 1, getDefaultBlockRange(5)[0][1]]; - // Substitute destination chain bundle block range. - const bundleBlockRanges = getDefaultBlockRange(5); - const destinationChainIndex = - dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); - bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, { - ...spokePoolClients, - [originChainId]: spokePoolClient_1, - [destinationChainId]: spokePoolClient_2, - }); - - // All fills and deposits are valid - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(3); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); - - // There are two "unexecutable slow fills" because there are two deposits that have "equivalent" input - // and output tokens AND: - // - one slow fill request does not get seen by the spoke pool client - // - one slow fill request is in an older bundle - expect(data1.unexecutableSlowFills[destinationChainId][erc20_2.address].length).to.equal(2); - expect( - data1.unexecutableSlowFills[destinationChainId][erc20_2.address].map((x) => x.depositId).sort() - ).to.deep.equal([depositWithMissingSlowFillRequest.depositId, eligibleSlowFills[0].depositId].sort()); - }); - - it("Handles fast fills replacing invalid slow fill request from older bundles", async function () { - // Create a Lite chain to test that slow fill requests involving lite chains are ignored. - mockConfigStore.updateGlobalConfig( - GLOBAL_CONFIG_STORE_KEYS.LITE_CHAIN_ID_INDICES, - JSON.stringify([mockOriginSpokePoolClient.chainId]) - ); - await mockConfigStore.update(); - (spokePoolClient_1 as any).configStoreClient = mockConfigStore; - (spokePoolClient_2 as any).configStoreClient = mockConfigStore; - - // Generate a deposit that cannot be slow filled, to test that its ignored as a slow fill excess. - // - first deposit is FROM lite chain - // - second deposit is TO lite chain - const depositsWithSlowFillRequests = [ - await depositV3( - spokePool_1, - destinationChainId, - depositor, - erc20_1.address, - amountToDeposit, - erc20_2.address, - amountToDeposit - ), - await depositV3( - spokePool_2, - originChainId, - depositor, - erc20_2.address, - amountToDeposit, - erc20_1.address, - amountToDeposit - ), - ]; - - await spokePoolClient_1.update(); - await spokePoolClient_2.update(); - const originChainDeposit = spokePoolClient_1.getDeposits()[0]; - const destinationChainDeposit = spokePoolClient_2.getDeposits()[0]; - - // Generate slow fill requests for the slow fill-eligible deposits - await requestSlowFill(spokePool_2, relayer, depositsWithSlowFillRequests[0]); - await requestSlowFill(spokePool_1, relayer, depositsWithSlowFillRequests[1]); - const lastDestinationChainSlowFillRequestBlock = await spokePool_2.provider.getBlockNumber(); - const lastOriginChainSlowFillRequestBlock = await spokePool_2.provider.getBlockNumber(); - - await mineRandomBlocks(); - - // Now, generate fast fills replacing slow fills for all deposits. - await fillV3Relay(spokePool_2, originChainDeposit, relayer, repaymentChainId); - await fillV3Relay(spokePool_1, destinationChainDeposit, relayer, repaymentChainId); - - await spokePoolClient_1.update(); - await spokePoolClient_2.update(); - assert( - spokePoolClient_2.getFills().every((x) => x.relayExecutionInfo.fillType === interfaces.FillType.ReplacedSlowFill), - "All fills should be replaced slow fills" - ); - assert( - spokePoolClient_1.getFills().every((x) => x.relayExecutionInfo.fillType === interfaces.FillType.ReplacedSlowFill), - "All fills should be replaced slow fills" - ); - - // Create a block range that would make the slow fill requests appear to be in an "older" bundle. - const destinationChainBlockRange = [lastDestinationChainSlowFillRequestBlock + 1, getDefaultBlockRange(5)[0][1]]; - const originChainBlockRange = [lastOriginChainSlowFillRequestBlock + 1, getDefaultBlockRange(5)[0][1]]; - // Substitute destination chain bundle block range. - const bundleBlockRanges = getDefaultBlockRange(5); - const destinationChainIndex = - dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); - bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - bundleBlockRanges[originChainIndex] = originChainBlockRange; - - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, { - ...spokePoolClients, - [originChainId]: spokePoolClient_1, - [destinationChainId]: spokePoolClient_2, - }); - - // All fills are valid. Note, origin chain deposit must take repayment on origin chain. - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(data1.bundleFillsV3[originChainId][erc20_1.address].fills.length).to.equal(1); - - // There are zero "unexecutable slow fills" because the slow fill requests in an older bundle are invalid - expect(data1.unexecutableSlowFills).to.deep.equal({}); - }); - it("Saves valid slow fill requests under destination chain and token", async function () { // Only one deposit is eligible to be slow filled because its input and output tokens are equivalent. generateV3Deposit({ outputToken: randomAddress() }); @@ -388,7 +198,7 @@ describe("BundleDataClient: Slow fill handling & validation", async function () ); }); - it("Ignores disabled chains", async function () { + it("Ignores disabled chains for slow fill requests", async function () { // Only one deposit is eligible to be slow filled because its input and output tokens are equivalent. generateV3Deposit({ outputToken: randomAddress() }); generateV3Deposit({ outputToken: erc20_2.address }); @@ -430,39 +240,6 @@ describe("BundleDataClient: Slow fill handling & validation", async function () expect(data1.unexecutableSlowFills).to.deep.equal({}); }); - it("Replacing a slow fill request with a fast fill in same bundle doesn't create unexecutable slow fill", async function () { - generateV3Deposit({ outputToken: erc20_2.address }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDeposits(); - - generateSlowFillRequestFromDeposit(deposits[0]); - generateV3FillFromDeposit(deposits[0], undefined, undefined, undefined, interfaces.FillType.ReplacedSlowFill); - await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill", "FilledV3Relay"]); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(getDefaultBlockRange(5), spokePoolClients); - - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(data1.bundleSlowFillsV3).to.deep.equal({}); - expect(data1.unexecutableSlowFills).to.deep.equal({}); - }); - - it("Ignores disabled chains", async function () { - generateV3Deposit({ outputToken: erc20_2.address }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDeposits(); - - generateSlowFillRequestFromDeposit(deposits[0]); - generateV3FillFromDeposit(deposits[0], undefined, undefined, undefined, interfaces.FillType.ReplacedSlowFill); - await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill", "FilledV3Relay"]); - - const emptyData = await dataworkerInstance.clients.bundleDataClient.loadData( - getDisabledBlockRanges(), - spokePoolClients - ); - expect(emptyData.unexecutableSlowFills).to.deep.equal({}); - expect(emptyData.bundleFillsV3).to.deep.equal({}); - expect(emptyData.bundleSlowFillsV3).to.deep.equal({}); - }); - it("Handles slow fill requests out of block range", async function () { generateV3Deposit({ outputToken: erc20_2.address, @@ -894,146 +671,6 @@ describe("BundleDataClient: Slow fill handling & validation", async function () expect(data1.bundleDepositsV3).to.deep.equal({}); }); - it("Adds prior bundle expired deposits that requested a slow fill in a prior bundle to unexecutable slow fills", async function () { - // Send deposit that expires in this bundle. - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - // Let's make fill status for the relay hash always return RequestedSlowFill. - const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); - mockDestinationSpokePool.fillStatuses - .whenCalledWith(expiredDepositHash) - .returns(interfaces.FillStatus.RequestedSlowFill); - - // Now, load a bundle that doesn't include the deposit in its range. - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; - const bundleBlockRanges = getDefaultBlockRange(5); - bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - - // Now, there is no bundle deposit but still an expired deposit to refund. - // There is also an unexecutable slow fill. - expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.unexecutableSlowFills[destinationChainId][erc20_2.address].length).to.equal(1); - }); - - it("Does not add prior bundle expired lite chain deposits that requested a slow fill in a prior bundle to unexecutable slow fills", async function () { - mockConfigStore.updateGlobalConfig( - GLOBAL_CONFIG_STORE_KEYS.LITE_CHAIN_ID_INDICES, - JSON.stringify([mockOriginSpokePoolClient.chainId]) - ); - await mockConfigStore.update(); - (mockOriginSpokePoolClient as any).configStoreClient = mockConfigStore; - (mockDestinationSpokePool as any).configStoreClient = mockConfigStore; - const updateEventTimestamp = mockConfigStore.liteChainIndicesUpdates[0].timestamp; - - // Send lite chain deposit that expires in this bundle. - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - const expiredDeposit = generateV3Deposit({ - fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1, - quoteTimestamp: updateEventTimestamp, - }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - const deposit = mockOriginSpokePoolClient.getDeposits()[0]; - assert(deposit.fromLiteChain, "Deposit should be from lite chain"); - - // Let's make fill status for the relay hash always return RequestedSlowFill. - const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); - mockDestinationSpokePool.fillStatuses - .whenCalledWith(expiredDepositHash) - .returns(interfaces.FillStatus.RequestedSlowFill); - - // Now, load a bundle that doesn't include the deposit in its range. - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; - const bundleBlockRanges = getDefaultBlockRange(5); - bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - - // Now, there is no bundle deposit but still an expired deposit to refund. - // There is NOT an unexecutable slow fill. - expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.unexecutableSlowFills).to.deep.equal({}); - }); - - it("Does not add prior bundle expired deposits that did not request a slow fill in a prior bundle to unexecutable slow fills", async function () { - // Send deposit that expires in this bundle. - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - // Let's make fill status for the relay hash always return Unfilled. - const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); - mockDestinationSpokePool.fillStatuses.whenCalledWith(expiredDepositHash).returns(interfaces.FillStatus.Unfilled); - - // Now, load a bundle that doesn't include the deposit in its range. - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; - const bundleBlockRanges = getDefaultBlockRange(5); - bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - - // Now, there is no bundle deposit but still an expired deposit to refund. - // There is also an unexecutable slow fill. - expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.unexecutableSlowFills).to.deep.equal({}); - }); - - it("Does not add unexecutable slow fill for prior bundle expired deposits that requested a slow fill if slow fill request is in current bundle", async function () { - // Send deposit that expires in this bundle. - const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( - [originChainId, destinationChainId], - getDefaultBlockRange(5), - spokePoolClients - ); - const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - - // If the slow fill request took place in the current bundle, then it is not marked as unexecutable since - // it would not have produced a slow fill request. - const deposit = mockOriginSpokePoolClient.getDeposits()[0]; - generateSlowFillRequestFromDeposit(deposit); - await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); - - // Let's make fill status for the relay hash always return RequestedSlowFill. - const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); - mockDestinationSpokePool.fillStatuses - .whenCalledWith(expiredDepositHash) - .returns(interfaces.FillStatus.RequestedSlowFill); - - // Now, load a bundle that doesn't include the deposit in its range. - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; - const bundleBlockRanges = getDefaultBlockRange(5); - bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - - // Now, there is no bundle deposit but still an expired deposit to refund. - // There is also no unexecutable slow fill because the slow fill request was sent in this bundle. - expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.unexecutableSlowFills).to.deep.equal({}); - expect(data1.bundleSlowFillsV3).to.deep.equal({}); - }); - it("Does not create slow fill for zero value deposit", async function () { generateV3Deposit({ inputAmount: bnZero, diff --git a/test/Dataworker.loadData.unexecutableSlowFill.ts b/test/Dataworker.loadData.unexecutableSlowFill.ts new file mode 100644 index 0000000000..04f7891c5a --- /dev/null +++ b/test/Dataworker.loadData.unexecutableSlowFill.ts @@ -0,0 +1,537 @@ +import { + BundleDataClient, + ConfigStoreClient, + GLOBAL_CONFIG_STORE_KEYS, + HubPoolClient, + SpokePoolClient, +} from "../src/clients"; +import { amountToDeposit, destinationChainId, originChainId, repaymentChainId } from "./constants"; +import { setupDataworker } from "./fixtures/Dataworker.Fixture"; +import { + Contract, + FakeContract, + SignerWithAddress, + V3FillFromDeposit, + depositV3, + ethers, + expect, + fillV3Relay, + getDefaultBlockRange, + getDisabledBlockRanges, + mineRandomBlocks, + requestSlowFill, + smock, +} from "./utils"; + +import { Dataworker } from "../src/dataworker/Dataworker"; // Tested +import { getCurrentTime, Event, toBNWei, assert, ZERO_ADDRESS } from "../src/utils"; +import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; +import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; + +describe("BundleDataClient: Expired deposit and Slow Fill interactions", async function () { + let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; + let l1Token_1: Contract; + let depositor: SignerWithAddress, relayer: SignerWithAddress; + + let spokePoolClient_1: SpokePoolClient, spokePoolClient_2: SpokePoolClient; + let hubPoolClient: HubPoolClient, configStoreClient: ConfigStoreClient; + let dataworkerInstance: Dataworker; + let spokePoolClients: { [chainId: number]: SpokePoolClient }; + + let updateAllClients: () => Promise; + + let mockOriginSpokePoolClient: MockSpokePoolClient, mockDestinationSpokePoolClient: MockSpokePoolClient; + let mockHubPoolClient: MockHubPoolClient; + let mockDestinationSpokePool: FakeContract; + let mockConfigStore: MockConfigStoreClient; + const lpFeePct = toBNWei("0.01"); + + function generateV3Deposit(eventOverride?: Partial): Event { + return mockOriginSpokePoolClient.depositV3({ + inputToken: erc20_1.address, + inputAmount: eventOverride?.inputAmount ?? undefined, + outputToken: eventOverride?.outputToken ?? erc20_2.address, + message: eventOverride?.message ?? "0x", + quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, + fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, + destinationChainId, + blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestBlockSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestBlockSearched gets set to the same value. + } as interfaces.DepositWithBlock); + } + + function generateV3FillFromDeposit( + deposit: interfaces.DepositWithBlock, + fillEventOverride?: Partial, + _relayer = relayer.address, + _repaymentChainId = repaymentChainId, + fillType = interfaces.FillType.FastFill + ): Event { + const fillObject = V3FillFromDeposit(deposit, _relayer, _repaymentChainId); + return mockDestinationSpokePoolClient.fillV3Relay({ + ...fillObject, + relayExecutionInfo: { + updatedRecipient: fillObject.updatedRecipient, + updatedMessage: fillObject.updatedMessage, + updatedOutputAmount: fillObject.updatedOutputAmount, + fillType, + }, + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestBlockSearched gets set to the same value. + } as interfaces.FillWithBlock); + } + + function generateSlowFillRequestFromDeposit( + deposit: interfaces.DepositWithBlock, + fillEventOverride?: Partial + ): Event { + const fillObject = V3FillFromDeposit(deposit, ZERO_ADDRESS); + const { relayer, repaymentChainId, relayExecutionInfo, ...relayData } = fillObject; + return mockDestinationSpokePoolClient.requestV3SlowFill({ + ...relayData, + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestBlockSearched gets set to the same value. + } as interfaces.SlowFillRequest); + } + + beforeEach(async function () { + ({ + spokePool_1, + erc20_1, + spokePool_2, + erc20_2, + configStoreClient, + hubPoolClient, + l1Token_1, + depositor, + relayer, + dataworkerInstance, + spokePoolClient_1, + spokePoolClient_2, + spokePoolClients, + updateAllClients, + } = await setupDataworker(ethers, 25, 25, 0)); + + await updateAllClients(); + mockHubPoolClient = new MockHubPoolClient( + hubPoolClient.logger, + hubPoolClient.hubPool, + configStoreClient, + hubPoolClient.deploymentBlock, + hubPoolClient.chainId + ); + mockConfigStore = new MockConfigStoreClient( + configStoreClient.logger, + configStoreClient.configStore, + undefined, + undefined, + undefined, + undefined, + true + ); + // Mock a realized lp fee pct for each deposit so we can check refund amounts and bundle lp fees. + mockHubPoolClient.setDefaultRealizedLpFeePct(lpFeePct); + mockOriginSpokePoolClient = new MockSpokePoolClient( + spokePoolClient_1.logger, + spokePoolClient_1.spokePool, + spokePoolClient_1.chainId, + spokePoolClient_1.deploymentBlock + ); + mockDestinationSpokePool = await smock.fake(spokePoolClient_2.spokePool.interface); + mockDestinationSpokePoolClient = new MockSpokePoolClient( + spokePoolClient_2.logger, + mockDestinationSpokePool as Contract, + spokePoolClient_2.chainId, + spokePoolClient_2.deploymentBlock + ); + spokePoolClients = { + ...spokePoolClients, + [originChainId]: mockOriginSpokePoolClient, + [destinationChainId]: mockDestinationSpokePoolClient, + }; + await mockHubPoolClient.update(); + await mockOriginSpokePoolClient.update(); + await mockDestinationSpokePoolClient.update(); + mockHubPoolClient.setTokenMapping(l1Token_1.address, originChainId, erc20_1.address); + mockHubPoolClient.setTokenMapping(l1Token_1.address, destinationChainId, erc20_2.address); + mockHubPoolClient.setTokenMapping(l1Token_1.address, repaymentChainId, l1Token_1.address); + const bundleDataClient = new BundleDataClient( + dataworkerInstance.logger, + { + ...dataworkerInstance.clients.bundleDataClient.clients, + hubPoolClient: mockHubPoolClient as unknown as HubPoolClient, + }, + dataworkerInstance.clients.bundleDataClient.spokePoolClients, + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers + ); + dataworkerInstance = new Dataworker( + dataworkerInstance.logger, + { ...dataworkerInstance.clients, bundleDataClient }, + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers, + dataworkerInstance.maxRefundCountOverride, + dataworkerInstance.maxL1TokenCountOverride, + dataworkerInstance.blockRangeEndBlockBuffer + ); + }); + + it("Filters for fast fills replacing slow fills from older bundles", async function () { + // Generate a deposit that cannot be slow filled, to test that its ignored as a slow fill excess. + // Generate a second deposit that can be slow filled but will be slow filled in an older bundle + // Generate a third deposit that does get slow filled but the slow fill is not "seen" by the client. + const depositWithMissingSlowFillRequest = await depositV3( + spokePool_1, + destinationChainId, + depositor, + erc20_1.address, + amountToDeposit, + erc20_2.address, + amountToDeposit + ); + await requestSlowFill(spokePool_2, relayer, depositWithMissingSlowFillRequest); + const missingSlowFillRequestBlock = await spokePool_2.provider.getBlockNumber(); + await mineRandomBlocks(); + + const depositsWithSlowFillRequests = [ + await depositV3( + spokePool_1, + destinationChainId, + depositor, + erc20_1.address, + amountToDeposit, + erc20_1.address, + amountToDeposit + ), + await depositV3( + spokePool_1, + destinationChainId, + depositor, + erc20_1.address, + amountToDeposit, + erc20_2.address, + amountToDeposit + ), + ]; + + await spokePoolClient_1.update(); + const deposits = spokePoolClient_1.getDeposits(); + expect(deposits.length).to.equal(3); + const eligibleSlowFills = depositsWithSlowFillRequests.filter((x) => erc20_2.address === x.outputToken); + const ineligibleSlowFills = depositsWithSlowFillRequests.filter((x) => erc20_2.address !== x.outputToken); + + // Generate slow fill requests for the slow fill-eligible deposits + await requestSlowFill(spokePool_2, relayer, eligibleSlowFills[0]); + await requestSlowFill(spokePool_2, relayer, ineligibleSlowFills[0]); + const lastSlowFillRequestBlock = await spokePool_2.provider.getBlockNumber(); + await mineRandomBlocks(); + + // Now, generate fast fills replacing slow fills for all deposits. + await fillV3Relay(spokePool_2, deposits[0], relayer, repaymentChainId); + await fillV3Relay(spokePool_2, deposits[1], relayer, repaymentChainId); + await fillV3Relay(spokePool_2, deposits[2], relayer, repaymentChainId); + + // Construct a spoke pool client with a small search range that would not include the first fill. + spokePoolClient_2.firstBlockToSearch = missingSlowFillRequestBlock + 1; + spokePoolClient_2.eventSearchConfig.fromBlock = spokePoolClient_2.firstBlockToSearch; + + // There should be one "missing" slow fill request. + await spokePoolClient_2.update(); + const fills = spokePoolClient_2.getFills(); + expect(fills.length).to.equal(3); + const slowFillRequests = spokePoolClient_2.getSlowFillRequestsForOriginChain(originChainId); + expect(slowFillRequests.length).to.equal(2); + assert( + fills.every((x) => x.relayExecutionInfo.fillType === interfaces.FillType.ReplacedSlowFill), + "All fills should be replaced slow fills" + ); + assert( + fills.every((x) => x.blockNumber > lastSlowFillRequestBlock), + "Fills should be later than slow fill request" + ); + + // Create a block range that would make the slow fill requests appear to be in an "older" bundle. + const destinationChainBlockRange = [lastSlowFillRequestBlock + 1, getDefaultBlockRange(5)[0][1]]; + // Substitute destination chain bundle block range. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, { + ...spokePoolClients, + [originChainId]: spokePoolClient_1, + [destinationChainId]: spokePoolClient_2, + }); + + // All fills and deposits are valid + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(3); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + + // There are two "unexecutable slow fills" because there are two deposits that have "equivalent" input + // and output tokens AND: + // - one slow fill request does not get seen by the spoke pool client + // - one slow fill request is in an older bundle + expect(data1.unexecutableSlowFills[destinationChainId][erc20_2.address].length).to.equal(2); + expect( + data1.unexecutableSlowFills[destinationChainId][erc20_2.address].map((x) => x.depositId).sort() + ).to.deep.equal([depositWithMissingSlowFillRequest.depositId, eligibleSlowFills[0].depositId].sort()); + }); + + it("Handles fast fills replacing invalid slow fill request from older bundles", async function () { + // Create a Lite chain to test that slow fill requests involving lite chains are ignored. + mockConfigStore.updateGlobalConfig( + GLOBAL_CONFIG_STORE_KEYS.LITE_CHAIN_ID_INDICES, + JSON.stringify([mockOriginSpokePoolClient.chainId]) + ); + await mockConfigStore.update(); + (spokePoolClient_1 as any).configStoreClient = mockConfigStore; + (spokePoolClient_2 as any).configStoreClient = mockConfigStore; + + // Generate a deposit that cannot be slow filled, to test that its ignored as a slow fill excess. + // - first deposit is FROM lite chain + // - second deposit is TO lite chain + const depositsWithSlowFillRequests = [ + await depositV3( + spokePool_1, + destinationChainId, + depositor, + erc20_1.address, + amountToDeposit, + erc20_2.address, + amountToDeposit + ), + await depositV3( + spokePool_2, + originChainId, + depositor, + erc20_2.address, + amountToDeposit, + erc20_1.address, + amountToDeposit + ), + ]; + + await spokePoolClient_1.update(); + await spokePoolClient_2.update(); + const originChainDeposit = spokePoolClient_1.getDeposits()[0]; + const destinationChainDeposit = spokePoolClient_2.getDeposits()[0]; + + // Generate slow fill requests for the slow fill-eligible deposits + await requestSlowFill(spokePool_2, relayer, depositsWithSlowFillRequests[0]); + await requestSlowFill(spokePool_1, relayer, depositsWithSlowFillRequests[1]); + const lastDestinationChainSlowFillRequestBlock = await spokePool_2.provider.getBlockNumber(); + const lastOriginChainSlowFillRequestBlock = await spokePool_2.provider.getBlockNumber(); + + await mineRandomBlocks(); + + // Now, generate fast fills replacing slow fills for all deposits. + await fillV3Relay(spokePool_2, originChainDeposit, relayer, repaymentChainId); + await fillV3Relay(spokePool_1, destinationChainDeposit, relayer, repaymentChainId); + + await spokePoolClient_1.update(); + await spokePoolClient_2.update(); + assert( + spokePoolClient_2.getFills().every((x) => x.relayExecutionInfo.fillType === interfaces.FillType.ReplacedSlowFill), + "All fills should be replaced slow fills" + ); + assert( + spokePoolClient_1.getFills().every((x) => x.relayExecutionInfo.fillType === interfaces.FillType.ReplacedSlowFill), + "All fills should be replaced slow fills" + ); + + // Create a block range that would make the slow fill requests appear to be in an "older" bundle. + const destinationChainBlockRange = [lastDestinationChainSlowFillRequestBlock + 1, getDefaultBlockRange(5)[0][1]]; + const originChainBlockRange = [lastOriginChainSlowFillRequestBlock + 1, getDefaultBlockRange(5)[0][1]]; + // Substitute destination chain bundle block range. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + bundleBlockRanges[originChainIndex] = originChainBlockRange; + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, { + ...spokePoolClients, + [originChainId]: spokePoolClient_1, + [destinationChainId]: spokePoolClient_2, + }); + + // All fills are valid. Note, origin chain deposit must take repayment on origin chain. + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[originChainId][erc20_1.address].fills.length).to.equal(1); + + // There are zero "unexecutable slow fills" because the slow fill requests in an older bundle are invalid + expect(data1.unexecutableSlowFills).to.deep.equal({}); + }); + + it("Replacing a slow fill request with a fast fill in same bundle doesn't create unexecutable slow fill", async function () { + generateV3Deposit({ outputToken: erc20_2.address }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + generateSlowFillRequestFromDeposit(deposits[0]); + generateV3FillFromDeposit(deposits[0], undefined, undefined, undefined, interfaces.FillType.ReplacedSlowFill); + await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill", "FilledV3Relay"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(getDefaultBlockRange(5), spokePoolClients); + + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleSlowFillsV3).to.deep.equal({}); + expect(data1.unexecutableSlowFills).to.deep.equal({}); + }); + + it("Ignores disabled chains for unexecutable slow fills", async function () { + generateV3Deposit({ outputToken: erc20_2.address }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + generateSlowFillRequestFromDeposit(deposits[0]); + generateV3FillFromDeposit(deposits[0], undefined, undefined, undefined, interfaces.FillType.ReplacedSlowFill); + await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill", "FilledV3Relay"]); + + const emptyData = await dataworkerInstance.clients.bundleDataClient.loadData( + getDisabledBlockRanges(), + spokePoolClients + ); + expect(emptyData.unexecutableSlowFills).to.deep.equal({}); + expect(emptyData.bundleFillsV3).to.deep.equal({}); + expect(emptyData.bundleSlowFillsV3).to.deep.equal({}); + }); + + it("Adds prior bundle expired deposits that requested a slow fill in a prior bundle to unexecutable slow fills", async function () { + // Send deposit that expires in this bundle. + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + // Let's make fill status for the relay hash always return RequestedSlowFill. + const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); + mockDestinationSpokePool.fillStatuses + .whenCalledWith(expiredDepositHash) + .returns(interfaces.FillStatus.RequestedSlowFill); + + // Now, load a bundle that doesn't include the deposit in its range. + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + // Now, there is no bundle deposit but still an expired deposit to refund. + // There is also an unexecutable slow fill. + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.unexecutableSlowFills[destinationChainId][erc20_2.address].length).to.equal(1); + }); + + it("Does not add prior bundle expired lite chain deposits that requested a slow fill in a prior bundle to unexecutable slow fills", async function () { + mockConfigStore.updateGlobalConfig( + GLOBAL_CONFIG_STORE_KEYS.LITE_CHAIN_ID_INDICES, + JSON.stringify([mockOriginSpokePoolClient.chainId]) + ); + await mockConfigStore.update(); + (mockOriginSpokePoolClient as any).configStoreClient = mockConfigStore; + (mockDestinationSpokePool as any).configStoreClient = mockConfigStore; + const updateEventTimestamp = mockConfigStore.liteChainIndicesUpdates[0].timestamp; + + // Send lite chain deposit that expires in this bundle. + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + const expiredDeposit = generateV3Deposit({ + fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1, + quoteTimestamp: updateEventTimestamp, + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + const deposit = mockOriginSpokePoolClient.getDeposits()[0]; + assert(deposit.fromLiteChain, "Deposit should be from lite chain"); + + // Let's make fill status for the relay hash always return RequestedSlowFill. + const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); + mockDestinationSpokePool.fillStatuses + .whenCalledWith(expiredDepositHash) + .returns(interfaces.FillStatus.RequestedSlowFill); + + // Now, load a bundle that doesn't include the deposit in its range. + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + // Now, there is no bundle deposit but still an expired deposit to refund. + // There is NOT an unexecutable slow fill. + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.unexecutableSlowFills).to.deep.equal({}); + }); + + it("Does not add prior bundle expired deposits that did not request a slow fill in a prior bundle to unexecutable slow fills", async function () { + // Send deposit that expires in this bundle. + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + // Let's make fill status for the relay hash always return Unfilled. + const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); + mockDestinationSpokePool.fillStatuses.whenCalledWith(expiredDepositHash).returns(interfaces.FillStatus.Unfilled); + + // Now, load a bundle that doesn't include the deposit in its range. + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + // Now, there is no bundle deposit but still an expired deposit to refund. + // There is also an unexecutable slow fill. + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.unexecutableSlowFills).to.deep.equal({}); + }); + + it("Does not add unexecutable slow fill for prior bundle expired deposits that requested a slow fill if slow fill request is in current bundle", async function () { + // Send deposit that expires in this bundle. + const bundleBlockTimestamps = await dataworkerInstance.clients.bundleDataClient.getBundleBlockTimestamps( + [originChainId, destinationChainId], + getDefaultBlockRange(5), + spokePoolClients + ); + const expiredDeposit = generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + + // If the slow fill request took place in the current bundle, then it is not marked as unexecutable since + // it would not have produced a slow fill request. + const deposit = mockOriginSpokePoolClient.getDeposits()[0]; + generateSlowFillRequestFromDeposit(deposit); + await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); + + // Let's make fill status for the relay hash always return RequestedSlowFill. + const expiredDepositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); + mockDestinationSpokePool.fillStatuses + .whenCalledWith(expiredDepositHash) + .returns(interfaces.FillStatus.RequestedSlowFill); + + // Now, load a bundle that doesn't include the deposit in its range. + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + const oldOriginChainToBlock = getDefaultBlockRange(5)[0][1]; + const bundleBlockRanges = getDefaultBlockRange(5); + bundleBlockRanges[originChainIndex] = [expiredDeposit.blockNumber + 1, oldOriginChainToBlock]; + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + + // Now, there is no bundle deposit but still an expired deposit to refund. + // There is also no unexecutable slow fill because the slow fill request was sent in this bundle. + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.unexecutableSlowFills).to.deep.equal({}); + expect(data1.bundleSlowFillsV3).to.deep.equal({}); + }); +}); From f1bbc6ed18b39fc15798fd1ef15fa09a87009af8 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 27 Jan 2025 14:39:15 -0500 Subject: [PATCH 19/62] import --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 960467f382..86c23771d5 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.3", + "@across-protocol/sdk": "^4.0.0-beta.4", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 3f5919bfe2..879777237b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.3": - version "4.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.3.tgz#26411b7d82f490c902db6d968ce39ffdcc986fec" - integrity sha512-jzSc39hulNxwbXi16247+h1CYgpjRFIZwNgVa/oqsbeCs7yRLwOmYWCXpJ/pnATbhAGNJojQJyz14Hm742g0cg== +"@across-protocol/sdk@^4.0.0-beta.4": + version "4.0.0-beta.4" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.4.tgz#0d6fec07bb2020e9698f35038c5bb99c85f68f23" + integrity sha512-1G3CoJkjmLIwSShcL9yzU7We5V8i9kgktPh+cQwj3kowhO3sBAxEuJj7YNR3wAQhXoucrOMZ0kyjd455gUIWbQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From cc902c5e8af6c68a4c4ebd7eba35ff433752481c Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 27 Jan 2025 14:52:43 -0500 Subject: [PATCH 20/62] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 86c23771d5..743abe8e46 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.4", + "@across-protocol/sdk": "^4.0.0-beta.5", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", From 5bd8e3e5f04765e7fdb2e7bf990a2f82b3ba89b4 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 27 Jan 2025 15:26:15 -0500 Subject: [PATCH 21/62] Update yarn.lock --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 879777237b..25b7c7fe53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.4": - version "4.0.0-beta.4" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.4.tgz#0d6fec07bb2020e9698f35038c5bb99c85f68f23" - integrity sha512-1G3CoJkjmLIwSShcL9yzU7We5V8i9kgktPh+cQwj3kowhO3sBAxEuJj7YNR3wAQhXoucrOMZ0kyjd455gUIWbQ== +"@across-protocol/sdk@^4.0.0-beta.5": + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.5.tgz#e6a0c5dc1a623b782ed20ad96d406b89a279b1cd" + integrity sha512-/GowufxWEXWK2mzB2F2M78XGxG8jaxp08AJm33BZ0kCuL16LkC/CbKEJ3yGeMkNORWYRXvS5oxj/lFaounUKrg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 496e1b693a9d78aec1e202f5ca4b95c78f6b4f57 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 27 Jan 2025 15:36:19 -0500 Subject: [PATCH 22/62] import --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 743abe8e46..7ebcf64988 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.5", + "@across-protocol/sdk": "^4.0.0-beta.6", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 25b7c7fe53..8a4aa37709 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.5": - version "4.0.0-beta.5" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.5.tgz#e6a0c5dc1a623b782ed20ad96d406b89a279b1cd" - integrity sha512-/GowufxWEXWK2mzB2F2M78XGxG8jaxp08AJm33BZ0kCuL16LkC/CbKEJ3yGeMkNORWYRXvS5oxj/lFaounUKrg== +"@across-protocol/sdk@^4.0.0-beta.6": + version "4.0.0-beta.6" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.6.tgz#2a5575cc27658832d5b7a54977d3988c0ad3151f" + integrity sha512-xq39AAvB5432YkOtaLck5xSGoL43F9nyktCSrrp0Grc/rQUsqrfLDV5wuQw1+iu047fbb77on1YTlKpe1c+2rw== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From de14a4ea1e5022cfe629609b3401e9794d117c77 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 28 Jan 2025 16:39:09 -0500 Subject: [PATCH 23/62] Fix tests --- package.json | 2 +- test/Dataworker.loadData.deposit.ts | 7 +++++-- test/Dataworker.loadData.fill.ts | 3 +++ test/Dataworker.loadData.prefill.ts | 7 +++++-- test/Dataworker.loadData.slowFill.ts | 4 +++- test/Dataworker.loadData.unexecutableSlowFill.ts | 6 ++++-- yarn.lock | 8 ++++---- 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 7ebcf64988..d9fc3a3bfb 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.6", + "@across-protocol/sdk": "^4.0.0-beta.7", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.deposit.ts b/test/Dataworker.loadData.deposit.ts index 61931ec532..69e00950e7 100644 --- a/test/Dataworker.loadData.deposit.ts +++ b/test/Dataworker.loadData.deposit.ts @@ -18,8 +18,8 @@ import { import { Dataworker } from "../src/dataworker/Dataworker"; // Tested import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS, BigNumber, bnZero } from "../src/utils"; -import { MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; +import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; +import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; let l1Token_1: Contract; @@ -65,6 +65,9 @@ describe("Dataworker: Load data used in all functions", async function () { beforeEach(async function () { await updateAllClients(); + (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( + sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION + ); mockHubPoolClient = new MockHubPoolClient( hubPoolClient.logger, hubPoolClient.hubPool, diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 067e804acb..950dea5a7d 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -76,6 +76,9 @@ describe("Dataworker: Load data used in all functions", async function () { spy, } = await setupDataworker(ethers, 25, 25, 0)); bundleDataClient = dataworkerInstance.clients.bundleDataClient; + (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( + sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION + ); }); it("Default conditions", async function () { diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index f8a91c39f1..e7a70b086b 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -17,8 +17,8 @@ import { import { Dataworker } from "../src/dataworker/Dataworker"; // Tested import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS } from "../src/utils"; -import { MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; +import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; +import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; let erc20_1: Contract, erc20_2: Contract; let l1Token_1: Contract; @@ -48,6 +48,9 @@ describe("BundleDataClient: Pre-fill logic", async function () { updateAllClients, } = await setupDataworker(ethers, 25, 25, 0)); await updateAllClients(); + (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( + sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION + ); }); describe("Tests with real events", function () { diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index b4c57ccd4c..b2aa6aa4fa 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -117,7 +117,9 @@ describe("BundleDataClient: Slow fill handling & validation", async function () updateAllClients, spy, } = await setupDataworker(ethers, 25, 25, 0)); - + (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( + sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION + ); await updateAllClients(); mockHubPoolClient = new MockHubPoolClient( hubPoolClient.logger, diff --git a/test/Dataworker.loadData.unexecutableSlowFill.ts b/test/Dataworker.loadData.unexecutableSlowFill.ts index 04f7891c5a..8e3d49429a 100644 --- a/test/Dataworker.loadData.unexecutableSlowFill.ts +++ b/test/Dataworker.loadData.unexecutableSlowFill.ts @@ -26,7 +26,7 @@ import { import { Dataworker } from "../src/dataworker/Dataworker"; // Tested import { getCurrentTime, Event, toBNWei, assert, ZERO_ADDRESS } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; +import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; describe("BundleDataClient: Expired deposit and Slow Fill interactions", async function () { let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; @@ -111,7 +111,9 @@ describe("BundleDataClient: Expired deposit and Slow Fill interactions", async f spokePoolClients, updateAllClients, } = await setupDataworker(ethers, 25, 25, 0)); - + (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( + sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION + ); await updateAllClients(); mockHubPoolClient = new MockHubPoolClient( hubPoolClient.logger, diff --git a/yarn.lock b/yarn.lock index 8a4aa37709..c4a7a23bda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.6": - version "4.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.6.tgz#2a5575cc27658832d5b7a54977d3988c0ad3151f" - integrity sha512-xq39AAvB5432YkOtaLck5xSGoL43F9nyktCSrrp0Grc/rQUsqrfLDV5wuQw1+iu047fbb77on1YTlKpe1c+2rw== +"@across-protocol/sdk@^4.0.0-beta.7": + version "4.0.0-beta.7" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.7.tgz#dfa912f995c0857e9bef24ef2ecce4b7346fedda" + integrity sha512-NeyNTkAsaxhx0SQNwv0zJhvjQd6GjJUJY95UwqNQeRcs/7CEB6Zfkwd353oCOMvO2HFc7dMCIlSkPlF+X9VCiA== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 512937dfe422a11474ec61d14f240763b076f801 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 28 Jan 2025 19:19:17 -0500 Subject: [PATCH 24/62] fix --- package.json | 2 +- test/Dataworker.loadData.fill.ts | 2 +- test/Dataworker.loadData.prefill.ts | 2 +- test/Dataworker.loadData.slowFill.ts | 2 +- test/Dataworker.loadData.unexecutableSlowFill.ts | 2 +- yarn.lock | 8 ++++---- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index d9fc3a3bfb..4fb2762c25 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.7", + "@across-protocol/sdk": "^4.0.0-beta.8", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 950dea5a7d..5cdaae5070 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -78,7 +78,7 @@ describe("Dataworker: Load data used in all functions", async function () { bundleDataClient = dataworkerInstance.clients.bundleDataClient; (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION - ); + ); }); it("Default conditions", async function () { diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index e7a70b086b..ad27d181ae 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -50,7 +50,7 @@ describe("BundleDataClient: Pre-fill logic", async function () { await updateAllClients(); (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION - ); + ); }); describe("Tests with real events", function () { diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index b2aa6aa4fa..f89c880d98 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -119,7 +119,7 @@ describe("BundleDataClient: Slow fill handling & validation", async function () } = await setupDataworker(ethers, 25, 25, 0)); (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION - ); + ); await updateAllClients(); mockHubPoolClient = new MockHubPoolClient( hubPoolClient.logger, diff --git a/test/Dataworker.loadData.unexecutableSlowFill.ts b/test/Dataworker.loadData.unexecutableSlowFill.ts index 8e3d49429a..9839c3a840 100644 --- a/test/Dataworker.loadData.unexecutableSlowFill.ts +++ b/test/Dataworker.loadData.unexecutableSlowFill.ts @@ -113,7 +113,7 @@ describe("BundleDataClient: Expired deposit and Slow Fill interactions", async f } = await setupDataworker(ethers, 25, 25, 0)); (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION - ); + ); await updateAllClients(); mockHubPoolClient = new MockHubPoolClient( hubPoolClient.logger, diff --git a/yarn.lock b/yarn.lock index c4a7a23bda..79486659b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.7": - version "4.0.0-beta.7" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.7.tgz#dfa912f995c0857e9bef24ef2ecce4b7346fedda" - integrity sha512-NeyNTkAsaxhx0SQNwv0zJhvjQd6GjJUJY95UwqNQeRcs/7CEB6Zfkwd353oCOMvO2HFc7dMCIlSkPlF+X9VCiA== +"@across-protocol/sdk@^4.0.0-beta.8": + version "4.0.0-beta.8" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.8.tgz#608a2418fa4b12acbe7fcf94a99bfd5948300942" + integrity sha512-/RyVLrL68s4TBqjUbmleifTg9rPv3moHa/T2TYOEg7PocYIaP+xtAIXC+cpvO+vPqTH6XNH+H0aVfxSKR2T1EA== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 95fac03e22decf2f0319070b2f657a257f90671c Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 09:31:04 -0500 Subject: [PATCH 25/62] bump --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4fb2762c25..bd9c1046e5 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.8", + "@across-protocol/sdk": "^4.0.0-beta.9", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 79486659b7..61abd80a16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.8": - version "4.0.0-beta.8" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.8.tgz#608a2418fa4b12acbe7fcf94a99bfd5948300942" - integrity sha512-/RyVLrL68s4TBqjUbmleifTg9rPv3moHa/T2TYOEg7PocYIaP+xtAIXC+cpvO+vPqTH6XNH+H0aVfxSKR2T1EA== +"@across-protocol/sdk@^4.0.0-beta.9": + version "4.0.0-beta.9" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.9.tgz#45176a3639f09edebc1dff5bd16c69c1b5298e5d" + integrity sha512-UemJCMA9jbNS7guC4mmGIIvVzSRu2pCgY6EJ4KAHqRtAwywn7sxhQCGcg448igtP2givsAt/VAmWaUuDibhYdg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 1f7f2cd0b5f39274b6d51bd968856c29f7f82898 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 09:35:22 -0500 Subject: [PATCH 26/62] Remove mocked version bump in non-prefill tests --- test/Dataworker.loadData.deposit.ts | 7 ++----- test/Dataworker.loadData.fill.ts | 3 --- test/Dataworker.loadData.slowFill.ts | 3 --- test/Dataworker.loadData.unexecutableSlowFill.ts | 5 +---- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/test/Dataworker.loadData.deposit.ts b/test/Dataworker.loadData.deposit.ts index 69e00950e7..61931ec532 100644 --- a/test/Dataworker.loadData.deposit.ts +++ b/test/Dataworker.loadData.deposit.ts @@ -18,8 +18,8 @@ import { import { Dataworker } from "../src/dataworker/Dataworker"; // Tested import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS, BigNumber, bnZero } from "../src/utils"; -import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; +import { MockHubPoolClient, MockSpokePoolClient } from "./mocks"; +import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; let l1Token_1: Contract; @@ -65,9 +65,6 @@ describe("Dataworker: Load data used in all functions", async function () { beforeEach(async function () { await updateAllClients(); - (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( - sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION - ); mockHubPoolClient = new MockHubPoolClient( hubPoolClient.logger, hubPoolClient.hubPool, diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 5cdaae5070..067e804acb 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -76,9 +76,6 @@ describe("Dataworker: Load data used in all functions", async function () { spy, } = await setupDataworker(ethers, 25, 25, 0)); bundleDataClient = dataworkerInstance.clients.bundleDataClient; - (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( - sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION - ); }); it("Default conditions", async function () { diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index f89c880d98..d078ea29db 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -117,9 +117,6 @@ describe("BundleDataClient: Slow fill handling & validation", async function () updateAllClients, spy, } = await setupDataworker(ethers, 25, 25, 0)); - (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( - sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION - ); await updateAllClients(); mockHubPoolClient = new MockHubPoolClient( hubPoolClient.logger, diff --git a/test/Dataworker.loadData.unexecutableSlowFill.ts b/test/Dataworker.loadData.unexecutableSlowFill.ts index 9839c3a840..189fac9e98 100644 --- a/test/Dataworker.loadData.unexecutableSlowFill.ts +++ b/test/Dataworker.loadData.unexecutableSlowFill.ts @@ -26,7 +26,7 @@ import { import { Dataworker } from "../src/dataworker/Dataworker"; // Tested import { getCurrentTime, Event, toBNWei, assert, ZERO_ADDRESS } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; +import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; describe("BundleDataClient: Expired deposit and Slow Fill interactions", async function () { let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; @@ -111,9 +111,6 @@ describe("BundleDataClient: Expired deposit and Slow Fill interactions", async f spokePoolClients, updateAllClients, } = await setupDataworker(ethers, 25, 25, 0)); - (configStoreClient as unknown as MockConfigStoreClient).setConfigStoreVersion( - sdkConstants.PRE_FILL_MIN_CONFIG_STORE_VERSION - ); await updateAllClients(); mockHubPoolClient = new MockHubPoolClient( hubPoolClient.logger, From ce657f56fd5f338d39fb44ff21d83c7a4ad490c0 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 17:50:01 -0500 Subject: [PATCH 27/62] wip --- package.json | 2 +- test/Dataworker.loadData.fill.ts | 7 ++-- test/Dataworker.loadData.prefill.ts | 61 +++++++++++++++++++++++++++-- test/mocks/MockBundleDataClient.ts | 19 ++++++++- test/mocks/MockSpokePoolClient.ts | 17 +++++++- yarn.lock | 8 ++-- 6 files changed, 99 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index bd9c1046e5..2b73cfe396 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.9", + "@across-protocol/sdk": "^4.0.0-beta.11", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 067e804acb..aa5d96b335 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -631,11 +631,10 @@ describe("Dataworker: Load data used in all functions", async function () { inputAmount: depositEvent.args.inputAmount.add(1), outputAmount: depositEvent.args.outputAmount.add(1), originChainId: destinationChainId, - depositId: depositEvent.args.depositId + 1, + depositId: toBN(depositEvent.args.depositId + 1), fillDeadline: depositEvent.args.fillDeadline + 1, exclusivityDeadline: depositEvent.args.exclusivityDeadline + 1, message: randomAddress(), - destinationChainId: originChainId, }; for (const [key, val] of Object.entries(invalidRelayData)) { const _depositEvent = cloneDeep(depositEvent); @@ -730,7 +729,7 @@ describe("Dataworker: Load data used in all functions", async function () { // Approximate refunds should count both fills await updateAllClients(); - const refunds = bundleDataClient.getApproximateRefundsForBlockRange( + const refunds = await bundleDataClient.getApproximateRefundsForBlockRange( [originChainId, destinationChainId], getDefaultBlockRange(5) ); @@ -770,7 +769,7 @@ describe("Dataworker: Load data used in all functions", async function () { await updateAllClients(); expect( convertToNumericStrings( - bundleDataClient.getApproximateRefundsForBlockRange( + await bundleDataClient.getApproximateRefundsForBlockRange( [originChainId, destinationChainId], getDefaultBlockRange(5) ) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index ad27d181ae..2cc706ea2a 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -6,6 +6,7 @@ import { FakeContract, SignerWithAddress, V3FillFromDeposit, + createRandomBytes32, depositV3, ethers, expect, @@ -16,9 +17,10 @@ import { } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS } from "../src/utils"; -import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; +import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS, bnZero, TransactionResponse, spreadEventWithBlockNumber } from "../src/utils"; +import { MockBundleDataClient, MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; +import { interfaces, utils as sdkUtils, constants as sdkConstants, providers } from "@across-protocol/sdk"; +import { FillWithBlock } from "../src/interfaces"; let erc20_1: Contract, erc20_2: Contract; let l1Token_1: Contract; @@ -125,7 +127,7 @@ describe("BundleDataClient: Pre-fill logic", async function () { mockHubPoolClient.setTokenMapping(l1Token_1.address, originChainId, erc20_1.address); mockHubPoolClient.setTokenMapping(l1Token_1.address, destinationChainId, erc20_2.address); mockHubPoolClient.setTokenMapping(l1Token_1.address, repaymentChainId, l1Token_1.address); - const bundleDataClient = new BundleDataClient( + const bundleDataClient = new MockBundleDataClient( dataworkerInstance.logger, { ...dataworkerInstance.clients.bundleDataClient.clients, @@ -219,6 +221,57 @@ describe("BundleDataClient: Pre-fill logic", async function () { ); }); + it("Refunds fill to msg.sender if fill is not in-memory and repayment info is invalid", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit fill that we won't include in the bundle block range. Make sure its relayer address is invalid + // so that the bundle data client is forced to overwrite the refund recipient. + const fill = generateV3FillFromDeposit(deposits[0], {}, createRandomBytes32()); + const fillWithBlock = { + ...spreadEventWithBlockNumber(fill), + destinationChainId + } as FillWithBlock; + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + deposits[0], + fillWithBlock + ); + + // Don't include the fill event in the update so that the bundle data client is forced to load the event + // fresh. + await mockDestinationSpokePoolClient.update([]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(0); + + // Mock FillStatus to be Filled so that the BundleDataClient searches for event. + mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); + + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid + // but the msg.sender is valid. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const validRelayerAddress = randomAddress(); + provider._setTransaction(fill.transactionHash, { from: validRelayerAddress } as unknown as TransactionResponse); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + // The fill is a pre-fill because its earlier than the bundle block range. Because its corresponding + // deposit is in the block range, we should refund it. + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(getDefaultBlockRange(5), spokePoolClients); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( + fill.args.depositId + ); + // Check its refunded to correct address: + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal( + validRelayerAddress + ); + }); + it("Refunds pre-fills for duplicate deposits", async function () { // In this test, we send multiple fills per deposit. We assume you cannot send more than one fill per deposit. const deposit = generateV3Deposit({ outputToken: randomAddress() }); diff --git a/test/mocks/MockBundleDataClient.ts b/test/mocks/MockBundleDataClient.ts index 97acac2724..0b29bbb831 100644 --- a/test/mocks/MockBundleDataClient.ts +++ b/test/mocks/MockBundleDataClient.ts @@ -1,9 +1,11 @@ -import { BundleDataClient } from "../../src/clients"; +import { BundleDataClient, SpokePoolClient } from "../../src/clients"; import { CombinedRefunds } from "../../src/dataworker/DataworkerUtils"; +import { DepositWithBlock, FillWithBlock } from "../../src/interfaces"; export class MockBundleDataClient extends BundleDataClient { private pendingBundleRefunds: CombinedRefunds = {}; private nextBundleRefunds: CombinedRefunds = {}; + private matchingFillEvents: Record = {}; async getPendingRefundsFromValidBundles(): Promise { return [this.pendingBundleRefunds]; @@ -28,4 +30,19 @@ export class MockBundleDataClient extends BundleDataClient { getPersistedNextBundleRefunds(): Promise { return Promise.resolve(undefined); } + + setMatchingFillEvent(deposit: DepositWithBlock, fill: FillWithBlock): void { + const relayDataHash = this.getRelayHashFromEvent(deposit); + this.matchingFillEvents[relayDataHash] = fill; + } + + findMatchingFillEvent( + deposit: DepositWithBlock, + spokePoolClient: SpokePoolClient + ): Promise { + const relayDataHash = this.getRelayHashFromEvent(deposit); + return this.matchingFillEvents[relayDataHash] + ? Promise.resolve(this.matchingFillEvents[relayDataHash]) + : super.findMatchingFillEvent(deposit, spokePoolClient); + } } diff --git a/test/mocks/MockSpokePoolClient.ts b/test/mocks/MockSpokePoolClient.ts index 260625eda6..62739d5f14 100644 --- a/test/mocks/MockSpokePoolClient.ts +++ b/test/mocks/MockSpokePoolClient.ts @@ -1,7 +1,9 @@ -import { clients } from "@across-protocol/sdk"; +import { clients, interfaces } from "@across-protocol/sdk"; +import { Deposit } from "../../src/interfaces"; export class MockSpokePoolClient extends clients.mocks.MockSpokePoolClient { public maxFillDeadlineOverride?: number; public oldestBlockTimestampOverride?: number; + private relayFillStatuses: Record = {}; public setMaxFillDeadlineOverride(maxFillDeadlineOverride?: number): void { this.maxFillDeadlineOverride = maxFillDeadlineOverride; @@ -18,4 +20,17 @@ export class MockSpokePoolClient extends clients.mocks.MockSpokePoolClient { public getOldestTime(): number { return this.oldestBlockTimestampOverride ?? super.getOldestTime(); } + + public setRelayFillStatus(deposit: Deposit, fillStatus: interfaces.FillStatus): void { + const relayDataHash = deposit.depositId.toString(); + this.relayFillStatuses[relayDataHash] = fillStatus; + } + public relayFillStatus(relayData: interfaces.RelayData, + blockTag?: number | "latest", + destinationChainId?: number): Promise { + const relayDataHash = relayData.depositId.toString(); + return this.relayFillStatuses[relayDataHash] + ? Promise.resolve(this.relayFillStatuses[relayDataHash]) + : super.relayFillStatus(relayData, blockTag, destinationChainId); + } } diff --git a/yarn.lock b/yarn.lock index 61abd80a16..2ad0674d54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.9": - version "4.0.0-beta.9" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.9.tgz#45176a3639f09edebc1dff5bd16c69c1b5298e5d" - integrity sha512-UemJCMA9jbNS7guC4mmGIIvVzSRu2pCgY6EJ4KAHqRtAwywn7sxhQCGcg448igtP2givsAt/VAmWaUuDibhYdg== +"@across-protocol/sdk@^4.0.0-beta.11": + version "4.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.11.tgz#a87505f78aaec286b942e1cb4019647da393f15b" + integrity sha512-tJU1qpncF/KgeEXwBlnTRY7RnVHYgYY/NJ+HaXZTT3290I3Go9jVLdTx08Rj3LAtJW9QEhvnOBA+TE9ogwOK0A== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From b5252cf9c14f0d4cf60b79b3c51cc73245378c78 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 17:56:10 -0500 Subject: [PATCH 28/62] lint --- test/Dataworker.loadData.prefill.ts | 25 +++++++++++++++++-------- test/mocks/MockSpokePoolClient.ts | 12 +++++++----- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 2cc706ea2a..291db1de63 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -1,4 +1,4 @@ -import { BundleDataClient, ConfigStoreClient, HubPoolClient, SpokePoolClient } from "../src/clients"; +import { ConfigStoreClient, HubPoolClient, SpokePoolClient } from "../src/clients"; import { amountToDeposit, destinationChainId, originChainId, repaymentChainId } from "./constants"; import { setupDataworker } from "./fixtures/Dataworker.Fixture"; import { @@ -17,7 +17,15 @@ import { } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS, bnZero, TransactionResponse, spreadEventWithBlockNumber } from "../src/utils"; +import { + getCurrentTime, + Event, + toBNWei, + ZERO_ADDRESS, + bnZero, + TransactionResponse, + spreadEventWithBlockNumber, +} from "../src/utils"; import { MockBundleDataClient, MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; import { interfaces, utils as sdkUtils, constants as sdkConstants, providers } from "@across-protocol/sdk"; import { FillWithBlock } from "../src/interfaces"; @@ -231,14 +239,14 @@ describe("BundleDataClient: Pre-fill logic", async function () { const fill = generateV3FillFromDeposit(deposits[0], {}, createRandomBytes32()); const fillWithBlock = { ...spreadEventWithBlockNumber(fill), - destinationChainId + destinationChainId, } as FillWithBlock; (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( deposits[0], fillWithBlock ); - // Don't include the fill event in the update so that the bundle data client is forced to load the event + // Don't include the fill event in the update so that the bundle data client is forced to load the event // fresh. await mockDestinationSpokePoolClient.update([]); expect(mockDestinationSpokePoolClient.getFills().length).to.equal(0); @@ -261,15 +269,16 @@ describe("BundleDataClient: Pre-fill logic", async function () { // The fill is a pre-fill because its earlier than the bundle block range. Because its corresponding // deposit is in the block range, we should refund it. - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(getDefaultBlockRange(5), spokePoolClients); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( fill.args.depositId ); // Check its refunded to correct address: - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal( - validRelayerAddress - ); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal(validRelayerAddress); }); it("Refunds pre-fills for duplicate deposits", async function () { diff --git a/test/mocks/MockSpokePoolClient.ts b/test/mocks/MockSpokePoolClient.ts index 62739d5f14..017405fd17 100644 --- a/test/mocks/MockSpokePoolClient.ts +++ b/test/mocks/MockSpokePoolClient.ts @@ -22,15 +22,17 @@ export class MockSpokePoolClient extends clients.mocks.MockSpokePoolClient { } public setRelayFillStatus(deposit: Deposit, fillStatus: interfaces.FillStatus): void { - const relayDataHash = deposit.depositId.toString(); + const relayDataHash = deposit.depositId.toString(); this.relayFillStatuses[relayDataHash] = fillStatus; } - public relayFillStatus(relayData: interfaces.RelayData, + public relayFillStatus( + relayData: interfaces.RelayData, blockTag?: number | "latest", - destinationChainId?: number): Promise { - const relayDataHash = relayData.depositId.toString(); + destinationChainId?: number + ): Promise { + const relayDataHash = relayData.depositId.toString(); return this.relayFillStatuses[relayDataHash] ? Promise.resolve(this.relayFillStatuses[relayDataHash]) : super.relayFillStatus(relayData, blockTag, destinationChainId); - } + } } From d11e9822951db6a02beb4ddd115e4c4fb3cccc66 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 18:22:51 -0500 Subject: [PATCH 29/62] bump --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2b73cfe396..2a691bb435 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.11", + "@across-protocol/sdk": "^4.0.0-beta.13", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 2ad0674d54..3fe70f5009 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.11": - version "4.0.0-beta.11" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.11.tgz#a87505f78aaec286b942e1cb4019647da393f15b" - integrity sha512-tJU1qpncF/KgeEXwBlnTRY7RnVHYgYY/NJ+HaXZTT3290I3Go9jVLdTx08Rj3LAtJW9QEhvnOBA+TE9ogwOK0A== +"@across-protocol/sdk@^4.0.0-beta.13": + version "4.0.0-beta.13" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.13.tgz#8e89b8a35d4a695786b2127fee434cf79e7c5bb5" + integrity sha512-/UVhUqcyAOZnR+k3uQfe+mLftwtISUeXFNLPUPt00IJ0CmeUIbt1tgwsYggDc+ZgiP2vsjJWPGJhDJE+9YlA8w== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 99496793724ac632bcd8d97d9ceeaa494822b323 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 18:28:38 -0500 Subject: [PATCH 30/62] bump --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2a691bb435..5814d8b273 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.13", + "@across-protocol/sdk": "^4.0.0-beta.14", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 3fe70f5009..22f68a93e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.13": - version "4.0.0-beta.13" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.13.tgz#8e89b8a35d4a695786b2127fee434cf79e7c5bb5" - integrity sha512-/UVhUqcyAOZnR+k3uQfe+mLftwtISUeXFNLPUPt00IJ0CmeUIbt1tgwsYggDc+ZgiP2vsjJWPGJhDJE+9YlA8w== +"@across-protocol/sdk@^4.0.0-beta.14": + version "4.0.0-beta.14" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.14.tgz#cd8c081e16b0efbb44082cbc6c4a16b5a3f60aab" + integrity sha512-nan1a0LRzUKMyv/FagaSVzNoDH7PgDLqUBzmvGCUcYqG2jjfi4l4LP1uq8Oyp5eVjQNWRD3MlYEhNQhDSenfIg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From c46bcd2dd3155982b2ceef19e7f536700b553b74 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 18:50:56 -0500 Subject: [PATCH 31/62] fix --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5814d8b273..9d311f3881 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.14", + "@across-protocol/sdk": "^4.0.0-beta.15", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 22f68a93e9..48c58462d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.14": - version "4.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.14.tgz#cd8c081e16b0efbb44082cbc6c4a16b5a3f60aab" - integrity sha512-nan1a0LRzUKMyv/FagaSVzNoDH7PgDLqUBzmvGCUcYqG2jjfi4l4LP1uq8Oyp5eVjQNWRD3MlYEhNQhDSenfIg== +"@across-protocol/sdk@^4.0.0-beta.15": + version "4.0.0-beta.15" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.15.tgz#f8ff0779807af1a81b1f6ba3ad25e84294e1b8ad" + integrity sha512-SufLB6aszBqWhjQSlU6GTyoCf2Fd6Nb7HzISmE2D/7a0KyCKrny23Zd6QjQby/B0rdP3AoJt0n0mNAziruDflA== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 959b57f79708a498f75e56f6eac5c20303c97af9 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 19:28:06 -0500 Subject: [PATCH 32/62] import --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9d311f3881..296e64e1ad 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.15", + "@across-protocol/sdk": "^4.0.0-beta.16", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 48c58462d4..dadc9168a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.15": - version "4.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.15.tgz#f8ff0779807af1a81b1f6ba3ad25e84294e1b8ad" - integrity sha512-SufLB6aszBqWhjQSlU6GTyoCf2Fd6Nb7HzISmE2D/7a0KyCKrny23Zd6QjQby/B0rdP3AoJt0n0mNAziruDflA== +"@across-protocol/sdk@^4.0.0-beta.16": + version "4.0.0-beta.16" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.16.tgz#9aa5c98b2dd9d15391c230b5862bc3bfd59967f9" + integrity sha512-VFAokJkIUJkZjU5DEYMZBBxGEh/MBO8kK2N6p9Z3tK/7AuuHbUGPj0JnDpIpJyY3xpqMddvzfoEGqNhOJHu+Gw== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From b385670add8853e8a2b94366095c1103caf22f4d Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 19:36:59 -0500 Subject: [PATCH 33/62] fix --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 296e64e1ad..7854eee7f2 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.16", + "@across-protocol/sdk": "^4.0.0-beta.17", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index dadc9168a5..eecf74548c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.16": - version "4.0.0-beta.16" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.16.tgz#9aa5c98b2dd9d15391c230b5862bc3bfd59967f9" - integrity sha512-VFAokJkIUJkZjU5DEYMZBBxGEh/MBO8kK2N6p9Z3tK/7AuuHbUGPj0JnDpIpJyY3xpqMddvzfoEGqNhOJHu+Gw== +"@across-protocol/sdk@^4.0.0-beta.17": + version "4.0.0-beta.17" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.17.tgz#c9673f2d756680e4f2c98a862fb1bdffed251d4e" + integrity sha512-DnC7OSpeBzNO6gllyQQG776s6zTHn2OcQDUAyCda+5rNtT32F61YdcOMCVJY9JkQIczkFnx5wVV45E6UJzHzNg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 4cbfc5d9d5b57e94d8104bc32d43201891337a8f Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 29 Jan 2025 19:41:03 -0500 Subject: [PATCH 34/62] Update Dataworker.loadData.fill.ts --- test/Dataworker.loadData.fill.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 2c878eeaa0..5bdfcc12e6 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -38,7 +38,7 @@ import { bnZero, } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, constants as sdkConstants, providers} from "@across-protocol/sdk"; +import { interfaces, constants as sdkConstants, providers } from "@across-protocol/sdk"; import { cloneDeep } from "lodash"; import { CombinedRefunds } from "../src/dataworker/DataworkerUtils"; import { INFINITE_FILL_DEADLINE } from "../src/common"; From 92ff83ea908ef743167219383afbc4001d6f1c9c Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 07:55:39 -0500 Subject: [PATCH 35/62] Fix tests --- test/Dataworker.loadData.deposit.ts | 20 ++--- test/Dataworker.loadData.fill.ts | 76 +++++++++++-------- test/Dataworker.loadData.prefill.ts | 16 ++-- test/Dataworker.loadData.slowFill.ts | 16 ++-- ...ataworker.loadData.unexecutableSlowFill.ts | 16 ++-- 5 files changed, 79 insertions(+), 65 deletions(-) diff --git a/test/Dataworker.loadData.deposit.ts b/test/Dataworker.loadData.deposit.ts index 61931ec532..71967050c6 100644 --- a/test/Dataworker.loadData.deposit.ts +++ b/test/Dataworker.loadData.deposit.ts @@ -17,7 +17,7 @@ import { } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS, BigNumber, bnZero } from "../src/utils"; +import { getCurrentTime, toBNWei, ZERO_ADDRESS, BigNumber, bnZero } from "../src/utils"; import { MockHubPoolClient, MockSpokePoolClient } from "./mocks"; import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; @@ -117,7 +117,7 @@ describe("Dataworker: Load data used in all functions", async function () { ); }); - function generateV3Deposit(eventOverride?: Partial): Event { + function generateV3Deposit(eventOverride?: Partial): interfaces.Log { return mockOriginSpokePoolClient.depositV3({ inputToken: erc20_1.address, inputAmount: eventOverride?.inputAmount ?? undefined, @@ -137,14 +137,14 @@ describe("Dataworker: Load data used in all functions", async function () { _relayer = relayer.address, _repaymentChainId = repaymentChainId, fillType = interfaces.FillType.FastFill - ): Event { + ): interfaces.Log { const fillObject = V3FillFromDeposit(deposit, _relayer, _repaymentChainId); return mockDestinationSpokePoolClient.fillV3Relay({ ...fillObject, relayExecutionInfo: { - updatedRecipient: fillObject.updatedRecipient, - updatedMessage: fillObject.updatedMessage, - updatedOutputAmount: fillObject.updatedOutputAmount, + updatedRecipient: fillObject.relayExecutionInfo.updatedRecipient, + updatedMessage: fillObject.relayExecutionInfo.updatedMessage, + updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client @@ -153,14 +153,14 @@ describe("Dataworker: Load data used in all functions", async function () { } function generateV3FillFromDepositEvent( - depositEvent: Event, + depositEvent: interfaces.Log, fillEventOverride?: Partial, _relayer = relayer.address, _repaymentChainId = repaymentChainId, fillType = interfaces.FillType.FastFill, outputAmount: BigNumber = depositEvent.args.outputAmount, updatedOutputAmount: BigNumber = depositEvent.args.outputAmount - ): Event { + ): interfaces.Log { const { args } = depositEvent; return mockDestinationSpokePoolClient.fillV3Relay({ ...args, @@ -168,8 +168,8 @@ describe("Dataworker: Load data used in all functions", async function () { outputAmount, repaymentChainId: _repaymentChainId, relayExecutionInfo: { - updatedRecipient: depositEvent.updatedRecipient, - updatedMessage: depositEvent.updatedMessage, + updatedRecipient: depositEvent.args.updatedRecipient, + updatedMessage: depositEvent.args.updatedMessage, updatedOutputAmount: updatedOutputAmount, fillType, }, diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 5bdfcc12e6..9195e11a80 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -23,21 +23,11 @@ import { sinon, smock, spyLogIncludes, - bnZero, } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { - getCurrentTime, - toBN, - Event, - toBNWei, - fixedPointAdjustment, - ZERO_ADDRESS, - BigNumber, - bnZero, -} from "../src/utils"; -import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; +import { getCurrentTime, toBN, toBNWei, fixedPointAdjustment, ZERO_ADDRESS, BigNumber, bnZero } from "../src/utils"; +import { MockBundleDataClient, MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; import { interfaces, constants as sdkConstants, providers } from "@across-protocol/sdk"; import { cloneDeep } from "lodash"; import { CombinedRefunds } from "../src/dataworker/DataworkerUtils"; @@ -153,7 +143,7 @@ describe("Dataworker: Load data used in all functions", async function () { mockHubPoolClient.setTokenMapping(l1Token_1.address, originChainId, erc20_1.address); mockHubPoolClient.setTokenMapping(l1Token_1.address, destinationChainId, erc20_2.address); mockHubPoolClient.setTokenMapping(l1Token_1.address, repaymentChainId, l1Token_1.address); - const bundleDataClient = new BundleDataClient( + const bundleDataClient = new MockBundleDataClient( dataworkerInstance.logger, { ...dataworkerInstance.clients.bundleDataClient.clients, @@ -172,7 +162,7 @@ describe("Dataworker: Load data used in all functions", async function () { ); }); - function generateV3Deposit(eventOverride?: Partial): Event { + function generateV3Deposit(eventOverride?: Partial): interfaces.Log { return mockOriginSpokePoolClient.depositV3({ inputToken: erc20_1.address, inputAmount: eventOverride?.inputAmount ?? undefined, @@ -192,14 +182,14 @@ describe("Dataworker: Load data used in all functions", async function () { _relayer = relayer.address, _repaymentChainId = repaymentChainId, fillType = interfaces.FillType.FastFill - ): Event { + ): interfaces.Log { const fillObject = V3FillFromDeposit(deposit, _relayer, _repaymentChainId); return mockDestinationSpokePoolClient.fillV3Relay({ ...fillObject, relayExecutionInfo: { - updatedRecipient: fillObject.updatedRecipient, - updatedMessage: fillObject.updatedMessage, - updatedOutputAmount: fillObject.updatedOutputAmount, + updatedRecipient: fillObject.relayExecutionInfo.updatedRecipient, + updatedMessage: fillObject.relayExecutionInfo.updatedMessage, + updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client @@ -208,14 +198,14 @@ describe("Dataworker: Load data used in all functions", async function () { } function generateV3FillFromDepositEvent( - depositEvent: Event, + depositEvent: interfaces.Log, fillEventOverride?: Partial, _relayer = relayer.address, _repaymentChainId = repaymentChainId, fillType = interfaces.FillType.FastFill, outputAmount: BigNumber = depositEvent.args.outputAmount, updatedOutputAmount: BigNumber = depositEvent.args.outputAmount - ): Event { + ): interfaces.Log { const { args } = depositEvent; return mockDestinationSpokePoolClient.fillV3Relay({ ...args, @@ -223,8 +213,8 @@ describe("Dataworker: Load data used in all functions", async function () { outputAmount, repaymentChainId: _repaymentChainId, relayExecutionInfo: { - updatedRecipient: depositEvent.updatedRecipient, - updatedMessage: depositEvent.updatedMessage, + updatedRecipient: depositEvent.args.updatedRecipient, + updatedMessage: depositEvent.args.updatedMessage, updatedOutputAmount: updatedOutputAmount, fillType, }, @@ -700,8 +690,8 @@ describe("Dataworker: Load data used in all functions", async function () { }); describe("Bytes32 address invalid cases", async function () { it("Fallback to msg.sender when the relayer repayment address is invalid on an EVM chain", async function () { - const depositV3Events: Event[] = []; - const fillV3Events: Event[] = []; + const depositV3Events: interfaces.Log[] = []; + const fillV3Events: interfaces.Log[] = []; const destinationChainId = mockDestinationSpokePoolClient.chainId; // Create three valid deposits depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); @@ -757,8 +747,8 @@ describe("Dataworker: Load data used in all functions", async function () { }); }); it("Treats a relayer's fill with a bytes32 address taking repayment on an EVM network as invalid", async function () { - const depositV3Events: Event[] = []; - const fillV3Events: Event[] = []; + const depositV3Events: interfaces.Log[] = []; + const fillV3Events: interfaces.Log[] = []; const destinationChainId = mockDestinationSpokePoolClient.chainId; // Create three valid deposits depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); @@ -785,6 +775,19 @@ describe("Dataworker: Load data used in all functions", async function () { fillV3Events.forEach((event) => provider._setTransaction(event.transactionHash, { from: invalidRelayer })); mockDestinationSpokePoolClient.spokePool = spokeWrapper; + // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will + // query relayStatuses() on the spoke pool. + const invalidFill = mockDestinationSpokePoolClient + .getFills() + .find((e) => e.depositId.eq(fillV3Events[2].args.depositId)); + const invalidFilledDeposit = deposits.find((e) => e.depositId.eq(invalidFill!.depositId)); + mockDestinationSpokePoolClient.setRelayFillStatus(invalidFilledDeposit!, interfaces.FillStatus.Filled); + // Also mock the matched fill event so that BundleDataClient doesn't query for it. + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + invalidFilledDeposit!, + invalidFill! + ); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( getDefaultBlockRange(5), spokePoolClients @@ -803,8 +806,8 @@ describe("Dataworker: Load data used in all functions", async function () { }); // This is essentially a copy of the first test in this block, with the addition of the change to the config store. it("Fill with bytes32 relayer with lite chain deposit is refunded on lite chain to msg.sender", async function () { - const depositV3Events: Event[] = []; - const fillV3Events: Event[] = []; + const depositV3Events: interfaces.Log[] = []; + const fillV3Events: interfaces.Log[] = []; const destinationChainId = mockDestinationSpokePoolClient.chainId; // Update and set the config store client. hubPoolClient.configStoreClient._updateLiteChains([mockOriginSpokePoolClient.chainId]); @@ -865,8 +868,8 @@ describe("Dataworker: Load data used in all functions", async function () { }); // This is almost the same as the second test in this block, with the exception of the change to the config store. it("Fill with bytes32 relayer with lite chain deposit is invalid if msg.sender is not a bytes20 address", async function () { - const depositV3Events: Event[] = []; - const fillV3Events: Event[] = []; + const depositV3Events: interfaces.Log[] = []; + const fillV3Events: interfaces.Log[] = []; const destinationChainId = mockDestinationSpokePoolClient.chainId; // Update and set the config store client. hubPoolClient.configStoreClient._updateLiteChains([mockOriginSpokePoolClient.chainId]); @@ -896,6 +899,19 @@ describe("Dataworker: Load data used in all functions", async function () { fillV3Events.forEach((event) => provider._setTransaction(event.transactionHash, { from: invalidRelayer })); mockDestinationSpokePoolClient.spokePool = spokeWrapper; + // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will + // query relayStatuses() on the spoke pool. + const invalidFill = mockDestinationSpokePoolClient + .getFills() + .find((e) => e.depositId.eq(fillV3Events[2].args.depositId)); + const invalidFilledDeposit = deposits.find((e) => e.depositId.eq(invalidFill!.depositId)); + mockDestinationSpokePoolClient.setRelayFillStatus(invalidFilledDeposit!, interfaces.FillStatus.Filled); + // Also mock the matched fill event so that BundleDataClient doesn't query for it. + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + invalidFilledDeposit!, + invalidFill! + ); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( getDefaultBlockRange(5), spokePoolClients diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 291db1de63..6a0c0523e0 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -15,11 +15,9 @@ import { randomAddress, smock, } from "./utils"; - import { Dataworker } from "../src/dataworker/Dataworker"; // Tested import { getCurrentTime, - Event, toBNWei, ZERO_ADDRESS, bnZero, @@ -154,7 +152,7 @@ describe("BundleDataClient: Pre-fill logic", async function () { ); }); - function generateV3Deposit(eventOverride?: Partial): Event { + function generateV3Deposit(eventOverride?: Partial): interfaces.Log { return mockOriginSpokePoolClient.depositV3({ inputToken: erc20_1.address, outputToken: eventOverride?.outputToken ?? erc20_2.address, @@ -173,14 +171,14 @@ describe("BundleDataClient: Pre-fill logic", async function () { _relayer = relayer.address, _repaymentChainId = repaymentChainId, fillType = interfaces.FillType.FastFill - ): Event { + ): interfaces.Log { const fillObject = V3FillFromDeposit(deposit, _relayer, _repaymentChainId); return mockDestinationSpokePoolClient.fillV3Relay({ ...fillObject, relayExecutionInfo: { - updatedRecipient: fillObject.updatedRecipient, - updatedMessage: fillObject.updatedMessage, - updatedOutputAmount: fillObject.updatedOutputAmount, + updatedRecipient: fillObject.relayExecutionInfo.updatedRecipient, + updatedMessage: fillObject.relayExecutionInfo.updatedMessage, + updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client @@ -191,14 +189,14 @@ describe("BundleDataClient: Pre-fill logic", async function () { function generateSlowFillRequestFromDeposit( deposit: interfaces.DepositWithBlock, fillEventOverride?: Partial - ): Event { + ): interfaces.Log { const fillObject = V3FillFromDeposit(deposit, ZERO_ADDRESS); const { relayer, repaymentChainId, relayExecutionInfo, ...relayData } = fillObject; return mockDestinationSpokePoolClient.requestV3SlowFill({ ...relayData, blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client // so that mocked client's latestBlockSearched gets set to the same value. - } as interfaces.SlowFillRequest); + } as interfaces.SlowFillRequestWithBlock); } describe("Pre-fills", function () { diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index d078ea29db..fbe14a5c51 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -25,7 +25,7 @@ import { } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { getCurrentTime, Event, toBNWei, ZERO_ADDRESS, bnZero } from "../src/utils"; +import { getCurrentTime, toBNWei, ZERO_ADDRESS, bnZero } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; import { cloneDeep } from "lodash"; @@ -51,7 +51,7 @@ describe("BundleDataClient: Slow fill handling & validation", async function () let mockConfigStore: MockConfigStoreClient; const lpFeePct = toBNWei("0.01"); - function generateV3Deposit(eventOverride?: Partial): Event { + function generateV3Deposit(eventOverride?: Partial): interfaces.Log { return mockOriginSpokePoolClient.depositV3({ inputToken: erc20_1.address, inputAmount: eventOverride?.inputAmount ?? undefined, @@ -71,14 +71,14 @@ describe("BundleDataClient: Slow fill handling & validation", async function () _relayer = relayer.address, _repaymentChainId = repaymentChainId, fillType = interfaces.FillType.FastFill - ): Event { + ): interfaces.Log { const fillObject = V3FillFromDeposit(deposit, _relayer, _repaymentChainId); return mockDestinationSpokePoolClient.fillV3Relay({ ...fillObject, relayExecutionInfo: { - updatedRecipient: fillObject.updatedRecipient, - updatedMessage: fillObject.updatedMessage, - updatedOutputAmount: fillObject.updatedOutputAmount, + updatedRecipient: fillObject.relayExecutionInfo.updatedRecipient, + updatedMessage: fillObject.relayExecutionInfo.updatedMessage, + updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client @@ -89,14 +89,14 @@ describe("BundleDataClient: Slow fill handling & validation", async function () function generateSlowFillRequestFromDeposit( deposit: interfaces.DepositWithBlock, fillEventOverride?: Partial - ): Event { + ): interfaces.Log { const fillObject = V3FillFromDeposit(deposit, ZERO_ADDRESS); const { relayer, repaymentChainId, relayExecutionInfo, ...relayData } = fillObject; return mockDestinationSpokePoolClient.requestV3SlowFill({ ...relayData, blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client // so that mocked client's latestBlockSearched gets set to the same value. - } as interfaces.SlowFillRequest); + } as interfaces.SlowFillRequestWithBlock); } beforeEach(async function () { diff --git a/test/Dataworker.loadData.unexecutableSlowFill.ts b/test/Dataworker.loadData.unexecutableSlowFill.ts index 189fac9e98..3c8ee39388 100644 --- a/test/Dataworker.loadData.unexecutableSlowFill.ts +++ b/test/Dataworker.loadData.unexecutableSlowFill.ts @@ -24,7 +24,7 @@ import { } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { getCurrentTime, Event, toBNWei, assert, ZERO_ADDRESS } from "../src/utils"; +import { getCurrentTime, toBNWei, assert, ZERO_ADDRESS } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; @@ -46,7 +46,7 @@ describe("BundleDataClient: Expired deposit and Slow Fill interactions", async f let mockConfigStore: MockConfigStoreClient; const lpFeePct = toBNWei("0.01"); - function generateV3Deposit(eventOverride?: Partial): Event { + function generateV3Deposit(eventOverride?: Partial): interfaces.Log { return mockOriginSpokePoolClient.depositV3({ inputToken: erc20_1.address, inputAmount: eventOverride?.inputAmount ?? undefined, @@ -66,14 +66,14 @@ describe("BundleDataClient: Expired deposit and Slow Fill interactions", async f _relayer = relayer.address, _repaymentChainId = repaymentChainId, fillType = interfaces.FillType.FastFill - ): Event { + ): interfaces.Log { const fillObject = V3FillFromDeposit(deposit, _relayer, _repaymentChainId); return mockDestinationSpokePoolClient.fillV3Relay({ ...fillObject, relayExecutionInfo: { - updatedRecipient: fillObject.updatedRecipient, - updatedMessage: fillObject.updatedMessage, - updatedOutputAmount: fillObject.updatedOutputAmount, + updatedRecipient: fillObject.relayExecutionInfo.updatedRecipient, + updatedMessage: fillObject.relayExecutionInfo.updatedMessage, + updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client @@ -84,14 +84,14 @@ describe("BundleDataClient: Expired deposit and Slow Fill interactions", async f function generateSlowFillRequestFromDeposit( deposit: interfaces.DepositWithBlock, fillEventOverride?: Partial - ): Event { + ): interfaces.Log { const fillObject = V3FillFromDeposit(deposit, ZERO_ADDRESS); const { relayer, repaymentChainId, relayExecutionInfo, ...relayData } = fillObject; return mockDestinationSpokePoolClient.requestV3SlowFill({ ...relayData, blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client // so that mocked client's latestBlockSearched gets set to the same value. - } as interfaces.SlowFillRequest); + } as interfaces.SlowFillRequestWithBlock); } beforeEach(async function () { From 70ac78af19534f724e6b8c9c78edbdae41df7051 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 09:55:12 -0500 Subject: [PATCH 36/62] move some bytes32 invalid test cases to pre-fills because they hit pre-fill logic --- test/Dataworker.loadData.fill.ts | 120 ---------------------------- test/Dataworker.loadData.prefill.ts | 84 +++++++++++++++++++ 2 files changed, 84 insertions(+), 120 deletions(-) diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 9195e11a80..b5a3ff0426 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -746,64 +746,6 @@ describe("Dataworker: Load data used in all functions", async function () { .div(fixedPointAdjustment), }); }); - it("Treats a relayer's fill with a bytes32 address taking repayment on an EVM network as invalid", async function () { - const depositV3Events: interfaces.Log[] = []; - const fillV3Events: interfaces.Log[] = []; - const destinationChainId = mockDestinationSpokePoolClient.chainId; - // Create three valid deposits - depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); - depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); - depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDeposits(); - - // Fill deposits from different relayers - const invalidRelayer = ethers.utils.randomBytes(32); - fillV3Events.push(generateV3FillFromDeposit(deposits[0])); - fillV3Events.push(generateV3FillFromDeposit(deposits[1])); - fillV3Events.push(generateV3FillFromDeposit(deposits[2], {}, invalidRelayer)); - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); - // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so - // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, - // set the msg.sender as an invalid address. - const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); - const spokeWrapper = new Contract( - mockDestinationSpokePoolClient.spokePool.address, - mockDestinationSpokePoolClient.spokePool.interface, - provider - ); - fillV3Events.forEach((event) => provider._setTransaction(event.transactionHash, { from: invalidRelayer })); - mockDestinationSpokePoolClient.spokePool = spokeWrapper; - - // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will - // query relayStatuses() on the spoke pool. - const invalidFill = mockDestinationSpokePoolClient - .getFills() - .find((e) => e.depositId.eq(fillV3Events[2].args.depositId)); - const invalidFilledDeposit = deposits.find((e) => e.depositId.eq(invalidFill!.depositId)); - mockDestinationSpokePoolClient.setRelayFillStatus(invalidFilledDeposit!, interfaces.FillStatus.Filled); - // Also mock the matched fill event so that BundleDataClient doesn't query for it. - (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( - invalidFilledDeposit!, - invalidFill! - ); - - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - - // We expect the last fillV3Event to be invalid. - const nValidFills = fillV3Events.length - 1; - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(nValidFills); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.map((e) => e.depositId)).to.deep.equal( - fillV3Events.slice(0, nValidFills).map((event) => event.args.depositId) - ); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.map((e) => e.lpFeePct)).to.deep.equal( - fillV3Events.slice(0, nValidFills).map(() => lpFeePct) - ); - expect(spyLogIncludes(spy, -2, "invalid V3 fills in range")).to.be.true; - }); // This is essentially a copy of the first test in this block, with the addition of the change to the config store. it("Fill with bytes32 relayer with lite chain deposit is refunded on lite chain to msg.sender", async function () { const depositV3Events: interfaces.Log[] = []; @@ -866,68 +808,6 @@ describe("Dataworker: Load data used in all functions", async function () { .div(fixedPointAdjustment), }); }); - // This is almost the same as the second test in this block, with the exception of the change to the config store. - it("Fill with bytes32 relayer with lite chain deposit is invalid if msg.sender is not a bytes20 address", async function () { - const depositV3Events: interfaces.Log[] = []; - const fillV3Events: interfaces.Log[] = []; - const destinationChainId = mockDestinationSpokePoolClient.chainId; - // Update and set the config store client. - hubPoolClient.configStoreClient._updateLiteChains([mockOriginSpokePoolClient.chainId]); - mockOriginSpokePoolClient.configStoreClient = hubPoolClient.configStoreClient; - // Create three valid deposits - depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); - depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); - depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDeposits(); - - // Fill deposits from different relayers - const invalidRelayer = ethers.utils.randomBytes(32); - fillV3Events.push(generateV3FillFromDeposit(deposits[0])); - fillV3Events.push(generateV3FillFromDeposit(deposits[1])); - fillV3Events.push(generateV3FillFromDeposit(deposits[2], {}, invalidRelayer)); - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); - // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so - // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, - // set the msg.sender as an invalid address. - const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); - const spokeWrapper = new Contract( - mockDestinationSpokePoolClient.spokePool.address, - mockDestinationSpokePoolClient.spokePool.interface, - provider - ); - fillV3Events.forEach((event) => provider._setTransaction(event.transactionHash, { from: invalidRelayer })); - mockDestinationSpokePoolClient.spokePool = spokeWrapper; - - // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will - // query relayStatuses() on the spoke pool. - const invalidFill = mockDestinationSpokePoolClient - .getFills() - .find((e) => e.depositId.eq(fillV3Events[2].args.depositId)); - const invalidFilledDeposit = deposits.find((e) => e.depositId.eq(invalidFill!.depositId)); - mockDestinationSpokePoolClient.setRelayFillStatus(invalidFilledDeposit!, interfaces.FillStatus.Filled); - // Also mock the matched fill event so that BundleDataClient doesn't query for it. - (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( - invalidFilledDeposit!, - invalidFill! - ); - - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - - // We expect the last fillV3Event to be invalid. - const nValidFills = fillV3Events.length - 1; - expect(data1.bundleFillsV3[originChainId][erc20_1.address].fills.length).to.equal(nValidFills); - expect(data1.bundleFillsV3[originChainId][erc20_1.address].fills.map((e) => e.depositId)).to.deep.equal( - fillV3Events.slice(0, nValidFills).map((event) => event.args.depositId) - ); - expect(data1.bundleFillsV3[originChainId][erc20_1.address].fills.map((e) => e.lpFeePct)).to.deep.equal( - fillV3Events.slice(0, nValidFills).map(() => lpFeePct) - ); - expect(spyLogIncludes(spy, -2, "invalid V3 fills in range")).to.be.true; - }); }); }); diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 6a0c0523e0..a11a69ec97 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -37,6 +37,8 @@ let hubPoolClient: HubPoolClient, configStoreClient: ConfigStoreClient; let dataworkerInstance: Dataworker; let spokePoolClients: { [chainId: number]: SpokePoolClient }; +let spy: sinon.SinonSpy; + let updateAllClients: () => Promise; describe("BundleDataClient: Pre-fill logic", async function () { @@ -46,6 +48,7 @@ describe("BundleDataClient: Pre-fill logic", async function () { erc20_2, hubPoolClient, configStoreClient, + spy, l1Token_1, relayer, depositor, @@ -279,6 +282,87 @@ describe("BundleDataClient: Pre-fill logic", async function () { expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal(validRelayerAddress); }); + it("Does not refund fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for repayment chain", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Send fill with invalid repayment address + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will + // query relayStatuses() on the spoke pool. + const invalidFill = mockDestinationSpokePoolClient.getFills()[0]; + mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); + // Also mock the matched fill event so that BundleDataClient doesn't query for it. + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + deposits[0], + invalidFill + ); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + // We expect the fillV3Event to be invalid. + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(1); + }); + it("Does not refund lite chain fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for origin chain", async function () { + generateV3Deposit({ outputToken: randomAddress(), fromLiteChain: true }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Fill deposits from different relayers + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will + // query relayStatuses() on the spoke pool. + const invalidFill = mockDestinationSpokePoolClient.getFills()[0]; + mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); + // Also mock the matched fill event so that BundleDataClient doesn't query for it. + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + deposits[0], + invalidFill + ); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(1); + }); + it("Refunds pre-fills for duplicate deposits", async function () { // In this test, we send multiple fills per deposit. We assume you cannot send more than one fill per deposit. const deposit = generateV3Deposit({ outputToken: randomAddress() }); From 763e27cc47183138ba0eba81df30159b4990b65e Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 13:40:20 -0500 Subject: [PATCH 37/62] WIP --- scripts/withdrawFromOpStack.ts | 7 +- test/Dataworker.loadData.fill.ts | 73 +++++++++++++++++++ test/Dataworker.loadData.prefill.ts | 18 +++-- ...ataworker.loadData.unexecutableSlowFill.ts | 43 ++++++++++- 4 files changed, 128 insertions(+), 13 deletions(-) diff --git a/scripts/withdrawFromOpStack.ts b/scripts/withdrawFromOpStack.ts index e4ba5041d9..80d0abddc0 100644 --- a/scripts/withdrawFromOpStack.ts +++ b/scripts/withdrawFromOpStack.ts @@ -97,8 +97,9 @@ export async function run(): Promise { "0x", // _data ]; + const functionNameToCall = l1TokenInfo.symbol === "ETH" ? "bridgeETHTo" : "bridgeERC20To"; console.log( - `Submitting bridgeETHTo on the OVM standard bridge @ ${ovmStandardBridge.address} with the following args: `, + `Submitting ${functionNameToCall} on the OVM standard bridge @ ${ovmStandardBridge.address} with the following args: `, ...bridgeArgs ); @@ -122,9 +123,7 @@ export async function run(): Promise { if (!(await askYesNoQuestion("\nDo you want to proceed?"))) { return; } - const withdrawal = await ovmStandardBridge[l1TokenInfo.symbol === "ETH" ? "bridgeETHTo" : "bridgeERC20To"]( - ...bridgeArgs - ); + const withdrawal = await ovmStandardBridge[functionNameToCall](...bridgeArgs); console.log(`Submitted withdrawal: ${blockExplorerLink(withdrawal.hash, chainId)}.`); const receipt = await withdrawal.wait(); console.log("Receipt", receipt); diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index b5a3ff0426..26010c4317 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -251,6 +251,79 @@ describe("Dataworker: Load data used in all functions", async function () { expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(0); }); + it("Sends duplicate deposit refunds for fills in bundle", async function () { + // Send duplicate deposits. + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(3); + + // Fill deposit. + generateV3FillFromDeposit(deposits[0]); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + + // Bundle should contain all deposits. + // Bundle should refund fill. + // Bundle should refund duplicate deposits. + const bundleBlockRanges = getDefaultBlockRange(5); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + dupe1.transactionHash + ); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( + dupe2.transactionHash + ); + }); + + it("Sends duplicate deposit refunds even if relayer repayment information is invalid", async function () { + // Send duplicate deposits. + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(3); + + // Fill deposit with invalid repayment information. + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + // Bundle should contain all deposits. + // Bundle should not refund any fills + // Bundle should refund duplicate deposits. + const bundleBlockRanges = getDefaultBlockRange(5); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(1); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + dupe1.transactionHash + ); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( + dupe2.transactionHash + ); + }); + it("Does not create unexecutable slow fill for zero value deposit", async function () { generateV3Deposit({ inputAmount: bnZero, diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index a11a69ec97..50aae51d3c 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -290,7 +290,11 @@ describe("BundleDataClient: Pre-fill logic", async function () { // Send fill with invalid repayment address const invalidRelayer = ethers.utils.randomBytes(32); const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + const invalidFill = { + ...spreadEventWithBlockNumber(invalidFillEvent), + destinationChainId, + } as FillWithBlock; + await mockDestinationSpokePoolClient.update([]); // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, @@ -306,7 +310,6 @@ describe("BundleDataClient: Pre-fill logic", async function () { // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will // query relayStatuses() on the spoke pool. - const invalidFill = mockDestinationSpokePoolClient.getFills()[0]; mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); // Also mock the matched fill event so that BundleDataClient doesn't query for it. (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( @@ -319,9 +322,7 @@ describe("BundleDataClient: Pre-fill logic", async function () { spokePoolClients ); - // We expect the fillV3Event to be invalid. expect(data1.bundleFillsV3).to.deep.equal({}); - expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(1); }); it("Does not refund lite chain fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for origin chain", async function () { generateV3Deposit({ outputToken: randomAddress(), fromLiteChain: true }); @@ -331,7 +332,12 @@ describe("BundleDataClient: Pre-fill logic", async function () { // Fill deposits from different relayers const invalidRelayer = ethers.utils.randomBytes(32); const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + const invalidFill = { + ...spreadEventWithBlockNumber(invalidFillEvent), + destinationChainId, + } as FillWithBlock; + await mockDestinationSpokePoolClient.update([]); + await mockDestinationSpokePoolClient.update([]); // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, // set the msg.sender as an invalid address. @@ -346,7 +352,6 @@ describe("BundleDataClient: Pre-fill logic", async function () { // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will // query relayStatuses() on the spoke pool. - const invalidFill = mockDestinationSpokePoolClient.getFills()[0]; mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); // Also mock the matched fill event so that BundleDataClient doesn't query for it. (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( @@ -360,7 +365,6 @@ describe("BundleDataClient: Pre-fill logic", async function () { ); expect(data1.bundleFillsV3).to.deep.equal({}); - expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(1); }); it("Refunds pre-fills for duplicate deposits", async function () { diff --git a/test/Dataworker.loadData.unexecutableSlowFill.ts b/test/Dataworker.loadData.unexecutableSlowFill.ts index 3c8ee39388..2f88e5c1d1 100644 --- a/test/Dataworker.loadData.unexecutableSlowFill.ts +++ b/test/Dataworker.loadData.unexecutableSlowFill.ts @@ -24,9 +24,9 @@ import { } from "./utils"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { getCurrentTime, toBNWei, assert, ZERO_ADDRESS } from "../src/utils"; +import { getCurrentTime, toBNWei, assert, ZERO_ADDRESS, bnZero } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils } from "@across-protocol/sdk"; +import { interfaces, providers, utils as sdkUtils } from "@across-protocol/sdk"; describe("BundleDataClient: Expired deposit and Slow Fill interactions", async function () { let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; @@ -274,6 +274,45 @@ describe("BundleDataClient: Expired deposit and Slow Fill interactions", async f ).to.deep.equal([depositWithMissingSlowFillRequest.depositId, eligibleSlowFills[0].depositId].sort()); }); + it("Creates unexecutable slow fill even if fast fill repayment information is invalid", async function () { + generateV3Deposit({ outputToken: erc20_2.address }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Fill deposit but don't mine requested slow fill event. This makes BundleDataClient think that deposit's slow + // fill request was sent in a prior bundle. This simulates the situation where the slow fill request + // was sent in a prior bundle to the fast fill. + + // Fill deposits with invalid repayment information + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit( + deposits[0], + {}, + invalidRelayer, + undefined, + interfaces.FillType.ReplacedSlowFill + ); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(getDefaultBlockRange(5), spokePoolClients); + + // The fill cannot be refunded but there is still an unexecutable slow fill leaf we need to refund. + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.unexecutableSlowFills[destinationChainId][erc20_2.address].length).to.equal(1); + }); + it("Handles fast fills replacing invalid slow fill request from older bundles", async function () { // Create a Lite chain to test that slow fill requests involving lite chains are ignored. mockConfigStore.updateGlobalConfig( From e55238143a34cca8038db1261550d5b2960dc564 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 13:45:03 -0500 Subject: [PATCH 38/62] Update Dataworker.loadData.prefill.ts --- test/Dataworker.loadData.prefill.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 50aae51d3c..56cdc12d82 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -37,8 +37,6 @@ let hubPoolClient: HubPoolClient, configStoreClient: ConfigStoreClient; let dataworkerInstance: Dataworker; let spokePoolClients: { [chainId: number]: SpokePoolClient }; -let spy: sinon.SinonSpy; - let updateAllClients: () => Promise; describe("BundleDataClient: Pre-fill logic", async function () { @@ -48,7 +46,6 @@ describe("BundleDataClient: Pre-fill logic", async function () { erc20_2, hubPoolClient, configStoreClient, - spy, l1Token_1, relayer, depositor, From 96007bfa85a2e9b7d33126457dc2b217f833847e Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 13:46:56 -0500 Subject: [PATCH 39/62] update --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7854eee7f2..e51c4d64a2 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.17", + "@across-protocol/sdk": "^4.0.0-beta.18", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index eecf74548c..808354f377 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.17": - version "4.0.0-beta.17" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.17.tgz#c9673f2d756680e4f2c98a862fb1bdffed251d4e" - integrity sha512-DnC7OSpeBzNO6gllyQQG776s6zTHn2OcQDUAyCda+5rNtT32F61YdcOMCVJY9JkQIczkFnx5wVV45E6UJzHzNg== +"@across-protocol/sdk@^4.0.0-beta.18": + version "4.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.18.tgz#679af7bb7f2ced42559555d9e8eea06d27ff42c7" + integrity sha512-9RbH6GSCiQ4lnMgNJbzxdT3SvzlTCF1R2SQ3hX0DfdiWl6ja/+JQ01+J9KnbUpYhHCxQ91/th0LgVnnS26creQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 3f433678537f9e26e056711cc296c24a770339a0 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 17:44:15 -0500 Subject: [PATCH 40/62] WIP --- test/Dataworker.loadData.deposit.ts | 15 +- test/Dataworker.loadData.fill.ts | 47 ++++++- test/Dataworker.loadData.prefill.ts | 132 ++++++++++++++++-- test/Dataworker.loadData.slowFill.ts | 2 +- ...ataworker.loadData.unexecutableSlowFill.ts | 2 +- 5 files changed, 173 insertions(+), 25 deletions(-) diff --git a/test/Dataworker.loadData.deposit.ts b/test/Dataworker.loadData.deposit.ts index 71967050c6..141704308c 100644 --- a/test/Dataworker.loadData.deposit.ts +++ b/test/Dataworker.loadData.deposit.ts @@ -35,7 +35,7 @@ let spy: sinon.SinonSpy; let updateAllClients: () => Promise; // TODO: Rename this file to BundleDataClient -describe("Dataworker: Load data used in all functions", async function () { +describe("Dataworker: Load bundle data", async function () { beforeEach(async function () { ({ spokePool_1, @@ -57,7 +57,7 @@ describe("Dataworker: Load data used in all functions", async function () { bundleDataClient = dataworkerInstance.clients.bundleDataClient; }); - describe("V3 Events", function () { + describe("Computing bundle deposits and expired deposits to refund", function () { let mockOriginSpokePoolClient: MockSpokePoolClient, mockDestinationSpokePoolClient: MockSpokePoolClient; let mockHubPoolClient: MockHubPoolClient; let mockDestinationSpokePool: FakeContract; @@ -292,11 +292,11 @@ describe("Dataworker: Load data used in all functions", async function () { expect(data1.bundleDepositsV3[originChainId][erc20_1.address][0].depositId).to.equal(deposits[1].args.depositId); }); it("Includes duplicate deposits in bundle data", async function () { - generateV3Deposit(); + const deposit = generateV3Deposit(); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const depositToDuplicate = mockOriginSpokePoolClient.getDeposits()[0]; - mockOriginSpokePoolClient.depositV3(depositToDuplicate); - mockOriginSpokePoolClient.depositV3(depositToDuplicate); + const dupe1 = mockOriginSpokePoolClient.depositV3(depositToDuplicate); + const dupe2 = mockOriginSpokePoolClient.depositV3(depositToDuplicate); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); expect( mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId).length @@ -306,6 +306,11 @@ describe("Dataworker: Load data used in all functions", async function () { spokePoolClients ); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + deposit.transactionHash + ); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address][1].transactionHash).to.equal(dupe1.transactionHash); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address][2].transactionHash).to.equal(dupe2.transactionHash); }); it("Filters duplicate deposits out of block range", async function () { const deposit = generateV3Deposit({ blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1 }); diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 26010c4317..ac5104b82d 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -46,8 +46,7 @@ let spy: sinon.SinonSpy; let updateAllClients: () => Promise; -// TODO: Rename this file to BundleDataClient -describe("Dataworker: Load data used in all functions", async function () { +describe("Dataworker: Load bundle data", async function () { beforeEach(async function () { ({ spokePool_1, @@ -91,7 +90,7 @@ describe("Dataworker: Load data used in all functions", async function () { }); }); - describe("V3 Events", function () { + describe("Compute fills to refund", function () { let mockOriginSpokePoolClient: MockSpokePoolClient, mockDestinationSpokePoolClient: MockSpokePoolClient; let mockHubPoolClient: MockHubPoolClient; let mockDestinationSpokePool: FakeContract; @@ -281,6 +280,48 @@ describe("Dataworker: Load data used in all functions", async function () { ); }); + it("Does not account for duplicate deposit refunds for deposits after bundle block range", async function () { + generateV3Deposit({ + outputToken: randomAddress(), + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1, + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const dupe1 = await mockOriginSpokePoolClient.depositV3({ + ...mockOriginSpokePoolClient.getDeposits()[0], + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, + }); + await mockOriginSpokePoolClient.depositV3({ + ...mockOriginSpokePoolClient.getDeposits()[0], + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 21, + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(3); + + const fill = generateV3FillFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber + 21, + }); + + // Create a block range that removes latest event + const destinationChainBlockRange = [fill.blockNumber - 1, fill.blockNumber + 1]; + const originChainBlockRange = [deposits[0].blockNumber, deposits[1].blockNumber]; + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + bundleBlockRanges[originChainIndex] = originChainBlockRange; + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + dupe1.transactionHash + ); + }); + it("Sends duplicate deposit refunds even if relayer repayment information is invalid", async function () { // Send duplicate deposits. generateV3Deposit({ outputToken: randomAddress() }); diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 56cdc12d82..941356e89f 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -39,7 +39,7 @@ let spokePoolClients: { [chainId: number]: SpokePoolClient }; let updateAllClients: () => Promise; -describe("BundleDataClient: Pre-fill logic", async function () { +describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic", async function () { beforeEach(async function () { ({ erc20_1, @@ -364,14 +364,15 @@ describe("BundleDataClient: Pre-fill logic", async function () { expect(data1.bundleFillsV3).to.deep.equal({}); }); - it("Refunds pre-fills for duplicate deposits", async function () { - // In this test, we send multiple fills per deposit. We assume you cannot send more than one fill per deposit. + it("Refunds pre-fills in-memory for duplicate deposits", async function () { + // In this test, we send multiple deposits per fill. We assume you cannot send more than one fill per deposit. const deposit = generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); + const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); - expect(deposits.length).to.equal(2); + expect(deposits.length).to.equal(3); // Submit fill that we won't include in the bundle block range. const fill = generateV3FillFromDeposit(deposits[0], { @@ -386,15 +387,117 @@ describe("BundleDataClient: Pre-fill logic", async function () { await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); - // This should return two refunds for the two deposits. + // This should return one refund for for the pre-fill and one refund for each duplicate deposit + // All deposits should be in the bundle. const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(2); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( deposit.args.depositId ); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[1].depositId).to.equal( + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + dupe1.transactionHash + ); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( + dupe2.transactionHash + ); + }); + + it("Does not refund duplicate deposit for pre-fill if duplicate is from older bundle", async function () { + // In this test, we send multiple deposits per fill. We assume you cannot send more than one fill per deposit. + generateV3Deposit({ + outputToken: randomAddress(), + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1, + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposit = await mockOriginSpokePoolClient.depositV3({ + ...mockOriginSpokePoolClient.getDeposits()[0], + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, + }); + const dupe2 = await mockOriginSpokePoolClient.depositV3({ + ...mockOriginSpokePoolClient.getDeposits()[0], + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 21, + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(3); + + // Submit fill that we won't include in the bundle block range. + const fill = generateV3FillFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, + }); + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = [fill.blockNumber + 1, fill.blockNumber + 2]; + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + bundleBlockRanges[originChainIndex] = [deposit.blockNumber, dupe2.blockNumber]; + + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); + + // This should return one refund for for the pre-fill and one refund for each duplicate deposit + // in the bundle. + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( deposit.args.depositId ); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + dupe2.transactionHash + ); + }); + + it("Refunds duplicate deposits if pre-fill is not in memory", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); + const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(3); + + // Submit fill that we won't include in the bundle block range + const fill = generateV3FillFromDeposit(deposits[0]); + const fillWithBlock = { + ...spreadEventWithBlockNumber(fill), + destinationChainId, + } as FillWithBlock; + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + deposits[0], + fillWithBlock + ); + + // Don't include the fill event in the update so that the bundle data client is forced to load the event + // fresh. + await mockDestinationSpokePoolClient.update([]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(0); + + // Mock FillStatus to be Filled so that the BundleDataClient searches for event. + mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); + + // The fill is a pre-fill because its earlier than the bundle block range. Because its corresponding + // deposit is in the block range, we should refund the pre-fill once and any duplicate deposits. + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( + fill.args.depositId + ); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + dupe1.transactionHash + ); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( + dupe2.transactionHash + ); }); it("Does not refund fill if fill is in-memory but in a future bundle", async function () { @@ -475,9 +578,7 @@ describe("BundleDataClient: Pre-fill logic", async function () { ); }); - it("Creates leaves for duplicate deposits", async function () { - // In this test, we should create multiple leaves per duplicate deposit. - // We assume you cannot send more than one request per deposit. + it("Creates one leaf for duplicate deposits", async function () { const deposit = generateV3Deposit({ outputToken: erc20_2.address }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit @@ -499,13 +600,10 @@ describe("BundleDataClient: Pre-fill logic", async function () { expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(1); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address].length).to.equal(2); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address].length).to.equal(1); expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].depositId).to.equal( deposit.args.depositId ); - expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][1].depositId).to.equal( - deposit.args.depositId - ); }); it("Does not create slow fill leaf if slow fill request is in-memory but an invalid request", async function () { @@ -541,6 +639,10 @@ describe("BundleDataClient: Pre-fill logic", async function () { it("Creates slow fill leaf if fill status is RequestedSlowFill", async function () { const deposit = generateV3Deposit({ outputToken: erc20_2.address }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(2); const depositHash = sdkUtils.getRelayHashFromEvent(mockOriginSpokePoolClient.getDeposits()[0]); diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index fbe14a5c51..2dd7702d60 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -31,7 +31,7 @@ import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@acros import { cloneDeep } from "lodash"; import { INFINITE_FILL_DEADLINE } from "../src/common"; -describe("BundleDataClient: Slow fill handling & validation", async function () { +describe("Dataworker: Load bundle data: Computing slow fills", async function () { let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; let l1Token_1: Contract; let depositor: SignerWithAddress, relayer: SignerWithAddress; diff --git a/test/Dataworker.loadData.unexecutableSlowFill.ts b/test/Dataworker.loadData.unexecutableSlowFill.ts index 2f88e5c1d1..c48b472613 100644 --- a/test/Dataworker.loadData.unexecutableSlowFill.ts +++ b/test/Dataworker.loadData.unexecutableSlowFill.ts @@ -28,7 +28,7 @@ import { getCurrentTime, toBNWei, assert, ZERO_ADDRESS, bnZero } from "../src/ut import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; import { interfaces, providers, utils as sdkUtils } from "@across-protocol/sdk"; -describe("BundleDataClient: Expired deposit and Slow Fill interactions", async function () { +describe("Dataworker: Load bundle data: Computing unexecutable slow fills", async function () { let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; let l1Token_1: Contract; let depositor: SignerWithAddress, relayer: SignerWithAddress; From f25b7c40fdd7060c1c0f40d0e9fc0e8c74ea4446 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 18:10:46 -0500 Subject: [PATCH 41/62] fix --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e51c4d64a2..0cc1a55f23 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.18", + "@across-protocol/sdk": "^4.0.0-beta.19", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 808354f377..17770d582f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.18": - version "4.0.0-beta.18" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.18.tgz#679af7bb7f2ced42559555d9e8eea06d27ff42c7" - integrity sha512-9RbH6GSCiQ4lnMgNJbzxdT3SvzlTCF1R2SQ3hX0DfdiWl6ja/+JQ01+J9KnbUpYhHCxQ91/th0LgVnnS26creQ== +"@across-protocol/sdk@^4.0.0-beta.19": + version "4.0.0-beta.19" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.19.tgz#687a7f861090db004064e8cbcf1d45b657f157d6" + integrity sha512-f7mZ83VbwR2YG7v8+t6L99cZmDalztjDoeaHMqtFEgJjFrWoUyUDawFcAYutq85TVUetHXwP7bQnZTyyrSceyA== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 18a2b9d52c9cb330a9e56eed3227fadc44bda4df Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 19:10:43 -0500 Subject: [PATCH 42/62] wip --- package.json | 2 +- test/Dataworker.loadData.fill.ts | 2 +- test/Dataworker.loadData.prefill.ts | 5 +++ test/Dataworker.loadData.slowFill.ts | 44 ++++++++++++++++++- ...ataworker.loadData.unexecutableSlowFill.ts | 5 +++ yarn.lock | 8 ++-- 6 files changed, 59 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 0cc1a55f23..208abd25b7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.19", + "@across-protocol/sdk": "^4.0.0-beta.20", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index ac5104b82d..a476319d56 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -354,7 +354,7 @@ describe("Dataworker: Load bundle data", async function () { const bundleBlockRanges = getDefaultBlockRange(5); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); expect(data1.bundleFillsV3).to.deep.equal({}); - expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(1); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 941356e89f..6f9a8d1721 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -37,6 +37,8 @@ let hubPoolClient: HubPoolClient, configStoreClient: ConfigStoreClient; let dataworkerInstance: Dataworker; let spokePoolClients: { [chainId: number]: SpokePoolClient }; +let spy: sinon.SinonSpy; + let updateAllClients: () => Promise; describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic", async function () { @@ -46,6 +48,7 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic erc20_2, hubPoolClient, configStoreClient, + spy, l1Token_1, relayer, depositor, @@ -320,6 +323,7 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic ); expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); }); it("Does not refund lite chain fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for origin chain", async function () { generateV3Deposit({ outputToken: randomAddress(), fromLiteChain: true }); @@ -362,6 +366,7 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic ); expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); }); it("Refunds pre-fills in-memory for duplicate deposits", async function () { diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index 2dd7702d60..8a8f455493 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -27,7 +27,7 @@ import { import { Dataworker } from "../src/dataworker/Dataworker"; // Tested import { getCurrentTime, toBNWei, ZERO_ADDRESS, bnZero } from "../src/utils"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient } from "./mocks"; -import { interfaces, utils as sdkUtils, constants as sdkConstants } from "@across-protocol/sdk"; +import { interfaces, utils as sdkUtils, constants as sdkConstants, providers } from "@across-protocol/sdk"; import { cloneDeep } from "lodash"; import { INFINITE_FILL_DEADLINE } from "../src/common"; @@ -692,4 +692,46 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () expect(data1.bundleSlowFillsV3).to.deep.equal({}); }); + + it("Does not create a slow fill leaf if fast fill follows slow fill request but repayment information is invalid", async function () { + generateV3Deposit({ outputToken: erc20_2.address }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + generateSlowFillRequestFromDeposit(deposits[0]); + + // Fill deposits with invalid repayment information + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit( + deposits[0], + {}, + invalidRelayer, + undefined, + interfaces.FillType.ReplacedSlowFill + ); + await mockDestinationSpokePoolClient.update(["FilledV3Relay", "RequestedV3SlowFill"]); + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(getDefaultBlockRange(5), spokePoolClients); + + // Slow fill request, fast fill and deposit should all be in same bundle. Test that the bundle data client is + // aware of the fill, even if its invalid, and therefore doesn't create a slow fill leaf. + + // The fill cannot be refunded but there is still an unexecutable slow fill leaf we need to refund. + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.bundleSlowFillsV3).to.deep.equal({}); + const logs = spy.getCalls().filter((x) => x.lastArg.message.includes("unrepayable")); + expect(logs.length).to.equal(1); + }); }); diff --git a/test/Dataworker.loadData.unexecutableSlowFill.ts b/test/Dataworker.loadData.unexecutableSlowFill.ts index c48b472613..3f9acead49 100644 --- a/test/Dataworker.loadData.unexecutableSlowFill.ts +++ b/test/Dataworker.loadData.unexecutableSlowFill.ts @@ -38,6 +38,8 @@ describe("Dataworker: Load bundle data: Computing unexecutable slow fills", asyn let dataworkerInstance: Dataworker; let spokePoolClients: { [chainId: number]: SpokePoolClient }; + let spy: sinon.SinonSpy; + let updateAllClients: () => Promise; let mockOriginSpokePoolClient: MockSpokePoolClient, mockDestinationSpokePoolClient: MockSpokePoolClient; @@ -101,6 +103,7 @@ describe("Dataworker: Load bundle data: Computing unexecutable slow fills", asyn spokePool_2, erc20_2, configStoreClient, + spy, hubPoolClient, l1Token_1, depositor, @@ -311,6 +314,8 @@ describe("Dataworker: Load bundle data: Computing unexecutable slow fills", asyn expect(data1.bundleFillsV3).to.deep.equal({}); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); expect(data1.unexecutableSlowFills[destinationChainId][erc20_2.address].length).to.equal(1); + const logs = spy.getCalls().filter((x) => x.lastArg.message.includes("unrepayable")); + expect(logs.length).to.equal(1); }); it("Handles fast fills replacing invalid slow fill request from older bundles", async function () { diff --git a/yarn.lock b/yarn.lock index 17770d582f..fc3100af0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.19": - version "4.0.0-beta.19" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.19.tgz#687a7f861090db004064e8cbcf1d45b657f157d6" - integrity sha512-f7mZ83VbwR2YG7v8+t6L99cZmDalztjDoeaHMqtFEgJjFrWoUyUDawFcAYutq85TVUetHXwP7bQnZTyyrSceyA== +"@across-protocol/sdk@^4.0.0-beta.20": + version "4.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.20.tgz#d7528e207c3187fbe25a443c0307c8cdb7c05e86" + integrity sha512-8RmJ7XttaZ8epo1ZTORCf5YBbxcSpeUhKOcoajau+67dkW1NEyrdUhm/oYmYLfiiyI9Chp+kf9qGVuoDeIsRZg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 649c5f304270b4b8a46167ce317ecc87e0527d7b Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 19:20:16 -0500 Subject: [PATCH 43/62] update --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 208abd25b7..811ce862fd 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.20", + "@across-protocol/sdk": "^4.0.0-beta.21", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index fc3100af0c..13454ba945 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.20": - version "4.0.0-beta.20" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.20.tgz#d7528e207c3187fbe25a443c0307c8cdb7c05e86" - integrity sha512-8RmJ7XttaZ8epo1ZTORCf5YBbxcSpeUhKOcoajau+67dkW1NEyrdUhm/oYmYLfiiyI9Chp+kf9qGVuoDeIsRZg== +"@across-protocol/sdk@^4.0.0-beta.21": + version "4.0.0-beta.21" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.21.tgz#c464f7ded8cf10a84c12e021f5d3402158f5e6f8" + integrity sha512-p58mfm3RziXOVUO2KtsvwwRLL7gOpfE1V0YCcKwc6fg0l0IbeojlNKyTai/7mHJQvlJxT3lQ+fQewceItCIFuw== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 43d73f59cef59ba4226b2931bafc332aa04d9e4a Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 19:53:21 -0500 Subject: [PATCH 44/62] fix --- package.json | 2 +- test/Dataworker.loadData.prefill.ts | 40 ++++++++++++++++++++++++++++- yarn.lock | 8 +++--- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 811ce862fd..76bb1c1e26 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.21", + "@across-protocol/sdk": "^4.0.0-beta.22", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 6f9a8d1721..799179844a 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -282,6 +282,43 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal(validRelayerAddress); }); + it("Does not refund fill if fill is not in-memory and is a slow fill", async function() { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit fill that we won't include in the bundle block range. + const fill = generateV3FillFromDeposit(deposits[0], {}, + undefined, + undefined, + interfaces.FillType.SlowFill); + const fillWithBlock = { + ...spreadEventWithBlockNumber(fill), + destinationChainId, + } as FillWithBlock; + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + deposits[0], + fillWithBlock + ); + + // Don't include the fill event in the update so that the bundle data client is forced to load the event + // fresh. + await mockDestinationSpokePoolClient.update([]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(0); + + // Mock FillStatus to be Filled so that the BundleDataClient searches for event. + mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); + + // The fill is a pre-fill because its earlier than the bundle block range. Because its corresponding + // deposit is in the block range, we should refund it. + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].refunds).to.deep.equal({}); + expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].realizedLpFees).to.gt(0); + }) + it("Does not refund fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for repayment chain", async function () { generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); @@ -552,7 +589,8 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleFillsV3).to.deep.equal({}); + expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].refunds).to.deep.equal({}); + expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].realizedLpFees).to.gt(0); }); }); diff --git a/yarn.lock b/yarn.lock index 13454ba945..04263dd794 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.21": - version "4.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.21.tgz#c464f7ded8cf10a84c12e021f5d3402158f5e6f8" - integrity sha512-p58mfm3RziXOVUO2KtsvwwRLL7gOpfE1V0YCcKwc6fg0l0IbeojlNKyTai/7mHJQvlJxT3lQ+fQewceItCIFuw== +"@across-protocol/sdk@^4.0.0-beta.22": + version "4.0.0-beta.22" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.22.tgz#104e858bba48496eabd5a3e3a4af46ac0a04b473" + integrity sha512-MRmNRZBRLUzWHeQfmQ/7PMJKABkmqvt/gt9M/SpEup7lPZI3Hyae2wPXaQpTHKm4LhO5I6mlBXe2/TJWs0AoCA== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 2694774b95d8dc2fb7d1b59c51999da2b1a6701f Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 20:01:33 -0500 Subject: [PATCH 45/62] Update Dataworker.loadData.prefill.ts --- test/Dataworker.loadData.prefill.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 799179844a..7935f55af3 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -282,16 +282,13 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal(validRelayerAddress); }); - it("Does not refund fill if fill is not in-memory and is a slow fill", async function() { + it("Does not refund fill if fill is not in-memory and is a slow fill", async function () { generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); - // Submit fill that we won't include in the bundle block range. - const fill = generateV3FillFromDeposit(deposits[0], {}, - undefined, - undefined, - interfaces.FillType.SlowFill); + // Submit fill that we won't include in the bundle block range. + const fill = generateV3FillFromDeposit(deposits[0], {}, undefined, undefined, interfaces.FillType.SlowFill); const fillWithBlock = { ...spreadEventWithBlockNumber(fill), destinationChainId, @@ -317,7 +314,7 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic ); expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].refunds).to.deep.equal({}); expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].realizedLpFees).to.gt(0); - }) + }); it("Does not refund fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for repayment chain", async function () { generateV3Deposit({ outputToken: randomAddress() }); From 8e3c775ad0081324b47be38d7b440bcc3ecf8f37 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 30 Jan 2025 20:13:06 -0500 Subject: [PATCH 46/62] fix --- test/Dataworker.loadData.fill.ts | 4 ++-- test/Dataworker.loadData.slowFill.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index a476319d56..e7e23cf739 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -237,7 +237,7 @@ describe("Dataworker: Load bundle data", async function () { generateV3FillFromDeposit(deposits[0]); generateV3FillFromDeposit({ ...deposits[1], - message: sdkConstants.EMPTY_MESSAGE_HASH, + message: sdkConstants.ZERO_BYTES, }); await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); @@ -389,7 +389,7 @@ describe("Dataworker: Load bundle data", async function () { generateV3FillFromDeposit( { ...deposits[1], - message: sdkConstants.EMPTY_MESSAGE_HASH, + message: sdkConstants.ZERO_BYTES, }, undefined, undefined, diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index 8a8f455493..6bd1584feb 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -685,7 +685,7 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () generateSlowFillRequestFromDeposit(deposits[0]); generateSlowFillRequestFromDeposit({ ...deposits[1], - message: sdkConstants.EMPTY_MESSAGE_HASH, + message: sdkConstants.ZERO_BYTES, }); await mockDestinationSpokePoolClient.update(["RequestedV3SlowFill"]); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(getDefaultBlockRange(5), spokePoolClients); From 3bfc2e154dab34acf5d7f329fc306d763f13dbfc Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 31 Jan 2025 12:28:17 -0500 Subject: [PATCH 47/62] bump package to new duplicate refund version --- package.json | 2 +- test/Dataworker.loadData.fill.ts | 101 ++------------------------ test/Dataworker.loadData.prefill.ts | 108 ++++++---------------------- yarn.lock | 8 +-- 4 files changed, 33 insertions(+), 186 deletions(-) diff --git a/package.json b/package.json index 76bb1c1e26..e23c3f11bf 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.22", + "@across-protocol/sdk": "^4.0.0-beta.23", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index e7e23cf739..05c2823d99 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -250,12 +250,12 @@ describe("Dataworker: Load bundle data", async function () { expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(0); }); - it("Sends duplicate deposit refunds for fills in bundle", async function () { + it("Duplicate deposits do not counfound fill refunds", async function () { // Send duplicate deposits. generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit - const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); expect(deposits.length).to.equal(3); @@ -266,103 +266,12 @@ describe("Dataworker: Load bundle data", async function () { // Bundle should contain all deposits. // Bundle should refund fill. - // Bundle should refund duplicate deposits. + // Bundle should not refund duplicate deposits. const bundleBlockRanges = getDefaultBlockRange(5); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( - dupe1.transactionHash - ); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( - dupe2.transactionHash - ); - }); - - it("Does not account for duplicate deposit refunds for deposits after bundle block range", async function () { - generateV3Deposit({ - outputToken: randomAddress(), - blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1, - }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const dupe1 = await mockOriginSpokePoolClient.depositV3({ - ...mockOriginSpokePoolClient.getDeposits()[0], - blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, - }); - await mockOriginSpokePoolClient.depositV3({ - ...mockOriginSpokePoolClient.getDeposits()[0], - blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 21, - }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); - expect(deposits.length).to.equal(3); - - const fill = generateV3FillFromDeposit(deposits[0], { - blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber + 21, - }); - - // Create a block range that removes latest event - const destinationChainBlockRange = [fill.blockNumber - 1, fill.blockNumber + 1]; - const originChainBlockRange = [deposits[0].blockNumber, deposits[1].blockNumber]; - // Substitute bundle block ranges. - const bundleBlockRanges = getDefaultBlockRange(5); - const destinationChainIndex = - dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); - bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - bundleBlockRanges[originChainIndex] = originChainBlockRange; - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( - dupe1.transactionHash - ); - }); - - it("Sends duplicate deposit refunds even if relayer repayment information is invalid", async function () { - // Send duplicate deposits. - generateV3Deposit({ outputToken: randomAddress() }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit - const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); - expect(deposits.length).to.equal(3); - - // Fill deposit with invalid repayment information. - const invalidRelayer = ethers.utils.randomBytes(32); - const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); - // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so - // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, - // set the msg.sender as an invalid address. - const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); - const spokeWrapper = new Contract( - mockDestinationSpokePoolClient.spokePool.address, - mockDestinationSpokePoolClient.spokePool.interface, - provider - ); - provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); - mockDestinationSpokePoolClient.spokePool = spokeWrapper; - - // Bundle should contain all deposits. - // Bundle should not refund any fills - // Bundle should refund duplicate deposits. - const bundleBlockRanges = getDefaultBlockRange(5); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleFillsV3).to.deep.equal({}); - expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( - dupe1.transactionHash - ); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( - dupe2.transactionHash - ); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); }); it("Does not create unexecutable slow fill for zero value deposit", async function () { diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 7935f55af3..5def47908b 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -282,7 +282,7 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal(validRelayerAddress); }); - it("Does not refund fill if fill is not in-memory and is a slow fill", async function () { + it("Refunds deposit as a duplicate if fill is not in-memory and is a slow fill", async function () { generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); @@ -312,8 +312,9 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic getDefaultBlockRange(5), spokePoolClients ); - expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].refunds).to.deep.equal({}); - expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].realizedLpFees).to.gt(0); + expect(data1.bundleFillsV3).to.deep.equal({}); + // Should refund the deposit: + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); }); it("Does not refund fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for repayment chain", async function () { @@ -405,10 +406,10 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic it("Refunds pre-fills in-memory for duplicate deposits", async function () { // In this test, we send multiple deposits per fill. We assume you cannot send more than one fill per deposit. - const deposit = generateV3Deposit({ outputToken: randomAddress() }); + generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); - const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); expect(deposits.length).to.equal(3); @@ -426,76 +427,19 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); - // This should return one refund for for the pre-fill and one refund for each duplicate deposit - // All deposits should be in the bundle. + // This should return one refund for each pre-fill. const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( - deposit.args.depositId - ); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( - dupe1.transactionHash - ); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( - dupe2.transactionHash - ); - }); - - it("Does not refund duplicate deposit for pre-fill if duplicate is from older bundle", async function () { - // In this test, we send multiple deposits per fill. We assume you cannot send more than one fill per deposit. - generateV3Deposit({ - outputToken: randomAddress(), - blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1, - }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposit = await mockOriginSpokePoolClient.depositV3({ - ...mockOriginSpokePoolClient.getDeposits()[0], - blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, - }); - const dupe2 = await mockOriginSpokePoolClient.depositV3({ - ...mockOriginSpokePoolClient.getDeposits()[0], - blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 21, - }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); - expect(deposits.length).to.equal(3); - - // Submit fill that we won't include in the bundle block range. - const fill = generateV3FillFromDeposit(deposits[0], { - blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber, - }); - // Substitute bundle block ranges. - const bundleBlockRanges = getDefaultBlockRange(5); - const destinationChainIndex = - dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); - bundleBlockRanges[destinationChainIndex] = [fill.blockNumber + 1, fill.blockNumber + 2]; - const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); - bundleBlockRanges[originChainIndex] = [deposit.blockNumber, dupe2.blockNumber]; - - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); - expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); - - // This should return one refund for for the pre-fill and one refund for each duplicate deposit - // in the bundle. - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( - deposit.args.depositId - ); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( - dupe2.transactionHash - ); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(3); + // Duplicate deposits should not be refunded to depositor + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); }); it("Refunds duplicate deposits if pre-fill is not in memory", async function () { generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); - const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); expect(deposits.length).to.equal(3); @@ -520,23 +464,14 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); // The fill is a pre-fill because its earlier than the bundle block range. Because its corresponding - // deposit is in the block range, we should refund the pre-fill once and any duplicate deposits. + // deposit is in the block range, we refund the pre-fill once per duplicate deposit. const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( getDefaultBlockRange(5), spokePoolClients ); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( - fill.args.depositId - ); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( - dupe1.transactionHash - ); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( - dupe2.transactionHash - ); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(3); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); }); it("Does not refund fill if fill is in-memory but in a future bundle", async function () { @@ -562,7 +497,7 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(data1.bundleFillsV3).to.deep.equal({}); }); - it("Does not refund fill if fill is in-memory but its a SlowFill", async function () { + it("Refunds deposit as duplicate if fill is in-memory but its a SlowFill", async function () { generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); @@ -586,8 +521,9 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].refunds).to.deep.equal({}); - expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].realizedLpFees).to.gt(0); + expect(data1.bundleFillsV3).to.deep.equal({}); + // Should refund the deposit: + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); }); }); @@ -622,9 +558,10 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic const deposit = generateV3Deposit({ outputToken: erc20_2.address }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); - expect(deposits.length).to.equal(2); + expect(deposits.length).to.equal(3); // Submit request for prior bundle. const request = generateSlowFillRequestFromDeposit(deposits[0], { @@ -640,6 +577,7 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(mockDestinationSpokePoolClient.getSlowFillRequestsForOriginChain(originChainId).length).to.equal(1); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address].length).to.equal(1); expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].depositId).to.equal( deposit.args.depositId diff --git a/yarn.lock b/yarn.lock index 04263dd794..ef3aa2b224 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.22": - version "4.0.0-beta.22" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.22.tgz#104e858bba48496eabd5a3e3a4af46ac0a04b473" - integrity sha512-MRmNRZBRLUzWHeQfmQ/7PMJKABkmqvt/gt9M/SpEup7lPZI3Hyae2wPXaQpTHKm4LhO5I6mlBXe2/TJWs0AoCA== +"@across-protocol/sdk@^4.0.0-beta.23": + version "4.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.23.tgz#457addff771bfdd4f08af6255aa2cbd6ff642ce5" + integrity sha512-FKgOXMxJx/SA3WvCA5F6vFCrjGx2LRefcX+AQIraUI2AQ80TWZTAgKmWbxUnHq6zHwXgEHfBVZjtlavPkIL4wQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From e9c384d270e17c457f2b32050934739f59cd1450 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 31 Jan 2025 14:11:52 -0500 Subject: [PATCH 48/62] Add test cases for historical deposit query when matched deposit is in future bundle --- package.json | 2 +- test/Dataworker.loadData.fill.ts | 51 ++++++++++++++++++++++++++-- test/Dataworker.loadData.slowFill.ts | 49 +++++++++++++++++++++++++- yarn.lock | 8 ++--- 4 files changed, 102 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index e23c3f11bf..d13b21d60b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.23", + "@across-protocol/sdk": "^4.0.0-beta.25", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 05c2823d99..f2e34abc75 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -446,7 +446,7 @@ describe("Dataworker: Load bundle data", async function () { }); }); - it("Validates fill against old deposit", async function () { + it("Validates fill against old deposit if deposit is not in-memory", async function () { // For this test, we need to actually send a deposit on the spoke pool // because queryHistoricalDepositForFill eth_call's the contract. @@ -487,7 +487,54 @@ describe("Dataworker: Load bundle data", async function () { expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); expect(data1.bundleDepositsV3).to.deep.equal({}); }); - it("Validates fill from lite chain against old deposit", async function () { + it("Does not validate fill against deposit in future bundle if deposit is not in-memory", async function () { + // For this test, we need to actually send a deposit on the spoke pool + // because queryHistoricalDepositForFill eth_call's the contract. + + // Send a legacy deposit. + const depositObject = await depositV3( + spokePool_1, + destinationChainId, + depositor, + erc20_1.address, + amountToDeposit, + erc20_2.address, + amountToDeposit, + { + fillDeadline: INFINITE_FILL_DEADLINE.toNumber(), + } + ); + + // Modify the block ranges such that the deposit is in a future bundle block range. This should render + // the fill invalid. + const depositBlock = await spokePool_1.provider.getBlockNumber(); + const bundleBlockRanges = getDefaultBlockRange(5); + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + bundleBlockRanges[originChainIndex] = [depositBlock - 2, depositBlock - 1]; + + // Construct a spoke pool client with a small search range that would not include the deposit. + spokePoolClient_1.firstBlockToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + await spokePoolClient_1.update(); + const deposits = spokePoolClient_1.getDeposits(); + expect(deposits.length).to.equal(0); + + // Send a fill now and force the bundle data client to query for the historical deposit. + await fillV3Relay(spokePool_2, depositObject, relayer, repaymentChainId); + await updateAllClients(); + const fills = spokePoolClient_2.getFills(); + expect(fills.length).to.equal(1); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, { + ...spokePoolClients, + [originChainId]: spokePoolClient_1, + [destinationChainId]: spokePoolClient_2, + }); + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(1); + }); + it("Validates fill from lite chain against old bundle deposit", async function () { // For this test, we need to actually send a deposit on the spoke pool // because queryHistoricalDepositForFill eth_call's the contract. diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index 6bd1584feb..ab3cfa0562 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -285,7 +285,7 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () ); }); - it("Validates slow fill request against old deposit", async function () { + it("Validates slow fill request against old bundle deposit if deposit is not in-memory", async function () { // For this test, we need to actually send a deposit on the spoke pool // because queryHistoricalDepositForFill eth_call's the contract. @@ -327,6 +327,53 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () expect(data1.bundleDepositsV3).to.deep.equal({}); }); + it("Does not validate slow fill request against future bundle deposit if deposit is not in-memory", async function () { + // For this test, we need to actually send a deposit on the spoke pool + // because queryHistoricalDepositForFill eth_call's the contract. + + // Send a legacy deposit. + const depositObject = await depositV3( + spokePool_1, + destinationChainId, + depositor, + erc20_1.address, + amountToDeposit, + erc20_2.address, + amountToDeposit, + { + fillDeadline: INFINITE_FILL_DEADLINE.toNumber(), + } + ); + + // Modify the block ranges such that the deposit is in a future bundle block range. This should render + // the slow fill request invalid. + const depositBlock = await spokePool_1.provider.getBlockNumber(); + const bundleBlockRanges = getDefaultBlockRange(5); + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + bundleBlockRanges[originChainIndex] = [depositBlock - 2, depositBlock - 1]; + + // Construct a spoke pool client with a small search range that would not include the deposit. + spokePoolClient_1.firstBlockToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + await spokePoolClient_1.update(); + const deposits = spokePoolClient_1.getDeposits(); + expect(deposits.length).to.equal(0); + + // Send a slow fill request now and force the bundle data client to query for the historical deposit. + await requestSlowFill(spokePool_2, relayer, depositObject); + await updateAllClients(); + const requests = spokePoolClient_2.getSlowFillRequestsForOriginChain(originChainId); + expect(requests.length).to.equal(1); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, { + ...spokePoolClients, + [originChainId]: spokePoolClient_1, + [destinationChainId]: spokePoolClient_2, + }); + expect(data1.bundleSlowFillsV3).to.deep.equal({}); + expect(data1.bundleDepositsV3).to.deep.equal({}); + }); + it("Handles invalid slow fill requests with mismatching params from deposit", async function () { generateV3Deposit(); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); diff --git a/yarn.lock b/yarn.lock index ef3aa2b224..f0cf5626fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.23": - version "4.0.0-beta.23" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.23.tgz#457addff771bfdd4f08af6255aa2cbd6ff642ce5" - integrity sha512-FKgOXMxJx/SA3WvCA5F6vFCrjGx2LRefcX+AQIraUI2AQ80TWZTAgKmWbxUnHq6zHwXgEHfBVZjtlavPkIL4wQ== +"@across-protocol/sdk@^4.0.0-beta.25": + version "4.0.0-beta.25" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.25.tgz#c392e3ba9beddfbb596e19b3f9ae23ec7794a596" + integrity sha512-Dgz3XesdcK7Ow5t7nO4+wzPbpjE4MpVQS9cjWrV4MvLAIzKwLxHTyKEnoV8SgPhXuG8HXeX5qPaqqbLSTM4+sg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 9540e818e9c171bbfafa107f0b23cfcf3b84a278 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 31 Jan 2025 14:20:06 -0500 Subject: [PATCH 49/62] fix --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d13b21d60b..17ad6b9d68 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.25", + "@across-protocol/sdk": "^4.0.0-beta.26", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index f0cf5626fe..5eb577b2da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.25": - version "4.0.0-beta.25" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.25.tgz#c392e3ba9beddfbb596e19b3f9ae23ec7794a596" - integrity sha512-Dgz3XesdcK7Ow5t7nO4+wzPbpjE4MpVQS9cjWrV4MvLAIzKwLxHTyKEnoV8SgPhXuG8HXeX5qPaqqbLSTM4+sg== +"@across-protocol/sdk@^4.0.0-beta.26": + version "4.0.0-beta.26" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.26.tgz#1a2e3476c40b62af9cf06adfdc984db28a66b943" + integrity sha512-AF2+eUyc9T4FApza2pZOB7Thk29ZveFxjOCaj9FxViGQR5xKDzbug8D3HCi0FEyaa0kHaF2e52pBwLJpwwgojg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 5b060b1c6ff3289ccf5bb168850688c341875d0c Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 31 Jan 2025 14:23:58 -0500 Subject: [PATCH 50/62] Update Dataworker.loadData.fill.ts --- test/Dataworker.loadData.fill.ts | 139 ++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 23 deletions(-) diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index f2e34abc75..a492e5b3e4 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -250,29 +250,122 @@ describe("Dataworker: Load bundle data", async function () { expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(0); }); - it("Duplicate deposits do not counfound fill refunds", async function () { - // Send duplicate deposits. - generateV3Deposit({ outputToken: randomAddress() }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit - await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); - expect(deposits.length).to.equal(3); - - // Fill deposit. - generateV3FillFromDeposit(deposits[0]); - await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); - - // Bundle should contain all deposits. - // Bundle should refund fill. - // Bundle should not refund duplicate deposits. - const bundleBlockRanges = getDefaultBlockRange(5); - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); - expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); - }); + describe("Duplicate deposits in same bundle as fill", function() { + it("Sends duplicate deposit refunds for fills in bundle", async function () { + // Send duplicate deposits. + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(3); + + // Fill deposit. + generateV3FillFromDeposit(deposits[0]); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + + // Bundle should contain all deposits. + // Bundle should refund fill. + // Bundle should refund duplicate deposits. + const bundleBlockRanges = getDefaultBlockRange(5); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + dupe1.transactionHash + ); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( + dupe2.transactionHash + ); + }); + + it("Does not account for duplicate deposit refunds for deposits after bundle block range", async function () { + generateV3Deposit({ + outputToken: randomAddress(), + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1, + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const dupe1 = await mockOriginSpokePoolClient.depositV3({ + ...mockOriginSpokePoolClient.getDeposits()[0], + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, + }); + await mockOriginSpokePoolClient.depositV3({ + ...mockOriginSpokePoolClient.getDeposits()[0], + blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 21, + }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(3); + + const fill = generateV3FillFromDeposit(deposits[0], { + blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber + 21, + }); + + // Create a block range that removes latest event + const destinationChainBlockRange = [fill.blockNumber - 1, fill.blockNumber + 1]; + const originChainBlockRange = [deposits[0].blockNumber, deposits[1].blockNumber]; + // Substitute bundle block ranges. + const bundleBlockRanges = getDefaultBlockRange(5); + const destinationChainIndex = + dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); + bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; + const originChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(originChainId); + bundleBlockRanges[originChainIndex] = originChainBlockRange; + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + dupe1.transactionHash + ); + }); + + it("Sends duplicate deposit refunds even if relayer repayment information is invalid", async function () { + // Send duplicate deposits. + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(3); + + // Fill deposit with invalid repayment information. + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + // Bundle should contain all deposits. + // Bundle should not refund any fills + // Bundle should refund duplicate deposits. + const bundleBlockRanges = getDefaultBlockRange(5); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( + dupe1.transactionHash + ); + expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( + dupe2.transactionHash + ); + }); + }) it("Does not create unexecutable slow fill for zero value deposit", async function () { generateV3Deposit({ From a48576892281cd4154e9a5a1a23ac3c949cb8fe7 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 31 Jan 2025 15:25:47 -0500 Subject: [PATCH 51/62] Update Dataworker.loadData.fill.ts --- test/Dataworker.loadData.fill.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index a492e5b3e4..1a23962289 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -250,7 +250,7 @@ describe("Dataworker: Load bundle data", async function () { expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(0); }); - describe("Duplicate deposits in same bundle as fill", function() { + describe("Duplicate deposits in same bundle as fill", function () { it("Sends duplicate deposit refunds for fills in bundle", async function () { // Send duplicate deposits. generateV3Deposit({ outputToken: randomAddress() }); @@ -260,11 +260,11 @@ describe("Dataworker: Load bundle data", async function () { await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); expect(deposits.length).to.equal(3); - + // Fill deposit. generateV3FillFromDeposit(deposits[0]); await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); - + // Bundle should contain all deposits. // Bundle should refund fill. // Bundle should refund duplicate deposits. @@ -280,7 +280,7 @@ describe("Dataworker: Load bundle data", async function () { dupe2.transactionHash ); }); - + it("Does not account for duplicate deposit refunds for deposits after bundle block range", async function () { generateV3Deposit({ outputToken: randomAddress(), @@ -298,11 +298,11 @@ describe("Dataworker: Load bundle data", async function () { await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); expect(deposits.length).to.equal(3); - + const fill = generateV3FillFromDeposit(deposits[0], { blockNumber: mockDestinationSpokePoolClient.eventManager.blockNumber + 21, }); - + // Create a block range that removes latest event const destinationChainBlockRange = [fill.blockNumber - 1, fill.blockNumber + 1]; const originChainBlockRange = [deposits[0].blockNumber, deposits[1].blockNumber]; @@ -322,7 +322,7 @@ describe("Dataworker: Load bundle data", async function () { dupe1.transactionHash ); }); - + it("Sends duplicate deposit refunds even if relayer repayment information is invalid", async function () { // Send duplicate deposits. generateV3Deposit({ outputToken: randomAddress() }); @@ -332,7 +332,7 @@ describe("Dataworker: Load bundle data", async function () { await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); expect(deposits.length).to.equal(3); - + // Fill deposit with invalid repayment information. const invalidRelayer = ethers.utils.randomBytes(32); const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); @@ -348,7 +348,7 @@ describe("Dataworker: Load bundle data", async function () { ); provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); mockDestinationSpokePoolClient.spokePool = spokeWrapper; - + // Bundle should contain all deposits. // Bundle should not refund any fills // Bundle should refund duplicate deposits. @@ -364,8 +364,8 @@ describe("Dataworker: Load bundle data", async function () { expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( dupe2.transactionHash ); - }); - }) + }); + }); it("Does not create unexecutable slow fill for zero value deposit", async function () { generateV3Deposit({ From 45aa8372a2bfb2c751944f7795bb556ab8006b31 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 31 Jan 2025 15:57:48 -0500 Subject: [PATCH 52/62] pay refunds to pre-filler unless slow fill --- package.json | 2 +- test/Dataworker.loadData.fill.ts | 58 ++++++++++++++++++++------------ yarn.lock | 8 ++--- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 17ad6b9d68..f416d80bab 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.26", + "@across-protocol/sdk": "^4.0.0-beta.27", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 1a23962289..501c27c891 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -255,8 +255,8 @@ describe("Dataworker: Load bundle data", async function () { // Send duplicate deposits. generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit - const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); expect(deposits.length).to.equal(3); @@ -267,10 +267,35 @@ describe("Dataworker: Load bundle data", async function () { // Bundle should contain all deposits. // Bundle should refund fill. - // Bundle should refund duplicate deposits. + // Bundle should refund duplicate deposits to filler const bundleBlockRanges = getDefaultBlockRange(5); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(3); + expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); + }); + + it("Sends duplicate deposit refunds for slow fills in bundle", async function () { + // Send duplicate deposits. + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); + expect(deposits.length).to.equal(3); + + // Fill deposit as slow fill + generateV3FillFromDeposit(deposits[0], {}, undefined, undefined, interfaces.FillType.SlowFill); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + + // Bundle should contain all deposits. + // Bundle should refund fill. + // Bundle should refund duplicate deposits to depositor + const bundleBlockRanges = getDefaultBlockRange(5); + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); + expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[destinationChainId][erc20_2.address].refunds).to.deep.equal({}); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( @@ -287,7 +312,7 @@ describe("Dataworker: Load bundle data", async function () { blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 1, }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const dupe1 = await mockOriginSpokePoolClient.depositV3({ + await mockOriginSpokePoolClient.depositV3({ ...mockOriginSpokePoolClient.getDeposits()[0], blockNumber: mockOriginSpokePoolClient.eventManager.blockNumber + 11, }); @@ -315,20 +340,17 @@ describe("Dataworker: Load bundle data", async function () { bundleBlockRanges[originChainIndex] = originChainBlockRange; await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(2); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(2); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( - dupe1.transactionHash - ); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); }); - it("Sends duplicate deposit refunds even if relayer repayment information is invalid", async function () { + it("Does not send duplicate deposit refunds if relayer repayment information is invalid", async function () { // Send duplicate deposits. generateV3Deposit({ outputToken: randomAddress() }); await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const dupe1 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit - const dupe2 = await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit + await mockOriginSpokePoolClient.depositV3(mockOriginSpokePoolClient.getDeposits()[0]); // Duplicate deposit await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDepositsForDestinationChainWithDuplicates(destinationChainId); expect(deposits.length).to.equal(3); @@ -350,20 +372,12 @@ describe("Dataworker: Load bundle data", async function () { mockDestinationSpokePoolClient.spokePool = spokeWrapper; // Bundle should contain all deposits. - // Bundle should not refund any fills - // Bundle should refund duplicate deposits. const bundleBlockRanges = getDefaultBlockRange(5); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); expect(data1.bundleFillsV3).to.deep.equal({}); expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(3); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(2); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][0].transactionHash).to.equal( - dupe1.transactionHash - ); - expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address][1].transactionHash).to.equal( - dupe2.transactionHash - ); + expect(data1.expiredDepositsToRefundV3).to.deep.equal({}); }); }); diff --git a/yarn.lock b/yarn.lock index 5eb577b2da..5b82414255 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.26": - version "4.0.0-beta.26" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.26.tgz#1a2e3476c40b62af9cf06adfdc984db28a66b943" - integrity sha512-AF2+eUyc9T4FApza2pZOB7Thk29ZveFxjOCaj9FxViGQR5xKDzbug8D3HCi0FEyaa0kHaF2e52pBwLJpwwgojg== +"@across-protocol/sdk@^4.0.0-beta.27": + version "4.0.0-beta.27" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.27.tgz#7e9fa3e9743c0d0019a61330fa6a841f3030d55d" + integrity sha512-h2g6XLvM+cdQIulRXXpZwMqKSqtU1WVCeJtEbj4vUWk4ykC3Uh7UG8/A8bg9OsQ2zlQSLeKWf/9P5FIJGYpicQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 0f44c367d9cb712babfe2696ccaaf1aa891ebb1d Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 31 Jan 2025 18:10:41 -0500 Subject: [PATCH 53/62] Update Dataworker.loadData.prefill.ts --- test/Dataworker.loadData.prefill.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 5def47908b..3bd05ee292 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -582,6 +582,9 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].depositId).to.equal( deposit.args.depositId ); + expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].transactionHash).to.equal( + deposit.transactionHash + ); }); it("Does not create slow fill leaf if slow fill request is in-memory but an invalid request", async function () { From 5961af994c6ad459a09e593a351f94ee64db32a0 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 31 Jan 2025 18:11:13 -0500 Subject: [PATCH 54/62] Update Dataworker.loadData.prefill.ts --- test/Dataworker.loadData.prefill.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 3bd05ee292..9b1946ccb8 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -582,6 +582,9 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].depositId).to.equal( deposit.args.depositId ); + + // The first deposit should be matched which is important because the quote timestamp of the deposit is not + // in the relay data hash so it can change between duplicate deposits. expect(data1.bundleSlowFillsV3[destinationChainId][erc20_2.address][0].transactionHash).to.equal( deposit.transactionHash ); From 2e761ebb6565a1be06bc906e6b1d393cca9056c2 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 31 Jan 2025 18:15:20 -0500 Subject: [PATCH 55/62] fix --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f416d80bab..cebc8435d8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.27", + "@across-protocol/sdk": "^4.0.0-beta.28", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 5b82414255..8dbfc81c00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.27": - version "4.0.0-beta.27" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.27.tgz#7e9fa3e9743c0d0019a61330fa6a841f3030d55d" - integrity sha512-h2g6XLvM+cdQIulRXXpZwMqKSqtU1WVCeJtEbj4vUWk4ykC3Uh7UG8/A8bg9OsQ2zlQSLeKWf/9P5FIJGYpicQ== +"@across-protocol/sdk@^4.0.0-beta.28": + version "4.0.0-beta.28" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.28.tgz#d01c6b673b679e3c54a440c3d726dcf57217a065" + integrity sha512-7Tz1We3MDgzHR/vaQskUw/+ZCBNrhBmCMXD0kQ6zcIzlR9mkeYA9PFl9QAljQm+FM6CuOPTwsj9TXMRvCuhRhQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 65791812f6b407c402aaf3b14c18a43e14f18fa6 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Sat, 1 Feb 2025 16:23:45 -0500 Subject: [PATCH 56/62] Add verifyFillRepayment test for pre fills --- package.json | 2 +- test/Dataworker.loadData.prefill.ts | 373 ++++++++++++++++++---------- yarn.lock | 8 +- 3 files changed, 246 insertions(+), 137 deletions(-) diff --git a/package.json b/package.json index cebc8435d8..02f4b21b33 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.28", + "@across-protocol/sdk": "^4.0.0-beta.30", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index 9b1946ccb8..f7df080990 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -230,56 +230,252 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic ); }); - it("Refunds fill to msg.sender if fill is not in-memory and repayment info is invalid", async function () { - generateV3Deposit({ outputToken: randomAddress() }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDeposits(); + describe("Pre-fill has invalid repayment information", function () { + it("Refunds fill to msg.sender if fill is not in-memory and repayment info is invalid", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit fill that we won't include in the bundle block range. Make sure its relayer address is invalid + // so that the bundle data client is forced to overwrite the refund recipient. + const fill = generateV3FillFromDeposit(deposits[0], {}, createRandomBytes32()); + const fillWithBlock = { + ...spreadEventWithBlockNumber(fill), + destinationChainId, + } as FillWithBlock; + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + deposits[0], + fillWithBlock + ); + + // Don't include the fill event in the update so that the bundle data client is forced to load the event + // fresh. + await mockDestinationSpokePoolClient.update([]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(0); + + // Mock FillStatus to be Filled so that the BundleDataClient searches for event. + mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); + + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid + // but the msg.sender is valid. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const validRelayerAddress = randomAddress(); + provider._setTransaction(fill.transactionHash, { + from: validRelayerAddress, + } as unknown as TransactionResponse); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + // The fill is a pre-fill because its earlier than the bundle block range. Because its corresponding + // deposit is in the block range, we should refund it. + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( + fill.args.depositId + ); + // Check its refunded to correct address: + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal( + validRelayerAddress + ); + }); - // Submit fill that we won't include in the bundle block range. Make sure its relayer address is invalid - // so that the bundle data client is forced to overwrite the refund recipient. - const fill = generateV3FillFromDeposit(deposits[0], {}, createRandomBytes32()); - const fillWithBlock = { - ...spreadEventWithBlockNumber(fill), - destinationChainId, - } as FillWithBlock; - (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( - deposits[0], - fillWithBlock - ); + it("Refunds fill to msg.sender if fill is in-memory and repayment info is invalid", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Submit fill with invalid relayer address so that the bundle data client is forced to + // overwrite the refund recipient. + const fill = generateV3FillFromDeposit(deposits[0], {}, createRandomBytes32()); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + expect(mockDestinationSpokePoolClient.getFills().length).to.equal(1); + + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid + // but the msg.sender is valid. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const validRelayerAddress = randomAddress(); + provider._setTransaction(fill.transactionHash, { + from: validRelayerAddress, + } as unknown as TransactionResponse); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + // The fill is a pre-fill because its earlier than the bundle block range. Because its corresponding + // deposit is in the block range, we should refund it. + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( + fill.args.depositId + ); + // Check its refunded to correct address: + expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal( + validRelayerAddress + ); + }); - // Don't include the fill event in the update so that the bundle data client is forced to load the event - // fresh. - await mockDestinationSpokePoolClient.update([]); - expect(mockDestinationSpokePoolClient.getFills().length).to.equal(0); + it("Does not refund fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for repayment chain", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Send fill with invalid repayment address + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); + const invalidFill = { + ...spreadEventWithBlockNumber(invalidFillEvent), + destinationChainId, + } as FillWithBlock; + await mockDestinationSpokePoolClient.update([]); + + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will + // query relayStatuses() on the spoke pool. + mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); + // Also mock the matched fill event so that BundleDataClient doesn't query for it. + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + deposits[0], + invalidFill + ); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); + }); - // Mock FillStatus to be Filled so that the BundleDataClient searches for event. - mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); + it("Does not refund fill to msg.sender if fill is in-memory and repayment address and msg.sender are invalid for repayment chain", async function () { + generateV3Deposit({ outputToken: randomAddress() }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Send fill with invalid repayment address + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); + }); - // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so - // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid - // but the msg.sender is valid. - const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); - const validRelayerAddress = randomAddress(); - provider._setTransaction(fill.transactionHash, { from: validRelayerAddress } as unknown as TransactionResponse); - const spokeWrapper = new Contract( - mockDestinationSpokePoolClient.spokePool.address, - mockDestinationSpokePoolClient.spokePool.interface, - provider - ); - mockDestinationSpokePoolClient.spokePool = spokeWrapper; + it("Does not refund lite chain fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for origin chain", async function () { + generateV3Deposit({ outputToken: randomAddress(), fromLiteChain: true }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Fill deposits from different relayers + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); + const invalidFill = { + ...spreadEventWithBlockNumber(invalidFillEvent), + destinationChainId, + } as FillWithBlock; + await mockDestinationSpokePoolClient.update([]); + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will + // query relayStatuses() on the spoke pool. + mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); + // Also mock the matched fill event so that BundleDataClient doesn't query for it. + (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( + deposits[0], + invalidFill + ); + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); + }); - // The fill is a pre-fill because its earlier than the bundle block range. Because its corresponding - // deposit is in the block range, we should refund it. - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].depositId).to.equal( - fill.args.depositId - ); - // Check its refunded to correct address: - expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills[0].relayer).to.equal(validRelayerAddress); + it("Does not refund lite chain fill to msg.sender if fill is in-memory and repayment address and msg.sender are invalid for origin chain", async function () { + generateV3Deposit({ outputToken: randomAddress(), fromLiteChain: true }); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); + const deposits = mockOriginSpokePoolClient.getDeposits(); + + // Fill deposits from different relayers + const invalidRelayer = ethers.utils.randomBytes(32); + const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); + // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so + // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, + // set the msg.sender as an invalid address. + const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); + const spokeWrapper = new Contract( + mockDestinationSpokePoolClient.spokePool.address, + mockDestinationSpokePoolClient.spokePool.interface, + provider + ); + provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); + mockDestinationSpokePoolClient.spokePool = spokeWrapper; + + const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( + getDefaultBlockRange(5), + spokePoolClients + ); + + expect(data1.bundleFillsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); + }); }); it("Refunds deposit as a duplicate if fill is not in-memory and is a slow fill", async function () { @@ -317,93 +513,6 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic expect(data1.expiredDepositsToRefundV3[originChainId][erc20_1.address].length).to.equal(1); }); - it("Does not refund fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for repayment chain", async function () { - generateV3Deposit({ outputToken: randomAddress() }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDeposits(); - - // Send fill with invalid repayment address - const invalidRelayer = ethers.utils.randomBytes(32); - const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); - const invalidFill = { - ...spreadEventWithBlockNumber(invalidFillEvent), - destinationChainId, - } as FillWithBlock; - await mockDestinationSpokePoolClient.update([]); - - // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so - // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, - // set the msg.sender as an invalid address. - const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); - const spokeWrapper = new Contract( - mockDestinationSpokePoolClient.spokePool.address, - mockDestinationSpokePoolClient.spokePool.interface, - provider - ); - provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); - mockDestinationSpokePoolClient.spokePool = spokeWrapper; - - // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will - // query relayStatuses() on the spoke pool. - mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); - // Also mock the matched fill event so that BundleDataClient doesn't query for it. - (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( - deposits[0], - invalidFill - ); - - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - - expect(data1.bundleFillsV3).to.deep.equal({}); - expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); - }); - it("Does not refund lite chain fill to msg.sender if fill is not in-memory and repayment address and msg.sender are invalid for origin chain", async function () { - generateV3Deposit({ outputToken: randomAddress(), fromLiteChain: true }); - await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); - const deposits = mockOriginSpokePoolClient.getDeposits(); - - // Fill deposits from different relayers - const invalidRelayer = ethers.utils.randomBytes(32); - const invalidFillEvent = generateV3FillFromDeposit(deposits[0], {}, invalidRelayer); - const invalidFill = { - ...spreadEventWithBlockNumber(invalidFillEvent), - destinationChainId, - } as FillWithBlock; - await mockDestinationSpokePoolClient.update([]); - await mockDestinationSpokePoolClient.update([]); - // Replace the dataworker providers to use mock providers. We need to explicitly do this since we do not actually perform a contract call, so - // we must inject a transaction response into the provider to simulate the case when the relayer repayment address is invalid. In this case, - // set the msg.sender as an invalid address. - const provider = new providers.mocks.MockedProvider(bnZero, bnZero, destinationChainId); - const spokeWrapper = new Contract( - mockDestinationSpokePoolClient.spokePool.address, - mockDestinationSpokePoolClient.spokePool.interface, - provider - ); - provider._setTransaction(invalidFillEvent.transactionHash, { from: invalidRelayer }); - mockDestinationSpokePoolClient.spokePool = spokeWrapper; - - // Mock FillStatus to be Filled for any invalid fills otherwise the BundleDataClient will - // query relayStatuses() on the spoke pool. - mockDestinationSpokePoolClient.setRelayFillStatus(deposits[0], interfaces.FillStatus.Filled); - // Also mock the matched fill event so that BundleDataClient doesn't query for it. - (dataworkerInstance.clients.bundleDataClient as MockBundleDataClient).setMatchingFillEvent( - deposits[0], - invalidFill - ); - - const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( - getDefaultBlockRange(5), - spokePoolClients - ); - - expect(data1.bundleFillsV3).to.deep.equal({}); - expect(spy.getCalls().filter((e) => e.lastArg.message.includes("unrepayable")).length).to.equal(1); - }); - it("Refunds pre-fills in-memory for duplicate deposits", async function () { // In this test, we send multiple deposits per fill. We assume you cannot send more than one fill per deposit. generateV3Deposit({ outputToken: randomAddress() }); diff --git a/yarn.lock b/yarn.lock index 8dbfc81c00..24049fdca9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.28": - version "4.0.0-beta.28" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.28.tgz#d01c6b673b679e3c54a440c3d726dcf57217a065" - integrity sha512-7Tz1We3MDgzHR/vaQskUw/+ZCBNrhBmCMXD0kQ6zcIzlR9mkeYA9PFl9QAljQm+FM6CuOPTwsj9TXMRvCuhRhQ== +"@across-protocol/sdk@^4.0.0-beta.30": + version "4.0.0-beta.30" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.30.tgz#779722786b347cb7d1071344e9e36148ecb83063" + integrity sha512-BLAItklgDlscWkqHR73UgRiq0yyISlc52oloGzf4N5dwpbd3iRqmJBA/glo+FNIXJs5hOTCJ8sG1oP9vFDENpQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From f003ad5990b9c755e4fabece888cbed910ae66b7 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Sat, 1 Feb 2025 23:59:55 -0500 Subject: [PATCH 57/62] add asserts --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 02f4b21b33..1fb408aa30 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.30", + "@across-protocol/sdk": "^4.0.0-beta.31", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 24049fdca9..bfe1caefbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.30": - version "4.0.0-beta.30" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.30.tgz#779722786b347cb7d1071344e9e36148ecb83063" - integrity sha512-BLAItklgDlscWkqHR73UgRiq0yyISlc52oloGzf4N5dwpbd3iRqmJBA/glo+FNIXJs5hOTCJ8sG1oP9vFDENpQ== +"@across-protocol/sdk@^4.0.0-beta.31": + version "4.0.0-beta.31" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.31.tgz#a188dc52db0e1dc7324f357b8ade3e8749177c8e" + integrity sha512-xD6c0B5HxP+Ub3lr/Wx7dOWm3a5ttpzzRyYE7ifaltir9WNuoOT4kRO/rhn2/gTVJHmBAmSv0myOUI8XzN9AkQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 5532611c1ab92d43ffbd82fc38d1be2f112cd3b8 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Sun, 2 Feb 2025 21:43:17 -0500 Subject: [PATCH 58/62] wip --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1fb408aa30..5d0b156a9f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.31", + "@across-protocol/sdk": "^4.0.0-beta.33", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index bfe1caefbe..912d5087d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.31": - version "4.0.0-beta.31" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.31.tgz#a188dc52db0e1dc7324f357b8ade3e8749177c8e" - integrity sha512-xD6c0B5HxP+Ub3lr/Wx7dOWm3a5ttpzzRyYE7ifaltir9WNuoOT4kRO/rhn2/gTVJHmBAmSv0myOUI8XzN9AkQ== +"@across-protocol/sdk@^4.0.0-beta.33": + version "4.0.0-beta.33" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.33.tgz#2e21dd78eaf3f751cbb5238e6a0cb2b71f8610d5" + integrity sha512-EahTRgLE2Owlg8ZCQ3wbh2SZUVRiRYG4q7GXMbQNwHy7WS3zNzJ9LKQdX0fv/Chlh0WciSzFnO9Efb/KdyDeQQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From a9a4b5a42a142e07ce9069a7aed9ccaba049b917 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 3 Feb 2025 12:00:15 -0500 Subject: [PATCH 59/62] 34 --- package.json | 2 +- test/Dataworker.loadData.deposit.ts | 2 +- test/Dataworker.loadData.fill.ts | 6 +++--- test/Dataworker.loadData.slowFill.ts | 1 + yarn.lock | 8 ++++---- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 7b52abb959..c6b9cff2b2 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.33", + "@across-protocol/sdk": "^4.0.0-beta.34", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/test/Dataworker.loadData.deposit.ts b/test/Dataworker.loadData.deposit.ts index 141704308c..9814fc5e39 100644 --- a/test/Dataworker.loadData.deposit.ts +++ b/test/Dataworker.loadData.deposit.ts @@ -436,7 +436,7 @@ describe("Dataworker: Load bundle data", async function () { // Fill should not be included since we cannot validate fills when the deposit is in a following bundle. // This fill is considered a "pre-fill" and will be validated when the deposit is included in a bundle. expect(data1.bundleFillsV3).to.deep.equal({}); - expect(spyLogIncludes(spy, -2, "invalid V3 fills in range")).to.be.true; + expect(spyLogIncludes(spy, -2, "invalid fills in range")).to.be.true; }); it("Does not count prior bundle expired deposits that were filled", async function () { // Send deposit that expires in this bundle. diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index e2e1662175..65e725cc53 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -247,7 +247,7 @@ describe("Dataworker: Load bundle data", async function () { ); expect(data1.bundleFillsV3).to.deep.equal({}); - expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(0); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid fills")).length).to.equal(0); }); describe("Duplicate deposits in same bundle as fill", function () { @@ -639,7 +639,7 @@ describe("Dataworker: Load bundle data", async function () { }); expect(data1.bundleFillsV3).to.deep.equal({}); expect(data1.bundleDepositsV3).to.deep.equal({}); - expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid")).length).to.equal(1); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid fills")).length).to.equal(1); }); it("Validates fill from lite chain against old bundle deposit", async function () { // For this test, we need to actually send a deposit on the spoke pool @@ -823,7 +823,7 @@ describe("Dataworker: Load bundle data", async function () { spokePoolClients ); expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); - expect(spyLogIncludes(spy, -2, "invalid V3 fills in range")).to.be.true; + expect(spyLogIncludes(spy, -2, "invalid fills in range")).to.be.true; }); it("Matches fill with deposit with outputToken = 0x0", async function () { await depositV3( diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index ab3cfa0562..1daa12ed4b 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -372,6 +372,7 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () }); expect(data1.bundleSlowFillsV3).to.deep.equal({}); expect(data1.bundleDepositsV3).to.deep.equal({}); + expect(spy.getCalls().filter((e) => e.lastArg.message.includes("invalid slow fill requests")).length).to.equal(1); }); it("Handles invalid slow fill requests with mismatching params from deposit", async function () { diff --git a/yarn.lock b/yarn.lock index 887fddd806..c325d73aaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.33": - version "4.0.0-beta.33" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.33.tgz#2e21dd78eaf3f751cbb5238e6a0cb2b71f8610d5" - integrity sha512-EahTRgLE2Owlg8ZCQ3wbh2SZUVRiRYG4q7GXMbQNwHy7WS3zNzJ9LKQdX0fv/Chlh0WciSzFnO9Efb/KdyDeQQ== +"@across-protocol/sdk@^4.0.0-beta.34": + version "4.0.0-beta.34" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.34.tgz#5c7168cd17199849822546c532423bf5ff7a95a8" + integrity sha512-6tQsInQ+jckCE5/gssGpSAYT073SptFe4zxdQ5EpHZw/PWP3c4ivQ+FBlbjtSJj3OqzQFg9t8SgFc9GFmSaYXA== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From db032e7f2e534b08b0561ed879a6aa26014049a7 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 3 Feb 2025 12:16:43 -0500 Subject: [PATCH 60/62] fix test --- test/Dataworker.loadData.slowFill.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index 1daa12ed4b..8382225e13 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -515,7 +515,11 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () }); // Here we can see that the historical query for the deposit actually succeeds, but the deposit itself // was not one eligible to be slow filled. - expect(spyLogIncludes(spy, -4, "Located V3 deposit outside of SpokePoolClient's search range")).is.true; + expect( + spy + .getCalls() + .find((e) => e.lastArg.message.includes("Located V3 deposit outside of SpokePoolClient's search range")) + ).to.not.be.undefined; expect(data1.bundleSlowFillsV3).to.deep.equal({}); expect(data1.bundleDepositsV3).to.deep.equal({}); From a92ac5e7a17e50dd4e30eeb98c26202bca7d6709 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 3 Feb 2025 13:01:30 -0500 Subject: [PATCH 61/62] 4.0.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c6b9cff2b2..a4a0b1df84 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.30", "@across-protocol/contracts": "^3.0.25", - "@across-protocol/sdk": "^4.0.0-beta.34", + "@across-protocol/sdk": "^4.0.0", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index c325d73aaf..8a3393a51d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,10 +53,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^4.0.0-beta.34": - version "4.0.0-beta.34" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0-beta.34.tgz#5c7168cd17199849822546c532423bf5ff7a95a8" - integrity sha512-6tQsInQ+jckCE5/gssGpSAYT073SptFe4zxdQ5EpHZw/PWP3c4ivQ+FBlbjtSJj3OqzQFg9t8SgFc9GFmSaYXA== +"@across-protocol/sdk@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.0.0.tgz#83242907471577a8fe670dcee3d633b2338d15b9" + integrity sha512-qDkOYHlQy8KhT5WStRXJybp84pFuxQc4MgMwZzOOBTvvrGWtklSpWY9NT8Uu3Rd9v9Yn+oa/MRURdFmeMsJnrg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.30" From 49538ee12f44b592fdfa451a82cbd6653e805300 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 3 Feb 2025 13:42:40 -0500 Subject: [PATCH 62/62] Update Constants.ts --- src/common/Constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 3e77e687d1..b5dd8936d1 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -28,7 +28,7 @@ import { CONTRACT_ADDRESSES } from "./ContractAddresses"; // Maximum supported version of the configuration loaded into the Across ConfigStore. // It protects bots from running outdated code against newer version of the on-chain config store. // @dev Incorrectly setting this value may lead to incorrect behaviour and potential loss of funds. -export const CONFIG_STORE_VERSION = 4; +export const CONFIG_STORE_VERSION = 5; export const RELAYER_MIN_FEE_PCT = 0.0001;