From b0a7a2373fb8c11163b6ae35807f2c32353f2215 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 12:07:14 -0500 Subject: [PATCH 01/38] improve(BundleDataClient): Log about duplicate destination chain events We want to know when these events happen but we currently believe this is possible with the Indexed spoke pool client so we shouldn't throw. --- package.json | 2 +- src/clients/BundleDataClient/BundleDataClient.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f03b5abb..8d616944 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@across-protocol/sdk", "author": "UMA Team", - "version": "4.1.8", + "version": "4.1.9", "license": "AGPL-3.0", "homepage": "https://docs.across.to/reference/sdk", "files": [ diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 4d7378a3..5e821214 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -992,7 +992,11 @@ export class BundleDataClient { } } } else { - throw new Error("Duplicate fill detected"); + this.logger.warn({ + at: "BundleDataClient#loadDataFromScratch", + message: "Detected duplicate fill", + fill, + }); } return; } @@ -1115,7 +1119,11 @@ export class BundleDataClient { validatedBundleSlowFills.push(matchedDeposit); } } else { - throw new Error("Duplicate slow fill request detected."); + this.logger.warn({ + at: "BundleDataClient#loadDataFromScratch", + message: "Detected duplicate slow fill request", + slowFillRequest, + }); } return; } From 55783b7da8a7e801f90c0eb16110f38a58e0301e Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 12:20:44 -0500 Subject: [PATCH 02/38] Log duplicate deposits in spoke pool client --- src/clients/SpokePoolClient.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index be9c0ff6..7741929f 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -656,6 +656,21 @@ export class SpokePoolClient extends BaseAbstractClient { } if (this.depositHashes[getRelayEventKey(deposit)] !== undefined) { + // Sanity check that this event is not a duplicate, even though the relay data hash is a duplicate. + const allDeposits = this._getDuplicateDeposits(deposit).concat(this.depositHashes[getRelayEventKey(deposit)]); + if ( + allDeposits.some((e) => { + return e.transactionHash === deposit.transactionHash && e.logIndex === deposit.logIndex; + }) + ) { + this.logger.warn({ + at: "SpokePoolClient#update", + chainId: this.chainId, + message: "Duplicate deposit found with same transaction hash and log index", + deposit, + }); + continue; + } assign(this.duplicateDepositHashes, [getRelayEventKey(deposit)], [deposit]); continue; } From 9850552c280e1a9f73ef1e503d46eede5fda80f3 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 12:55:19 -0500 Subject: [PATCH 03/38] getLatestProposedBundleData should never load data from scratch --- .../BundleDataClient/BundleDataClient.ts | 75 +++++++++++-------- src/clients/SpokePoolClient.ts | 32 +++++++- 2 files changed, 76 insertions(+), 31 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 5e821214..27f85c71 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -240,13 +240,7 @@ export class BundleDataClient { ); } - private async loadPersistedDataFromArweave( - blockRangesForChains: number[][] - ): Promise { - if (!isDefined(this.clients?.arweaveClient)) { - return undefined; - } - const start = performance.now(); + private async getBundleDataFromArweave(blockRangesForChains: number[][]) { const persistedData = await this.clients.arweaveClient.getByTopic( this.getArweaveBundleDataClientKey(blockRangesForChains), BundleDataSS @@ -256,6 +250,20 @@ export class BundleDataClient { if (!isDefined(persistedData) || persistedData.length < 1) { return undefined; } + return persistedData; + } + + private async loadPersistedDataFromArweave( + blockRangesForChains: number[][] + ): Promise { + if (!isDefined(this.clients?.arweaveClient)) { + return undefined; + } + const start = performance.now(); + const persistedData = await this.getBundleDataFromArweave(blockRangesForChains); + if (!isDefined(persistedData)) { + return undefined; + } // A converter function to account for the fact that our SuperStruct schema does not support numeric // keys in records. Fundamentally, this is a limitation of superstruct itself. @@ -435,24 +443,39 @@ export class BundleDataClient { const hubPoolClient = this.clients.hubPoolClient; // Determine which bundle we should fetch from arweave, either the pending bundle or the latest // executed one. Both should have arweave data but if for some reason the arweave data is missing, - // this function will have to compute the bundle data from scratch which will be slow. We have to fallback - // to computing the bundle from scratch since this function needs to return the full bundle data so that - // it can be used to get the running balance proposed using its data. - const bundleBlockRanges = getImpliedBundleBlockRanges( + // this function will load the bundle data from the most recent bundle data published to Arweave. + + let bundleBlockRanges = getImpliedBundleBlockRanges( hubPoolClient, this.clients.configStoreClient, hubPoolClient.hasPendingProposal() ? hubPoolClient.getLatestProposedRootBundle() - : hubPoolClient.getLatestFullyExecutedRootBundle(hubPoolClient.latestBlockSearched)! // ! because we know there is a bundle + : hubPoolClient.getNthFullyExecutedRootBundle(-1)! ); - return { - blockRanges: bundleBlockRanges, - bundleData: await this.loadData( - bundleBlockRanges, - this.spokePoolClients, - true // this bundle data should have been published to arweave - ), - }; + // Check if bundle data exists on arweave, otherwise fallback to last published bundle data. If the + // first bundle block range we are trying is the pending proposal, then we'll grab the most recently + // validated bundle, otherwise we'll grab the second most recently validated bundle. + let n = hubPoolClient.hasPendingProposal() ? 1 : 2; + + // eslint-disable-next-line no-constant-condition + while (true) { + const bundleDataOnArweave = await this.getBundleDataFromArweave(bundleBlockRanges); + if (!isDefined(bundleDataOnArweave)) { + // Bundle data is not arweave, try the next most recently validated bundle. + bundleBlockRanges = getImpliedBundleBlockRanges( + hubPoolClient, + this.clients.configStoreClient, + hubPoolClient.getNthFullyExecutedRootBundle(-n)! + ); + } else { + return { + blockRanges: bundleBlockRanges, + bundleData: await this.loadData(bundleBlockRanges, this.spokePoolClients, true), + }; + } + + n++; + } } async getLatestPoolRebalanceRoot(): Promise<{ root: PoolRebalanceRoot; blockRanges: number[][] }> { @@ -992,11 +1015,7 @@ export class BundleDataClient { } } } else { - this.logger.warn({ - at: "BundleDataClient#loadDataFromScratch", - message: "Detected duplicate fill", - fill, - }); + throw new Error("Duplicate fill detected"); } return; } @@ -1119,11 +1138,7 @@ export class BundleDataClient { validatedBundleSlowFills.push(matchedDeposit); } } else { - this.logger.warn({ - at: "BundleDataClient#loadDataFromScratch", - message: "Detected duplicate slow fill request", - slowFillRequest, - }); + throw new Error("Duplicate slow fill request detected."); } return; } diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index 7741929f..d516a0f3 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -726,13 +726,27 @@ export class SpokePoolClient extends BaseAbstractClient { const slowFillRequest = { ...spreadEventWithBlockNumber(event), destinationChainId: this.chainId, - } as SlowFillRequestWithBlock; + } as SlowFillRequestWithBlock; if (eventName === "RequestedV3SlowFill") { slowFillRequest.messageHash = getMessageHash(slowFillRequest.message); } const depositHash = getRelayEventKey({ ...slowFillRequest, destinationChainId: this.chainId }); + + // Sanity check that this event is not a duplicate. + if ( + this.slowFillRequests[depositHash] !== undefined + ) { + this.logger.warn({ + at: "SpokePoolClient#update", + chainId: this.chainId, + message: "Duplicate slow fill request found", + slowFillRequest, + }); + continue; + } + this.slowFillRequests[depositHash] ??= slowFillRequest; } }; @@ -766,6 +780,22 @@ export class SpokePoolClient extends BaseAbstractClient { fill.relayExecutionInfo.updatedMessageHash = getMessageHash(event.args.relayExecutionInfo.updatedMessage); } + // Sanity check that this event is not a duplicate. + if ( + this.fills[fill.originChainId] !== undefined && + this.fills[fill.originChainId].some( + (f) => f.transactionHash === fill.transactionHash && f.logIndex === fill.logIndex + ) + ) { + this.logger.warn({ + at: "SpokePoolClient#update", + chainId: this.chainId, + message: "Duplicate fill found", + fill, + }); + continue; + } + assign(this.fills, [fill.originChainId], [fill]); assign(this.depositHashesToFills, [this.getDepositHash(fill)], [fill]); } From 1ab05cc99224d3e2f227f0d4ed0f27628a9ce80c Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 12:56:04 -0500 Subject: [PATCH 04/38] lint --- src/clients/BundleDataClient/BundleDataClient.ts | 2 +- src/clients/SpokePoolClient.ts | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 27f85c71..08669b81 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -444,7 +444,7 @@ export class BundleDataClient { // Determine which bundle we should fetch from arweave, either the pending bundle or the latest // executed one. Both should have arweave data but if for some reason the arweave data is missing, // this function will load the bundle data from the most recent bundle data published to Arweave. - + let bundleBlockRanges = getImpliedBundleBlockRanges( hubPoolClient, this.clients.configStoreClient, diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index d516a0f3..3c4c9545 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -726,18 +726,16 @@ export class SpokePoolClient extends BaseAbstractClient { const slowFillRequest = { ...spreadEventWithBlockNumber(event), destinationChainId: this.chainId, - } as SlowFillRequestWithBlock; + } as SlowFillRequestWithBlock; if (eventName === "RequestedV3SlowFill") { slowFillRequest.messageHash = getMessageHash(slowFillRequest.message); } const depositHash = getRelayEventKey({ ...slowFillRequest, destinationChainId: this.chainId }); - + // Sanity check that this event is not a duplicate. - if ( - this.slowFillRequests[depositHash] !== undefined - ) { + if (this.slowFillRequests[depositHash] !== undefined) { this.logger.warn({ at: "SpokePoolClient#update", chainId: this.chainId, @@ -746,7 +744,7 @@ export class SpokePoolClient extends BaseAbstractClient { }); continue; } - + this.slowFillRequests[depositHash] ??= slowFillRequest; } }; From 7c30e1a60a86cd6130dae5865c790eccd94b0d1f Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 13:00:55 -0500 Subject: [PATCH 05/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 08669b81..64789113 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -83,6 +83,13 @@ function updateBundleDepositsV3(dict: BundleDepositsV3, deposit: V3DepositWithBl if (!dict?.[originChainId]?.[inputToken]) { assign(dict, [originChainId, inputToken], []); } + if ( + dict[originChainId][inputToken].some( + (d) => d.transactionHash === deposit.transactionHash && d.logIndex === deposit.logIndex + ) + ) { + throw new Error("Duplicate deposit in bundleDeposits"); + }; dict[originChainId][inputToken].push(deposit); } From 8c066af38dd10756ae0dd01814e32df704506db6 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 13:07:57 -0500 Subject: [PATCH 06/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 64789113..de847895 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -89,7 +89,7 @@ function updateBundleDepositsV3(dict: BundleDepositsV3, deposit: V3DepositWithBl ) ) { throw new Error("Duplicate deposit in bundleDeposits"); - }; + } dict[originChainId][inputToken].push(deposit); } From ce0be1dae58f9d634803ea60161943ea236682ea Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 16:34:17 -0500 Subject: [PATCH 07/38] Update SpokePoolClient.ts --- src/clients/SpokePoolClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index 3c4c9545..754875e4 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -663,7 +663,7 @@ export class SpokePoolClient extends BaseAbstractClient { return e.transactionHash === deposit.transactionHash && e.logIndex === deposit.logIndex; }) ) { - this.logger.warn({ + this.logger.error({ at: "SpokePoolClient#update", chainId: this.chainId, message: "Duplicate deposit found with same transaction hash and log index", @@ -736,7 +736,7 @@ export class SpokePoolClient extends BaseAbstractClient { // Sanity check that this event is not a duplicate. if (this.slowFillRequests[depositHash] !== undefined) { - this.logger.warn({ + this.logger.error({ at: "SpokePoolClient#update", chainId: this.chainId, message: "Duplicate slow fill request found", @@ -785,7 +785,7 @@ export class SpokePoolClient extends BaseAbstractClient { (f) => f.transactionHash === fill.transactionHash && f.logIndex === fill.logIndex ) ) { - this.logger.warn({ + this.logger.error({ at: "SpokePoolClient#update", chainId: this.chainId, message: "Duplicate fill found", From bf3bae61529cd5f2f1adda0e398199eabc09a75d Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 16:35:57 -0500 Subject: [PATCH 08/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index de847895..7de933fc 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -1022,6 +1022,11 @@ export class BundleDataClient { } } } else { + this.logger.debug({ + at: "BundleDataClient#loadData", + message: "Duplicate fill detected", + fill, + }); throw new Error("Duplicate fill detected"); } return; @@ -1145,6 +1150,11 @@ export class BundleDataClient { validatedBundleSlowFills.push(matchedDeposit); } } else { + this.logger.debug({ + at: "BundleDataClient#loadData", + message: "Duplicate slow fill request detected", + slowFillRequest, + }); throw new Error("Duplicate slow fill request detected."); } return; From faebdb8a8020235f85c295d254ea2dd32536c3b4 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 16:46:28 -0500 Subject: [PATCH 09/38] exit after n == 4 --- src/clients/BundleDataClient/BundleDataClient.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 7de933fc..2bffefa1 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -464,8 +464,8 @@ export class BundleDataClient { // validated bundle, otherwise we'll grab the second most recently validated bundle. let n = hubPoolClient.hasPendingProposal() ? 1 : 2; - // eslint-disable-next-line no-constant-condition - while (true) { + // Try to find an older bundle that has arweave data but cut off after a few tries and just load data from scratch. + while (n < 4) { const bundleDataOnArweave = await this.getBundleDataFromArweave(bundleBlockRanges); if (!isDefined(bundleDataOnArweave)) { // Bundle data is not arweave, try the next most recently validated bundle. @@ -475,14 +475,16 @@ export class BundleDataClient { hubPoolClient.getNthFullyExecutedRootBundle(-n)! ); } else { - return { - blockRanges: bundleBlockRanges, - bundleData: await this.loadData(bundleBlockRanges, this.spokePoolClients, true), - }; + break; } n++; } + + return { + blockRanges: bundleBlockRanges, + bundleData: await this.loadData(bundleBlockRanges, this.spokePoolClients, true), + }; } async getLatestPoolRebalanceRoot(): Promise<{ root: PoolRebalanceRoot; blockRanges: number[][] }> { From 42fb574ba924ca723ead11c6c62048a4053fc918 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 16:51:35 -0500 Subject: [PATCH 10/38] Update SpokePoolClient.ts --- src/clients/SpokePoolClient.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index 754875e4..ead00e30 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -779,12 +779,10 @@ export class SpokePoolClient extends BaseAbstractClient { } // Sanity check that this event is not a duplicate. - if ( - this.fills[fill.originChainId] !== undefined && - this.fills[fill.originChainId].some( - (f) => f.transactionHash === fill.transactionHash && f.logIndex === fill.logIndex - ) - ) { + const duplicateFill = this.fills[fill.originChainId]?.find( + (f) => f.transactionHash === fill.transactionHash && f.logIndex === fill.logIndex + ); + if (duplicateFill) { this.logger.error({ at: "SpokePoolClient#update", chainId: this.chainId, From 04d3592e3139c7a7378dd3b868c92e65fe36aab5 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 17:16:39 -0500 Subject: [PATCH 11/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 2bffefa1..751d5c14 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -468,6 +468,12 @@ export class BundleDataClient { while (n < 4) { const bundleDataOnArweave = await this.getBundleDataFromArweave(bundleBlockRanges); if (!isDefined(bundleDataOnArweave)) { + this.logger.debug({ + at: "BundleDataClient#getLatestProposedBundleData", + message: `No bundle data found on arweave for ${this.getArweaveBundleDataClientKey( + bundleBlockRanges + )}, trying previous bundle block range`, + }); // Bundle data is not arweave, try the next most recently validated bundle. bundleBlockRanges = getImpliedBundleBlockRanges( hubPoolClient, From 7545cf232e6167288f320e1371ebce833c15eca6 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 19:06:28 -0500 Subject: [PATCH 12/38] reduce noisiness of logs --- src/clients/SpokePoolClient.ts | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index ead00e30..b845ff8f 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -587,6 +587,7 @@ export class SpokePoolClient extends BaseAbstractClient { * @see _update */ public async update(eventsToQuery = this.queryableEventNames): Promise { + const duplicateEvents: any[] = []; if (this.hubPoolClient !== null && !this.hubPoolClient.isUpdated) { throw new Error("HubPoolClient not updated"); } @@ -663,12 +664,7 @@ export class SpokePoolClient extends BaseAbstractClient { return e.transactionHash === deposit.transactionHash && e.logIndex === deposit.logIndex; }) ) { - this.logger.error({ - at: "SpokePoolClient#update", - chainId: this.chainId, - message: "Duplicate deposit found with same transaction hash and log index", - deposit, - }); + duplicateEvents.push(deposit); continue; } assign(this.duplicateDepositHashes, [getRelayEventKey(deposit)], [deposit]); @@ -736,12 +732,7 @@ export class SpokePoolClient extends BaseAbstractClient { // Sanity check that this event is not a duplicate. if (this.slowFillRequests[depositHash] !== undefined) { - this.logger.error({ - at: "SpokePoolClient#update", - chainId: this.chainId, - message: "Duplicate slow fill request found", - slowFillRequest, - }); + duplicateEvents.push(slowFillRequest); continue; } @@ -783,12 +774,7 @@ export class SpokePoolClient extends BaseAbstractClient { (f) => f.transactionHash === fill.transactionHash && f.logIndex === fill.logIndex ); if (duplicateFill) { - this.logger.error({ - at: "SpokePoolClient#update", - chainId: this.chainId, - message: "Duplicate fill found", - fill, - }); + duplicateEvents.push(duplicateFill); continue; } @@ -836,6 +822,12 @@ export class SpokePoolClient extends BaseAbstractClient { } } + if (duplicateEvents.length > 0) { + this.log("error", "Duplicate events found", { + duplicateEvents, + }); + } + // Next iteration should start off from where this one ended. this.currentTime = currentTime; if (this.oldestTime === 0) this.oldestTime = oldestTime; // Set oldest time only after the first update. From 2df77cf163d41be256d626858d4b5d4b5081fec8 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 19:10:19 -0500 Subject: [PATCH 13/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 751d5c14..7ebfee1f 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -88,7 +88,7 @@ function updateBundleDepositsV3(dict: BundleDepositsV3, deposit: V3DepositWithBl (d) => d.transactionHash === deposit.transactionHash && d.logIndex === deposit.logIndex ) ) { - throw new Error("Duplicate deposit in bundleDeposits"); + throw new Error(`Duplicate deposit in bundleDeposits: ${JSON.stringify(deposit)}`); } dict[originChainId][inputToken].push(deposit); } From 3f802dc1837d7d9dc42605550c1dcf291cb6c8df Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 19:15:38 -0500 Subject: [PATCH 14/38] Update SpokePoolClient.ts --- src/clients/SpokePoolClient.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index b845ff8f..cb3224c3 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -39,6 +39,7 @@ import { RelayerRefundExecutionWithBlock, RootBundleRelayWithBlock, SlowFillRequestWithBlock, + SortableEvent, SpeedUpWithBlock, TokensBridged, } from "../interfaces"; @@ -587,7 +588,7 @@ export class SpokePoolClient extends BaseAbstractClient { * @see _update */ public async update(eventsToQuery = this.queryableEventNames): Promise { - const duplicateEvents: any[] = []; + const duplicateEvents: SortableEvent[] = []; if (this.hubPoolClient !== null && !this.hubPoolClient.isUpdated) { throw new Error("HubPoolClient not updated"); } From f2429b88a5883f30755c63fe19dd4a73f7aa71ee Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 11 Feb 2025 10:16:21 -0500 Subject: [PATCH 15/38] Clean up logs --- .../BundleDataClient/BundleDataClient.ts | 19 ++++++++++++------- src/clients/SpokePoolClient.ts | 12 ++++++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 7ebfee1f..3f68826e 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -83,13 +83,6 @@ function updateBundleDepositsV3(dict: BundleDepositsV3, deposit: V3DepositWithBl if (!dict?.[originChainId]?.[inputToken]) { assign(dict, [originChainId, inputToken], []); } - if ( - dict[originChainId][inputToken].some( - (d) => d.transactionHash === deposit.transactionHash && d.logIndex === deposit.logIndex - ) - ) { - throw new Error(`Duplicate deposit in bundleDeposits: ${JSON.stringify(deposit)}`); - } dict[originChainId][inputToken].push(deposit); } @@ -917,6 +910,18 @@ export class BundleDataClient { "Not using correct bundle deposit hash key" ); if (deposit.blockNumber >= originChainBlockRange[0]) { + if ( + bundleDepositsV3[originChainId][deposit.inputToken].find( + (d) => d.transactionHash === deposit.transactionHash && d.logIndex === deposit.logIndex + ) + ) { + this.logger.debug({ + at: "BundleDataClient#loadData", + message: "Duplicate deposit detected", + deposit, + }); + throw new Error("Duplicate deposit detected"); + } bundleDepositHashes.push(newBundleDepositHash); updateBundleDepositsV3(bundleDepositsV3, deposit); } else if (deposit.blockNumber < originChainBlockRange[0]) { diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index cb3224c3..7fd164d7 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -39,7 +39,6 @@ import { RelayerRefundExecutionWithBlock, RootBundleRelayWithBlock, SlowFillRequestWithBlock, - SortableEvent, SpeedUpWithBlock, TokensBridged, } from "../interfaces"; @@ -588,7 +587,7 @@ export class SpokePoolClient extends BaseAbstractClient { * @see _update */ public async update(eventsToQuery = this.queryableEventNames): Promise { - const duplicateEvents: SortableEvent[] = []; + const duplicateEvents: Log[] = []; if (this.hubPoolClient !== null && !this.hubPoolClient.isUpdated) { throw new Error("HubPoolClient not updated"); } @@ -665,7 +664,7 @@ export class SpokePoolClient extends BaseAbstractClient { return e.transactionHash === deposit.transactionHash && e.logIndex === deposit.logIndex; }) ) { - duplicateEvents.push(deposit); + duplicateEvents.push(event); continue; } assign(this.duplicateDepositHashes, [getRelayEventKey(deposit)], [deposit]); @@ -733,7 +732,7 @@ export class SpokePoolClient extends BaseAbstractClient { // Sanity check that this event is not a duplicate. if (this.slowFillRequests[depositHash] !== undefined) { - duplicateEvents.push(slowFillRequest); + duplicateEvents.push(event); continue; } @@ -775,7 +774,7 @@ export class SpokePoolClient extends BaseAbstractClient { (f) => f.transactionHash === fill.transactionHash && f.logIndex === fill.logIndex ); if (duplicateFill) { - duplicateEvents.push(duplicateFill); + duplicateEvents.push(event); continue; } @@ -824,9 +823,10 @@ export class SpokePoolClient extends BaseAbstractClient { } if (duplicateEvents.length > 0) { - this.log("error", "Duplicate events found", { + this.log("debug", "Duplicate events listed", { duplicateEvents, }); + this.log("error", "Duplicate events detected, check debug logs"); } // Next iteration should start off from where this one ended. From 2b6a8ddfc42f63f697ac666c39d756bf74a93bcb Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 11 Feb 2025 10:20:51 -0500 Subject: [PATCH 16/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 3f68826e..f4c4ff54 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -486,7 +486,7 @@ export class BundleDataClient { }; } - async getLatestPoolRebalanceRoot(): Promise<{ root: PoolRebalanceRoot; blockRanges: number[][] }> { + async getLatestPoolRebalanceRootFromArweave(): Promise<{ root: PoolRebalanceRoot; blockRanges: number[][] }> { const { bundleData, blockRanges } = await this.getLatestProposedBundleData(); const hubPoolClient = this.clients.hubPoolClient; const root = _buildPoolRebalanceRoot( From 34ce1e0b60308cd37bd32b56966cad9f4e2031f1 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 11 Feb 2025 10:39:13 -0500 Subject: [PATCH 17/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index f4c4ff54..d0afcaa4 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -911,7 +911,7 @@ export class BundleDataClient { ); if (deposit.blockNumber >= originChainBlockRange[0]) { if ( - bundleDepositsV3[originChainId][deposit.inputToken].find( + bundleDepositsV3?.[originChainId]?.[deposit.inputToken].find( (d) => d.transactionHash === deposit.transactionHash && d.logIndex === deposit.logIndex ) ) { From ea48db5fdc8d096ac469e0b290f73846d6c3fffa Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 11 Feb 2025 10:54:22 -0500 Subject: [PATCH 18/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index d0afcaa4..c0616764 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -439,7 +439,10 @@ export class BundleDataClient { }, toBN(0)); } - private async getLatestProposedBundleData(): Promise<{ bundleData: LoadDataReturnValue; blockRanges: number[][] }> { + private async getLatestProposedBundleDataFromArweave(): Promise<{ + bundleData: LoadDataReturnValue; + blockRanges: number[][]; + }> { const hubPoolClient = this.clients.hubPoolClient; // Determine which bundle we should fetch from arweave, either the pending bundle or the latest // executed one. Both should have arweave data but if for some reason the arweave data is missing, @@ -462,7 +465,7 @@ export class BundleDataClient { const bundleDataOnArweave = await this.getBundleDataFromArweave(bundleBlockRanges); if (!isDefined(bundleDataOnArweave)) { this.logger.debug({ - at: "BundleDataClient#getLatestProposedBundleData", + at: "BundleDataClient#getLatestProposedBundleDataFromArweave", message: `No bundle data found on arweave for ${this.getArweaveBundleDataClientKey( bundleBlockRanges )}, trying previous bundle block range`, @@ -487,7 +490,7 @@ export class BundleDataClient { } async getLatestPoolRebalanceRootFromArweave(): Promise<{ root: PoolRebalanceRoot; blockRanges: number[][] }> { - const { bundleData, blockRanges } = await this.getLatestProposedBundleData(); + const { bundleData, blockRanges } = await this.getLatestProposedBundleDataFromArweave(); const hubPoolClient = this.clients.hubPoolClient; const root = _buildPoolRebalanceRoot( hubPoolClient.latestBlockSearched, From 331dd34d3061be4066e435378a902382b3e55034 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 11 Feb 2025 11:44:23 -0500 Subject: [PATCH 19/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index c0616764..4b5429a2 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -914,7 +914,7 @@ export class BundleDataClient { ); if (deposit.blockNumber >= originChainBlockRange[0]) { if ( - bundleDepositsV3?.[originChainId]?.[deposit.inputToken].find( + bundleDepositsV3?.[originChainId]?.[deposit.inputToken]?.find( (d) => d.transactionHash === deposit.transactionHash && d.logIndex === deposit.logIndex ) ) { From fafbbb381172fc60a0e390093e78dad3b82f2bef Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 12 Feb 2025 10:28:44 -0500 Subject: [PATCH 20/38] Update BundleDataClient.ts --- src/clients/BundleDataClient/BundleDataClient.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 4b5429a2..87baba88 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -448,20 +448,21 @@ export class BundleDataClient { // executed one. Both should have arweave data but if for some reason the arweave data is missing, // this function will load the bundle data from the most recent bundle data published to Arweave. - let bundleBlockRanges = getImpliedBundleBlockRanges( + const latestBundleBlockRanges = getImpliedBundleBlockRanges( hubPoolClient, this.clients.configStoreClient, hubPoolClient.hasPendingProposal() ? hubPoolClient.getLatestProposedRootBundle() : hubPoolClient.getNthFullyExecutedRootBundle(-1)! ); + let bundleBlockRanges = latestBundleBlockRanges; // Check if bundle data exists on arweave, otherwise fallback to last published bundle data. If the // first bundle block range we are trying is the pending proposal, then we'll grab the most recently // validated bundle, otherwise we'll grab the second most recently validated bundle. let n = hubPoolClient.hasPendingProposal() ? 1 : 2; // Try to find an older bundle that has arweave data but cut off after a few tries and just load data from scratch. - while (n < 4) { + while (n < 3) { const bundleDataOnArweave = await this.getBundleDataFromArweave(bundleBlockRanges); if (!isDefined(bundleDataOnArweave)) { this.logger.debug({ @@ -477,15 +478,22 @@ export class BundleDataClient { hubPoolClient.getNthFullyExecutedRootBundle(-n)! ); } else { + // Bundle block ranges have bundle data published on arweave, so use it: + return { + blockRanges: bundleBlockRanges, + bundleData: await this.loadData(bundleBlockRanges, this.spokePoolClients, true), + }; break; } n++; } + // None of the n bundles we looked at have arweave bundle data, so just load the pending bundle from scratch to + // at least get the latest data, albeit slowly. return { blockRanges: bundleBlockRanges, - bundleData: await this.loadData(bundleBlockRanges, this.spokePoolClients, true), + bundleData: await this.loadData(latestBundleBlockRanges, this.spokePoolClients, true), }; } From 9e3090bdb9d1926d0569b0a08393bb48362ef59a Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 12 Feb 2025 10:30:45 -0500 Subject: [PATCH 21/38] refactor --- .../BundleDataClient/BundleDataClient.ts | 155 +++++++++--------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 87baba88..cb6ee810 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -990,62 +990,7 @@ export class BundleDataClient { fillCounter++; const relayDataHash = getRelayEventKey(fill); if (v3RelayHashes[relayDataHash]) { - if (!v3RelayHashes[relayDataHash].fill) { - assert( - isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, - "Deposit should exist in relay hash dictionary." - ); - v3RelayHashes[relayDataHash].fill = fill; - if (fill.blockNumber >= destinationChainBlockRange[0]) { - const fillToRefund = await verifyFillRepayment( - fill, - destinationClient.spokePool.provider, - v3RelayHashes[relayDataHash].deposits![0], - this.clients.hubPoolClient - ); - if (!isDefined(fillToRefund)) { - bundleUnrepayableFillsV3.push(fill); - // We don't return here yet because we still need to mark unexecutable slow fill leaves - // or duplicate deposits. However, we won't issue a fast fill refund. - } else { - v3RelayHashes[relayDataHash].fill = fillToRefund; - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp, - }); - - // Now that we know this deposit has been filled on-chain, identify any duplicate deposits - // sent for this fill and refund them to the filler, because this value would not be paid out - // otherwise. These deposits can no longer expire and get refunded as an expired deposit, - // and they won't trigger a pre-fill refund because the fill is in this bundle. - // Pre-fill refunds only happen when deposits are sent in this bundle and the - // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how - // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or - // a current bundle fill. - const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1); - duplicateDeposits.forEach((duplicateDeposit) => { - if (isSlowFill(fill)) { - updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit); - } else { - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: duplicateDeposit.quoteTimestamp, - }); - } - }); - } - - // If fill replaced a slow fill request, then mark it as one that might have created an - // unexecutable slow fill. We can't know for sure until we check the slow fill request - // events. - if ( - fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill && - _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0]) - ) { - fastFillsReplacingSlowFills.push(relayDataHash); - } - } - } else { + if (v3RelayHashes[relayDataHash].fill) { this.logger.debug({ at: "BundleDataClient#loadData", message: "Duplicate fill detected", @@ -1053,6 +998,61 @@ export class BundleDataClient { }); throw new Error("Duplicate fill detected"); } + assert( + isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, + "Deposit should exist in relay hash dictionary." + ); + v3RelayHashes[relayDataHash].fill = fill; + if (fill.blockNumber >= destinationChainBlockRange[0]) { + const fillToRefund = await verifyFillRepayment( + fill, + destinationClient.spokePool.provider, + v3RelayHashes[relayDataHash].deposits![0], + this.clients.hubPoolClient + ); + if (!isDefined(fillToRefund)) { + bundleUnrepayableFillsV3.push(fill); + // We don't return here yet because we still need to mark unexecutable slow fill leaves + // or duplicate deposits. However, we won't issue a fast fill refund. + } else { + v3RelayHashes[relayDataHash].fill = fillToRefund; + validatedBundleV3Fills.push({ + ...fillToRefund, + quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp, + }); + + // Now that we know this deposit has been filled on-chain, identify any duplicate deposits + // sent for this fill and refund them to the filler, because this value would not be paid out + // otherwise. These deposits can no longer expire and get refunded as an expired deposit, + // and they won't trigger a pre-fill refund because the fill is in this bundle. + // Pre-fill refunds only happen when deposits are sent in this bundle and the + // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how + // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or + // a current bundle fill. + const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1); + duplicateDeposits.forEach((duplicateDeposit) => { + if (isSlowFill(fill)) { + updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit); + } else { + validatedBundleV3Fills.push({ + ...fillToRefund, + quoteTimestamp: duplicateDeposit.quoteTimestamp, + }); + } + }); + } + + // If fill replaced a slow fill request, then mark it as one that might have created an + // unexecutable slow fill. We can't know for sure until we check the slow fill request + // events. + if ( + fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill && + _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0]) + ) { + fastFillsReplacingSlowFills.push(relayDataHash); + } + } + return; } @@ -1152,28 +1152,7 @@ export class BundleDataClient { const relayDataHash = getRelayEventKey(slowFillRequest); if (v3RelayHashes[relayDataHash]) { - if (!v3RelayHashes[relayDataHash].slowFillRequest) { - v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest; - if (v3RelayHashes[relayDataHash].fill) { - // Exiting here assumes that slow fill requests must precede fills, so if there was a fill - // following this slow fill request, then we would have already seen it. We don't need to check - // for a fill older than this slow fill request. - return; - } - assert( - isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, - "Deposit should exist in relay hash dictionary." - ); - const matchedDeposit = v3RelayHashes[relayDataHash].deposits![0]; - - if ( - slowFillRequest.blockNumber >= destinationChainBlockRange[0] && - _canCreateSlowFillLeaf(matchedDeposit) && - !_depositIsExpired(matchedDeposit) - ) { - validatedBundleSlowFills.push(matchedDeposit); - } - } else { + if (v3RelayHashes[relayDataHash].slowFillRequest) { this.logger.debug({ at: "BundleDataClient#loadData", message: "Duplicate slow fill request detected", @@ -1181,6 +1160,26 @@ export class BundleDataClient { }); throw new Error("Duplicate slow fill request detected."); } + v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest; + if (v3RelayHashes[relayDataHash].fill) { + // Exiting here assumes that slow fill requests must precede fills, so if there was a fill + // following this slow fill request, then we would have already seen it. We don't need to check + // for a fill older than this slow fill request. + return; + } + assert( + isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, + "Deposit should exist in relay hash dictionary." + ); + const matchedDeposit = v3RelayHashes[relayDataHash].deposits![0]; + + if ( + slowFillRequest.blockNumber >= destinationChainBlockRange[0] && + _canCreateSlowFillLeaf(matchedDeposit) && + !_depositIsExpired(matchedDeposit) + ) { + validatedBundleSlowFills.push(matchedDeposit); + } return; } From 29b94649c4674a91b523f13f01989d1c9ff388b6 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 12 Feb 2025 11:09:30 -0500 Subject: [PATCH 22/38] duplicate event util --- src/clients/SpokePoolClient.ts | 7 +++---- src/utils/EventUtils.ts | 4 ++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index 7fd164d7..1dce56aa 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -21,6 +21,7 @@ import { toAddress, } from "../utils"; import { + duplicateEvent, paginatedEventQuery, sortEventsAscendingInPlace, spreadEvent, @@ -661,7 +662,7 @@ export class SpokePoolClient extends BaseAbstractClient { const allDeposits = this._getDuplicateDeposits(deposit).concat(this.depositHashes[getRelayEventKey(deposit)]); if ( allDeposits.some((e) => { - return e.transactionHash === deposit.transactionHash && e.logIndex === deposit.logIndex; + return duplicateEvent(deposit, e); }) ) { duplicateEvents.push(event); @@ -770,9 +771,7 @@ export class SpokePoolClient extends BaseAbstractClient { } // Sanity check that this event is not a duplicate. - const duplicateFill = this.fills[fill.originChainId]?.find( - (f) => f.transactionHash === fill.transactionHash && f.logIndex === fill.logIndex - ); + const duplicateFill = this.fills[fill.originChainId]?.find((f) => duplicateEvent(fill, f)); if (duplicateFill) { duplicateEvents.push(event); continue; diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 34af01c3..b47b18b6 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -266,3 +266,7 @@ export function isEventOlder(ex: T, ey: T): boolean { export function getTransactionHashes(events: SortableEvent[]): string[] { return [...Array.from(new Set(events.map((e) => e.transactionHash)))]; } + +export function duplicateEvent(a: SortableEvent, b: SortableEvent): boolean { + return a.transactionHash === b.transactionHash && a.logIndex === b.logIndex; +} From 30a1abb8dfcfd63dc48436d7ab203eca15c98ac0 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 12 Feb 2025 11:09:39 -0500 Subject: [PATCH 23/38] Add getPendingPoolRebalanceLeavesFromArweave --- .../BundleDataClient/BundleDataClient.ts | 111 ++++++------------ 1 file changed, 34 insertions(+), 77 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 2698f8b5..00066999 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -41,6 +41,7 @@ import { isZeroValueFillOrSlowFillRequest, chainIsEvm, isValidEvmAddress, + duplicateEvent, } from "../../utils"; import winston from "winston"; import { @@ -439,84 +440,44 @@ export class BundleDataClient { }, toBN(0)); } - private async getLatestProposedBundleDataFromArweave(): Promise<{ - bundleData: LoadDataReturnValue; - blockRanges: number[][]; - }> { + async getPendingPoolRebalanceLeavesFromArweave(): Promise { + if (!this.clients.hubPoolClient.hasPendingProposal()) { + return undefined; + } const hubPoolClient = this.clients.hubPoolClient; - // Determine which bundle we should fetch from arweave, either the pending bundle or the latest - // executed one. Both should have arweave data but if for some reason the arweave data is missing, - // this function will load the bundle data from the most recent bundle data published to Arweave. - - const latestBundleBlockRanges = getImpliedBundleBlockRanges( + const bundleBlockRanges = getImpliedBundleBlockRanges( hubPoolClient, this.clients.configStoreClient, - hubPoolClient.hasPendingProposal() - ? hubPoolClient.getLatestProposedRootBundle() - : hubPoolClient.getNthFullyExecutedRootBundle(-1)! + hubPoolClient.getLatestProposedRootBundle() ); - let bundleBlockRanges = latestBundleBlockRanges; - // Check if bundle data exists on arweave, otherwise fallback to last published bundle data. If the - // first bundle block range we are trying is the pending proposal, then we'll grab the most recently - // validated bundle, otherwise we'll grab the second most recently validated bundle. - let n = hubPoolClient.hasPendingProposal() ? 1 : 2; - - // Try to find an older bundle that has arweave data but cut off after a few tries and just load data from scratch. - while (n < 3) { - const bundleDataOnArweave = await this.getBundleDataFromArweave(bundleBlockRanges); - if (!isDefined(bundleDataOnArweave)) { - this.logger.debug({ - at: "BundleDataClient#getLatestProposedBundleDataFromArweave", - message: `No bundle data found on arweave for ${this.getArweaveBundleDataClientKey( - bundleBlockRanges - )}, trying previous bundle block range`, - }); - // Bundle data is not arweave, try the next most recently validated bundle. - bundleBlockRanges = getImpliedBundleBlockRanges( + // If bundle data is not on arweave, return undefined as this function is designed to return + // bundle data quickly rather than load from scratch. + const bundleDataOnArweave = await this.getBundleDataFromArweave(bundleBlockRanges); + if (!isDefined(bundleDataOnArweave)) { + return undefined; + } + const pendingBundleData = await this.loadData(bundleBlockRanges, this.spokePoolClients, true); + // This should be defined since we've checked that the data exists on Arweave, but handle the unexpected + // case where its not. + if (!isDefined(pendingBundleData)) { + return undefined; + } else { + const hubPoolClient = this.clients.hubPoolClient; + const root = _buildPoolRebalanceRoot( + hubPoolClient.latestBlockSearched, + bundleBlockRanges[0][1], + pendingBundleData.bundleDepositsV3, + pendingBundleData.bundleFillsV3, + pendingBundleData.bundleSlowFillsV3, + pendingBundleData.unexecutableSlowFills, + pendingBundleData.expiredDepositsToRefundV3, + { hubPoolClient, - this.clients.configStoreClient, - hubPoolClient.getNthFullyExecutedRootBundle(-n)! - ); - } else { - // Bundle block ranges have bundle data published on arweave, so use it: - return { - blockRanges: bundleBlockRanges, - bundleData: await this.loadData(bundleBlockRanges, this.spokePoolClients, true), - }; - break; - } - - n++; + configStoreClient: hubPoolClient.configStoreClient as AcrossConfigStoreClient, + } + ); + return root; } - - // None of the n bundles we looked at have arweave bundle data, so just load the pending bundle from scratch to - // at least get the latest data, albeit slowly. - return { - blockRanges: bundleBlockRanges, - bundleData: await this.loadData(latestBundleBlockRanges, this.spokePoolClients, true), - }; - } - - async getLatestPoolRebalanceRootFromArweave(): Promise<{ root: PoolRebalanceRoot; blockRanges: number[][] }> { - const { bundleData, blockRanges } = await this.getLatestProposedBundleDataFromArweave(); - const hubPoolClient = this.clients.hubPoolClient; - const root = _buildPoolRebalanceRoot( - hubPoolClient.latestBlockSearched, - blockRanges[0][1], - bundleData.bundleDepositsV3, - bundleData.bundleFillsV3, - bundleData.bundleSlowFillsV3, - bundleData.unexecutableSlowFills, - bundleData.expiredDepositsToRefundV3, - { - hubPoolClient, - configStoreClient: hubPoolClient.configStoreClient as AcrossConfigStoreClient, - } - ); - return { - root, - blockRanges, - }; } // @dev This function should probably be moved to the InventoryClient since it bypasses loadData completely now. @@ -905,11 +866,7 @@ export class BundleDataClient { "Not using correct bundle deposit hash key" ); if (deposit.blockNumber >= originChainBlockRange[0]) { - if ( - bundleDepositsV3?.[originChainId]?.[deposit.inputToken]?.find( - (d) => d.transactionHash === deposit.transactionHash && d.logIndex === deposit.logIndex - ) - ) { + if (bundleDepositsV3?.[originChainId]?.[deposit.inputToken]?.find((d) => duplicateEvent(deposit, d))) { this.logger.debug({ at: "BundleDataClient#loadData", message: "Duplicate deposit detected", From c90cb2fcd3803ca6fbb21cc91e015fde84faa004 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 12 Feb 2025 11:30:11 -0500 Subject: [PATCH 24/38] Update BundleDataClient.ts --- .../BundleDataClient/BundleDataClient.ts | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 00066999..b14e47e7 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -456,28 +456,21 @@ export class BundleDataClient { if (!isDefined(bundleDataOnArweave)) { return undefined; } - const pendingBundleData = await this.loadData(bundleBlockRanges, this.spokePoolClients, true); - // This should be defined since we've checked that the data exists on Arweave, but handle the unexpected - // case where its not. - if (!isDefined(pendingBundleData)) { - return undefined; - } else { - const hubPoolClient = this.clients.hubPoolClient; - const root = _buildPoolRebalanceRoot( - hubPoolClient.latestBlockSearched, - bundleBlockRanges[0][1], - pendingBundleData.bundleDepositsV3, - pendingBundleData.bundleFillsV3, - pendingBundleData.bundleSlowFillsV3, - pendingBundleData.unexecutableSlowFills, - pendingBundleData.expiredDepositsToRefundV3, - { - hubPoolClient, - configStoreClient: hubPoolClient.configStoreClient as AcrossConfigStoreClient, - } - ); - return root; - } + const pendingBundleData = await this.loadArweaveData(bundleBlockRanges); + const root = _buildPoolRebalanceRoot( + hubPoolClient.latestBlockSearched, + bundleBlockRanges[0][1], + pendingBundleData.bundleDepositsV3, + pendingBundleData.bundleFillsV3, + pendingBundleData.bundleSlowFillsV3, + pendingBundleData.unexecutableSlowFills, + pendingBundleData.expiredDepositsToRefundV3, + { + hubPoolClient, + configStoreClient: hubPoolClient.configStoreClient as AcrossConfigStoreClient, + } + ); + return root; } // @dev This function should probably be moved to the InventoryClient since it bypasses loadData completely now. From d2e23df71d523b4ffa23c8ca911b90ff82f00646 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 12 Feb 2025 14:14:57 -0500 Subject: [PATCH 25/38] fix --- .../BundleDataClient/BundleDataClient.ts | 37 +------------------ src/clients/HubPoolClient.ts | 22 ++++++----- 2 files changed, 13 insertions(+), 46 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index b14e47e7..5003b1f2 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -18,7 +18,7 @@ import { Deposit, DepositWithBlock, } from "../../interfaces"; -import { AcrossConfigStoreClient, SpokePoolClient } from ".."; +import { SpokePoolClient } from ".."; import { BigNumber, bnZero, @@ -45,7 +45,6 @@ import { } from "../../utils"; import winston from "winston"; import { - _buildPoolRebalanceRoot, BundleData, BundleDataSS, getEndBlockBuffers, @@ -53,7 +52,6 @@ import { getRefundsFromBundle, getWidestPossibleExpectedBlockRange, isChainDisabled, - PoolRebalanceRoot, prettyPrintV3SpokePoolEvents, V3DepositWithBlock, V3FillWithBlock, @@ -440,39 +438,6 @@ export class BundleDataClient { }, toBN(0)); } - async getPendingPoolRebalanceLeavesFromArweave(): Promise { - if (!this.clients.hubPoolClient.hasPendingProposal()) { - return undefined; - } - const hubPoolClient = this.clients.hubPoolClient; - const bundleBlockRanges = getImpliedBundleBlockRanges( - hubPoolClient, - this.clients.configStoreClient, - hubPoolClient.getLatestProposedRootBundle() - ); - // If bundle data is not on arweave, return undefined as this function is designed to return - // bundle data quickly rather than load from scratch. - const bundleDataOnArweave = await this.getBundleDataFromArweave(bundleBlockRanges); - if (!isDefined(bundleDataOnArweave)) { - return undefined; - } - const pendingBundleData = await this.loadArweaveData(bundleBlockRanges); - const root = _buildPoolRebalanceRoot( - hubPoolClient.latestBlockSearched, - bundleBlockRanges[0][1], - pendingBundleData.bundleDepositsV3, - pendingBundleData.bundleFillsV3, - pendingBundleData.bundleSlowFillsV3, - pendingBundleData.unexecutableSlowFills, - pendingBundleData.expiredDepositsToRefundV3, - { - hubPoolClient, - configStoreClient: hubPoolClient.configStoreClient as AcrossConfigStoreClient, - } - ); - return root; - } - // @dev This function should probably be moved to the InventoryClient since it bypasses loadData completely now. // Return refunds from the next valid bundle. This will contain any refunds that have been sent but are not included // in a valid bundle with all of its leaves executed. This contains refunds from: diff --git a/src/clients/HubPoolClient.ts b/src/clients/HubPoolClient.ts index 89d5e9d4..0f28cdee 100644 --- a/src/clients/HubPoolClient.ts +++ b/src/clients/HubPoolClient.ts @@ -776,17 +776,19 @@ export class HubPoolClient extends BaseAbstractClient { return endBlock > 0 ? endBlock + 1 : 0; } - getRunningBalanceBeforeBlockForChain(block: number, chain: number, l1Token: string): TokenRunningBalance { + getLatestExecutedRootBundleContainingL1Token(block: number, chain: number, l1Token: string): ExecutedRootBundle { // Search ExecutedRootBundles in descending block order to find the most recent event before the target block. - const executedRootBundle = sortEventsDescending(this.executedRootBundles).find( - (executedLeaf: ExecutedRootBundle) => { - return ( - executedLeaf.blockNumber <= block && - executedLeaf.chainId === chain && - executedLeaf.l1Tokens.map((l1Token) => l1Token.toLowerCase()).includes(l1Token.toLowerCase()) - ); - } - ) as ExecutedRootBundle; + return sortEventsDescending(this.executedRootBundles).find((executedLeaf: ExecutedRootBundle) => { + return ( + executedLeaf.blockNumber <= block && + executedLeaf.chainId === chain && + executedLeaf.l1Tokens.map((l1Token) => l1Token.toLowerCase()).includes(l1Token.toLowerCase()) + ); + }) as ExecutedRootBundle; + } + + getRunningBalanceBeforeBlockForChain(block: number, chain: number, l1Token: string): TokenRunningBalance { + const executedRootBundle = this.getLatestExecutedRootBundleContainingL1Token(block, chain, l1Token); return this.getRunningBalanceForToken(l1Token, executedRootBundle); } From 00e1d7b8219c777215641149ce7e1b7f9c17fa0c Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 12 Feb 2025 14:48:08 -0500 Subject: [PATCH 26/38] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c27ea106..47e854a4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@across-protocol/sdk", "author": "UMA Team", - "version": "4.1.9", + "version": "4.1.10-beta.0", "license": "AGPL-3.0", "homepage": "https://docs.across.to/reference/sdk", "files": [ From 6e3ca4d5092160aec395f92893b2a836058c5b54 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 12 Feb 2025 18:28:05 -0500 Subject: [PATCH 27/38] Update BundleDataClient.ts --- .../BundleDataClient/BundleDataClient.ts | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 5003b1f2..33b1271b 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -239,19 +239,6 @@ export class BundleDataClient { ); } - private async getBundleDataFromArweave(blockRangesForChains: number[][]) { - const persistedData = await this.clients.arweaveClient.getByTopic( - this.getArweaveBundleDataClientKey(blockRangesForChains), - BundleDataSS - ); - // If there is no data or the data is empty, return undefined because we couldn't - // pull info from the Arweave persistence layer. - if (!isDefined(persistedData) || persistedData.length < 1) { - return undefined; - } - return persistedData; - } - private async loadPersistedDataFromArweave( blockRangesForChains: number[][] ): Promise { @@ -259,8 +246,13 @@ export class BundleDataClient { return undefined; } const start = performance.now(); - const persistedData = await this.getBundleDataFromArweave(blockRangesForChains); - if (!isDefined(persistedData)) { + const persistedData = await this.clients.arweaveClient.getByTopic( + this.getArweaveBundleDataClientKey(blockRangesForChains), + BundleDataSS + ); + // If there is no data or the data is empty, return undefined because we couldn't + // pull info from the Arweave persistence layer. + if (!isDefined(persistedData) || persistedData.length < 1) { return undefined; } From 54741d64b08b19b767f73e75b2ae14d559b7cebb Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 09:53:22 -0500 Subject: [PATCH 28/38] Update BundleDataClient.ts --- .../BundleDataClient/BundleDataClient.ts | 144 +++++++++--------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 060c7d3b..84838419 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -893,57 +893,57 @@ export class BundleDataClient { }); throw new Error("Duplicate fill detected"); } - const { deposits } = v3RelayHashes[relayDataHash]; - assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary."); - v3RelayHashes[relayDataHash].fill = fill; - if (fill.blockNumber >= destinationChainBlockRange[0]) { - const fillToRefund = await verifyFillRepayment( - fill, - destinationClient.spokePool.provider, - deposits[0], - this.clients.hubPoolClient - ); - if (!isDefined(fillToRefund)) { - bundleUnrepayableFillsV3.push(fill); - // We don't return here yet because we still need to mark unexecutable slow fill leaves - // or duplicate deposits. However, we won't issue a fast fill refund. - } else { - v3RelayHashes[relayDataHash].fill = fillToRefund; - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: deposits[0].quoteTimestamp, - }); - - // Now that we know this deposit has been filled on-chain, identify any duplicate deposits - // sent for this fill and refund them to the filler, because this value would not be paid out - // otherwise. These deposits can no longer expire and get refunded as an expired deposit, - // and they won't trigger a pre-fill refund because the fill is in this bundle. - // Pre-fill refunds only happen when deposits are sent in this bundle and the - // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how - // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or - // a current bundle fill. - const duplicateDeposits = deposits.slice(1); - duplicateDeposits.forEach((duplicateDeposit) => { - if (isSlowFill(fill)) { - updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit); - } else { - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: duplicateDeposit.quoteTimestamp, - }); - } - }); - } - - // If fill replaced a slow fill request, then mark it as one that might have created an - // unexecutable slow fill. We can't know for sure until we check the slow fill request - // events. - if ( - fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill && - _canCreateSlowFillLeaf(deposits[0]) - ) { - fastFillsReplacingSlowFills.push(relayDataHash); - } + const { deposits } = v3RelayHashes[relayDataHash]; + assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary."); + v3RelayHashes[relayDataHash].fill = fill; + if (fill.blockNumber >= destinationChainBlockRange[0]) { + const fillToRefund = await verifyFillRepayment( + fill, + destinationClient.spokePool.provider, + deposits[0], + this.clients.hubPoolClient + ); + if (!isDefined(fillToRefund)) { + bundleUnrepayableFillsV3.push(fill); + // We don't return here yet because we still need to mark unexecutable slow fill leaves + // or duplicate deposits. However, we won't issue a fast fill refund. + } else { + v3RelayHashes[relayDataHash].fill = fillToRefund; + validatedBundleV3Fills.push({ + ...fillToRefund, + quoteTimestamp: deposits[0].quoteTimestamp, + }); + + // Now that we know this deposit has been filled on-chain, identify any duplicate deposits + // sent for this fill and refund them to the filler, because this value would not be paid out + // otherwise. These deposits can no longer expire and get refunded as an expired deposit, + // and they won't trigger a pre-fill refund because the fill is in this bundle. + // Pre-fill refunds only happen when deposits are sent in this bundle and the + // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how + // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or + // a current bundle fill. + const duplicateDeposits = deposits.slice(1); + duplicateDeposits.forEach((duplicateDeposit) => { + if (isSlowFill(fill)) { + updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit); + } else { + validatedBundleV3Fills.push({ + ...fillToRefund, + quoteTimestamp: duplicateDeposit.quoteTimestamp, + }); + } + }); + } + + // If fill replaced a slow fill request, then mark it as one that might have created an + // unexecutable slow fill. We can't know for sure until we check the slow fill request + // events. + if ( + fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill && + _canCreateSlowFillLeaf(deposits[0]) + ) { + fastFillsReplacingSlowFills.push(relayDataHash); + } } assert( isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, @@ -1099,31 +1099,32 @@ export class BundleDataClient { const relayDataHash = getRelayEventKey(slowFillRequest); if (v3RelayHashes[relayDataHash]) { - if (v3RelayHashes[relayDataHash].slowFillRequest) { + if (!v3RelayHashes[relayDataHash].slowFillRequest) { + v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest; + const { deposits, fill } = v3RelayHashes[relayDataHash]; + if (fill) { + // Exiting here assumes that slow fill requests must precede fills, so if there was a fill + // following this slow fill request, then we would have already seen it. We don't need to check + // for a fill older than this slow fill request. + return; + } + assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary."); + const matchedDeposit = deposits[0]; + + if ( + slowFillRequest.blockNumber >= destinationChainBlockRange[0] && + _canCreateSlowFillLeaf(matchedDeposit) && + !_depositIsExpired(matchedDeposit) + ) { + validatedBundleSlowFills.push(matchedDeposit); + } + } else { this.logger.debug({ at: "BundleDataClient#loadData", message: "Duplicate slow fill request detected", slowFillRequest, }); - throw new Error("Duplicate slow fill request detected"); - } - v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest; - const { deposits, fill } = v3RelayHashes[relayDataHash]; - if (fill) { - // Exiting here assumes that slow fill requests must precede fills, so if there was a fill - // following this slow fill request, then we would have already seen it. We don't need to check - // for a fill older than this slow fill request. - return; - } - assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary."); - const matchedDeposit = deposits[0]; - - if ( - slowFillRequest.blockNumber >= destinationChainBlockRange[0] && - _canCreateSlowFillLeaf(matchedDeposit) && - !_depositIsExpired(matchedDeposit) - ) { - validatedBundleSlowFills.push(matchedDeposit); + throw new Error("Duplicate slow fill request detected."); } v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest; if (v3RelayHashes[relayDataHash].fill) { @@ -1136,6 +1137,7 @@ export class BundleDataClient { isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, "Deposit should exist in relay hash dictionary." ); + const matchedDeposit = v3RelayHashes[relayDataHash].deposits![0]; if ( slowFillRequest.blockNumber >= destinationChainBlockRange[0] && From 72648543ed261db06c928e6daf5eefcc459d89ac Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 09:54:06 -0500 Subject: [PATCH 29/38] Update BundleDataClient.ts --- .../BundleDataClient/BundleDataClient.ts | 121 +++++++++--------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 84838419..9341c868 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -885,14 +885,7 @@ export class BundleDataClient { fillCounter++; const relayDataHash = getRelayEventKey(fill); if (v3RelayHashes[relayDataHash]) { - if (v3RelayHashes[relayDataHash].fill) { - this.logger.debug({ - at: "BundleDataClient#loadData", - message: "Duplicate fill detected", - fill, - }); - throw new Error("Duplicate fill detected"); - } + if (!v3RelayHashes[relayDataHash].fill) { const { deposits } = v3RelayHashes[relayDataHash]; assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary."); v3RelayHashes[relayDataHash].fill = fill; @@ -944,63 +937,71 @@ export class BundleDataClient { ) { fastFillsReplacingSlowFills.push(relayDataHash); } - } - assert( - isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, - "Deposit should exist in relay hash dictionary." - ); - v3RelayHashes[relayDataHash].fill = fill; - if (fill.blockNumber >= destinationChainBlockRange[0]) { - const fillToRefund = await verifyFillRepayment( - fill, - destinationClient.spokePool.provider, - v3RelayHashes[relayDataHash].deposits![0], - this.clients.hubPoolClient + } + assert( + isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, + "Deposit should exist in relay hash dictionary." ); - if (!isDefined(fillToRefund)) { - bundleUnrepayableFillsV3.push(fill); - // We don't return here yet because we still need to mark unexecutable slow fill leaves - // or duplicate deposits. However, we won't issue a fast fill refund. - } else { - v3RelayHashes[relayDataHash].fill = fillToRefund; - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp, - }); + v3RelayHashes[relayDataHash].fill = fill; + if (fill.blockNumber >= destinationChainBlockRange[0]) { + const fillToRefund = await verifyFillRepayment( + fill, + destinationClient.spokePool.provider, + v3RelayHashes[relayDataHash].deposits![0], + this.clients.hubPoolClient + ); + if (!isDefined(fillToRefund)) { + bundleUnrepayableFillsV3.push(fill); + // We don't return here yet because we still need to mark unexecutable slow fill leaves + // or duplicate deposits. However, we won't issue a fast fill refund. + } else { + v3RelayHashes[relayDataHash].fill = fillToRefund; + validatedBundleV3Fills.push({ + ...fillToRefund, + quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp, + }); - // Now that we know this deposit has been filled on-chain, identify any duplicate deposits - // sent for this fill and refund them to the filler, because this value would not be paid out - // otherwise. These deposits can no longer expire and get refunded as an expired deposit, - // and they won't trigger a pre-fill refund because the fill is in this bundle. - // Pre-fill refunds only happen when deposits are sent in this bundle and the - // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how - // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or - // a current bundle fill. - const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1); - duplicateDeposits.forEach((duplicateDeposit) => { - if (isSlowFill(fill)) { - updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit); - } else { - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: duplicateDeposit.quoteTimestamp, - }); - } - }); - } + // Now that we know this deposit has been filled on-chain, identify any duplicate deposits + // sent for this fill and refund them to the filler, because this value would not be paid out + // otherwise. These deposits can no longer expire and get refunded as an expired deposit, + // and they won't trigger a pre-fill refund because the fill is in this bundle. + // Pre-fill refunds only happen when deposits are sent in this bundle and the + // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how + // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or + // a current bundle fill. + const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1); + duplicateDeposits.forEach((duplicateDeposit) => { + if (isSlowFill(fill)) { + updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit); + } else { + validatedBundleV3Fills.push({ + ...fillToRefund, + quoteTimestamp: duplicateDeposit.quoteTimestamp, + }); + } + }); + } - // If fill replaced a slow fill request, then mark it as one that might have created an - // unexecutable slow fill. We can't know for sure until we check the slow fill request - // events. - if ( - fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill && - _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0]) - ) { - fastFillsReplacingSlowFills.push(relayDataHash); + // If fill replaced a slow fill request, then mark it as one that might have created an + // unexecutable slow fill. We can't know for sure until we check the slow fill request + // events. + if ( + fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill && + _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0]) + ) { + fastFillsReplacingSlowFills.push(relayDataHash); + } } - } - return; + return; + } + } else { + this.logger.debug({ + at: "BundleDataClient#loadData", + message: "Duplicate fill detected", + fill, + }); + throw new Error("Duplicate fill detected"); } // At this point, there is no relay hash dictionary entry for this fill, so we need to From 412fa8f9e089b45231074fbef28d1ebf16fa750c Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 09:54:49 -0500 Subject: [PATCH 30/38] Revert "Update BundleDataClient.ts" This reverts commit 72648543ed261db06c928e6daf5eefcc459d89ac. --- .../BundleDataClient/BundleDataClient.ts | 121 +++++++++--------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 9341c868..84838419 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -885,7 +885,14 @@ export class BundleDataClient { fillCounter++; const relayDataHash = getRelayEventKey(fill); if (v3RelayHashes[relayDataHash]) { - if (!v3RelayHashes[relayDataHash].fill) { + if (v3RelayHashes[relayDataHash].fill) { + this.logger.debug({ + at: "BundleDataClient#loadData", + message: "Duplicate fill detected", + fill, + }); + throw new Error("Duplicate fill detected"); + } const { deposits } = v3RelayHashes[relayDataHash]; assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary."); v3RelayHashes[relayDataHash].fill = fill; @@ -937,71 +944,63 @@ export class BundleDataClient { ) { fastFillsReplacingSlowFills.push(relayDataHash); } - } - assert( - isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, - "Deposit should exist in relay hash dictionary." + } + assert( + isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, + "Deposit should exist in relay hash dictionary." + ); + v3RelayHashes[relayDataHash].fill = fill; + if (fill.blockNumber >= destinationChainBlockRange[0]) { + const fillToRefund = await verifyFillRepayment( + fill, + destinationClient.spokePool.provider, + v3RelayHashes[relayDataHash].deposits![0], + this.clients.hubPoolClient ); - v3RelayHashes[relayDataHash].fill = fill; - if (fill.blockNumber >= destinationChainBlockRange[0]) { - const fillToRefund = await verifyFillRepayment( - fill, - destinationClient.spokePool.provider, - v3RelayHashes[relayDataHash].deposits![0], - this.clients.hubPoolClient - ); - if (!isDefined(fillToRefund)) { - bundleUnrepayableFillsV3.push(fill); - // We don't return here yet because we still need to mark unexecutable slow fill leaves - // or duplicate deposits. However, we won't issue a fast fill refund. - } else { - v3RelayHashes[relayDataHash].fill = fillToRefund; - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp, - }); - - // Now that we know this deposit has been filled on-chain, identify any duplicate deposits - // sent for this fill and refund them to the filler, because this value would not be paid out - // otherwise. These deposits can no longer expire and get refunded as an expired deposit, - // and they won't trigger a pre-fill refund because the fill is in this bundle. - // Pre-fill refunds only happen when deposits are sent in this bundle and the - // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how - // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or - // a current bundle fill. - const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1); - duplicateDeposits.forEach((duplicateDeposit) => { - if (isSlowFill(fill)) { - updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit); - } else { - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: duplicateDeposit.quoteTimestamp, - }); - } - }); - } + if (!isDefined(fillToRefund)) { + bundleUnrepayableFillsV3.push(fill); + // We don't return here yet because we still need to mark unexecutable slow fill leaves + // or duplicate deposits. However, we won't issue a fast fill refund. + } else { + v3RelayHashes[relayDataHash].fill = fillToRefund; + validatedBundleV3Fills.push({ + ...fillToRefund, + quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp, + }); - // If fill replaced a slow fill request, then mark it as one that might have created an - // unexecutable slow fill. We can't know for sure until we check the slow fill request - // events. - if ( - fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill && - _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0]) - ) { - fastFillsReplacingSlowFills.push(relayDataHash); - } + // Now that we know this deposit has been filled on-chain, identify any duplicate deposits + // sent for this fill and refund them to the filler, because this value would not be paid out + // otherwise. These deposits can no longer expire and get refunded as an expired deposit, + // and they won't trigger a pre-fill refund because the fill is in this bundle. + // Pre-fill refunds only happen when deposits are sent in this bundle and the + // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how + // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or + // a current bundle fill. + const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1); + duplicateDeposits.forEach((duplicateDeposit) => { + if (isSlowFill(fill)) { + updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit); + } else { + validatedBundleV3Fills.push({ + ...fillToRefund, + quoteTimestamp: duplicateDeposit.quoteTimestamp, + }); + } + }); } - return; + // If fill replaced a slow fill request, then mark it as one that might have created an + // unexecutable slow fill. We can't know for sure until we check the slow fill request + // events. + if ( + fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill && + _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0]) + ) { + fastFillsReplacingSlowFills.push(relayDataHash); + } } - } else { - this.logger.debug({ - at: "BundleDataClient#loadData", - message: "Duplicate fill detected", - fill, - }); - throw new Error("Duplicate fill detected"); + + return; } // At this point, there is no relay hash dictionary entry for this fill, so we need to From 337adc8403152e373342002e029e6f96d986cca6 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 09:55:53 -0500 Subject: [PATCH 31/38] Update BundleDataClient.ts --- .../BundleDataClient/BundleDataClient.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 84838419..69e75dda 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -885,14 +885,7 @@ export class BundleDataClient { fillCounter++; const relayDataHash = getRelayEventKey(fill); if (v3RelayHashes[relayDataHash]) { - if (v3RelayHashes[relayDataHash].fill) { - this.logger.debug({ - at: "BundleDataClient#loadData", - message: "Duplicate fill detected", - fill, - }); - throw new Error("Duplicate fill detected"); - } + if (!v3RelayHashes[relayDataHash].fill) { const { deposits } = v3RelayHashes[relayDataHash]; assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary."); v3RelayHashes[relayDataHash].fill = fill; @@ -944,6 +937,14 @@ export class BundleDataClient { ) { fastFillsReplacingSlowFills.push(relayDataHash); } + } else { + this.logger.debug({ + at: "BundleDataClient#loadData", + message: "Duplicate fill detected", + fill, + }); + throw new Error("Duplicate fill detected"); + } } assert( isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, From 988356aa2cc08d6c4192cc8fbcb4bd52b638fad4 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 09:57:54 -0500 Subject: [PATCH 32/38] Update BundleDataClient.ts --- .../BundleDataClient/BundleDataClient.ts | 72 +++---------------- 1 file changed, 10 insertions(+), 62 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 69e75dda..91e5a341 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -937,71 +937,19 @@ export class BundleDataClient { ) { fastFillsReplacingSlowFills.push(relayDataHash); } - } else { - this.logger.debug({ - at: "BundleDataClient#loadData", - message: "Duplicate fill detected", - fill, - }); - throw new Error("Duplicate fill detected"); } - } - assert( - isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, - "Deposit should exist in relay hash dictionary." - ); - v3RelayHashes[relayDataHash].fill = fill; - if (fill.blockNumber >= destinationChainBlockRange[0]) { - const fillToRefund = await verifyFillRepayment( - fill, - destinationClient.spokePool.provider, - v3RelayHashes[relayDataHash].deposits![0], - this.clients.hubPoolClient + assert( + isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, + "Deposit should exist in relay hash dictionary." ); - if (!isDefined(fillToRefund)) { - bundleUnrepayableFillsV3.push(fill); - // We don't return here yet because we still need to mark unexecutable slow fill leaves - // or duplicate deposits. However, we won't issue a fast fill refund. - } else { - v3RelayHashes[relayDataHash].fill = fillToRefund; - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp, - }); - - // Now that we know this deposit has been filled on-chain, identify any duplicate deposits - // sent for this fill and refund them to the filler, because this value would not be paid out - // otherwise. These deposits can no longer expire and get refunded as an expired deposit, - // and they won't trigger a pre-fill refund because the fill is in this bundle. - // Pre-fill refunds only happen when deposits are sent in this bundle and the - // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how - // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or - // a current bundle fill. - const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1); - duplicateDeposits.forEach((duplicateDeposit) => { - if (isSlowFill(fill)) { - updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit); - } else { - validatedBundleV3Fills.push({ - ...fillToRefund, - quoteTimestamp: duplicateDeposit.quoteTimestamp, - }); - } - }); - } - - // If fill replaced a slow fill request, then mark it as one that might have created an - // unexecutable slow fill. We can't know for sure until we check the slow fill request - // events. - if ( - fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill && - _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0]) - ) { - fastFillsReplacingSlowFills.push(relayDataHash); - } + } else { + this.logger.debug({ + at: "BundleDataClient#loadData", + message: "Duplicate fill detected", + fill, + }); + throw new Error("Duplicate fill detected"); } - - return; } // At this point, there is no relay hash dictionary entry for this fill, so we need to From 34b6010c35b10332d3e0de6d0c85d8e518e3e300 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 09:59:09 -0500 Subject: [PATCH 33/38] Update BundleDataClient.ts --- .../BundleDataClient/BundleDataClient.ts | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 91e5a341..9f982e16 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -938,10 +938,6 @@ export class BundleDataClient { fastFillsReplacingSlowFills.push(relayDataHash); } } - assert( - isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, - "Deposit should exist in relay hash dictionary." - ); } else { this.logger.debug({ at: "BundleDataClient#loadData", @@ -950,6 +946,7 @@ export class BundleDataClient { }); throw new Error("Duplicate fill detected"); } + return; } // At this point, there is no relay hash dictionary entry for this fill, so we need to @@ -1075,26 +1072,6 @@ export class BundleDataClient { }); throw new Error("Duplicate slow fill request detected."); } - v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest; - if (v3RelayHashes[relayDataHash].fill) { - // Exiting here assumes that slow fill requests must precede fills, so if there was a fill - // following this slow fill request, then we would have already seen it. We don't need to check - // for a fill older than this slow fill request. - return; - } - assert( - isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0, - "Deposit should exist in relay hash dictionary." - ); - const matchedDeposit = v3RelayHashes[relayDataHash].deposits![0]; - - if ( - slowFillRequest.blockNumber >= destinationChainBlockRange[0] && - _canCreateSlowFillLeaf(matchedDeposit) && - !_depositIsExpired(matchedDeposit) - ) { - validatedBundleSlowFills.push(matchedDeposit); - } return; } From aab61e9268a7f37782b17e92d39cf3170eb6f340 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Thu, 13 Feb 2025 10:01:27 -0500 Subject: [PATCH 34/38] Update src/clients/HubPoolClient.ts Co-authored-by: Paul <108695806+pxrl@users.noreply.github.com> --- src/clients/HubPoolClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/HubPoolClient.ts b/src/clients/HubPoolClient.ts index 12ee4b8d..bdc5a209 100644 --- a/src/clients/HubPoolClient.ts +++ b/src/clients/HubPoolClient.ts @@ -782,7 +782,7 @@ export class HubPoolClient extends BaseAbstractClient { return ( executedLeaf.blockNumber <= block && executedLeaf.chainId === chain && - executedLeaf.l1Tokens.map((l1Token) => l1Token.toLowerCase()).includes(l1Token.toLowerCase()) + executedLeaf.l1Tokens.some((token) => token.toLowerCase() === l1Token.toLowerCase() ); }) as ExecutedRootBundle; } From 329d44c2ad4e553a5bdb906b332aea411c0303db Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 10:03:55 -0500 Subject: [PATCH 35/38] Update HubPoolClient.ts --- src/clients/HubPoolClient.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/clients/HubPoolClient.ts b/src/clients/HubPoolClient.ts index bdc5a209..abfeb6b8 100644 --- a/src/clients/HubPoolClient.ts +++ b/src/clients/HubPoolClient.ts @@ -776,7 +776,11 @@ export class HubPoolClient extends BaseAbstractClient { return endBlock > 0 ? endBlock + 1 : 0; } - getLatestExecutedRootBundleContainingL1Token(block: number, chain: number, l1Token: string): ExecutedRootBundle { + getLatestExecutedRootBundleContainingL1Token( + block: number, + chain: number, + l1Token: string + ): ExecutedRootBundle | undefined { // Search ExecutedRootBundles in descending block order to find the most recent event before the target block. return sortEventsDescending(this.executedRootBundles).find((executedLeaf: ExecutedRootBundle) => { return ( @@ -784,7 +788,7 @@ export class HubPoolClient extends BaseAbstractClient { executedLeaf.chainId === chain && executedLeaf.l1Tokens.some((token) => token.toLowerCase() === l1Token.toLowerCase() ); - }) as ExecutedRootBundle; + }); } getRunningBalanceBeforeBlockForChain(block: number, chain: number, l1Token: string): TokenRunningBalance { @@ -793,7 +797,10 @@ export class HubPoolClient extends BaseAbstractClient { return this.getRunningBalanceForToken(l1Token, executedRootBundle); } - public getRunningBalanceForToken(l1Token: string, executedRootBundle: ExecutedRootBundle): TokenRunningBalance { + public getRunningBalanceForToken( + l1Token: string, + executedRootBundle: ExecutedRootBundle | undefined + ): TokenRunningBalance { let runningBalance = toBN(0); if (executedRootBundle) { const indexOfL1Token = executedRootBundle.l1Tokens From 4333390678ed02291e533cac34b0ec786b26076a Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 10:05:03 -0500 Subject: [PATCH 36/38] Update SpokePoolClient.ts --- src/clients/SpokePoolClient.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index 1dce56aa..a1e4dcca 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -660,11 +660,7 @@ export class SpokePoolClient extends BaseAbstractClient { if (this.depositHashes[getRelayEventKey(deposit)] !== undefined) { // Sanity check that this event is not a duplicate, even though the relay data hash is a duplicate. const allDeposits = this._getDuplicateDeposits(deposit).concat(this.depositHashes[getRelayEventKey(deposit)]); - if ( - allDeposits.some((e) => { - return duplicateEvent(deposit, e); - }) - ) { + if (allDeposits.some((e) => duplicateEvent(deposit, e))) { duplicateEvents.push(event); continue; } From 70773e24d757529c3e56a1287a8c20d58acf8632 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 10:09:35 -0500 Subject: [PATCH 37/38] Update HubPoolClient.ts --- src/clients/HubPoolClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/HubPoolClient.ts b/src/clients/HubPoolClient.ts index abfeb6b8..7a575da3 100644 --- a/src/clients/HubPoolClient.ts +++ b/src/clients/HubPoolClient.ts @@ -786,7 +786,7 @@ export class HubPoolClient extends BaseAbstractClient { return ( executedLeaf.blockNumber <= block && executedLeaf.chainId === chain && - executedLeaf.l1Tokens.some((token) => token.toLowerCase() === l1Token.toLowerCase() + executedLeaf.l1Tokens.some((token) => token.toLowerCase() === l1Token.toLowerCase()) ); }); } From 502ed7f5edb249bde259c03e8d1addd73b360d10 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 13 Feb 2025 13:46:27 -0500 Subject: [PATCH 38/38] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 47e854a4..27853f08 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@across-protocol/sdk", "author": "UMA Team", - "version": "4.1.10-beta.0", + "version": "4.1.10", "license": "AGPL-3.0", "homepage": "https://docs.across.to/reference/sdk", "files": [