@across-protocol/sdk 4.0.0-beta.27 → 4.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/cjs/clients/BundleDataClient/BundleDataClient.d.ts +4 -5
  2. package/dist/cjs/clients/BundleDataClient/BundleDataClient.js +162 -318
  3. package/dist/cjs/clients/BundleDataClient/BundleDataClient.js.map +1 -1
  4. package/dist/cjs/clients/BundleDataClient/utils/DataworkerUtils.d.ts +2 -1
  5. package/dist/cjs/clients/BundleDataClient/utils/DataworkerUtils.js +2 -1
  6. package/dist/cjs/clients/BundleDataClient/utils/DataworkerUtils.js.map +1 -1
  7. package/dist/cjs/clients/BundleDataClient/utils/FillUtils.d.ts +1 -3
  8. package/dist/cjs/clients/BundleDataClient/utils/FillUtils.js +1 -33
  9. package/dist/cjs/clients/BundleDataClient/utils/FillUtils.js.map +1 -1
  10. package/dist/cjs/clients/SpokePoolClient.d.ts +0 -1
  11. package/dist/cjs/clients/SpokePoolClient.js +4 -13
  12. package/dist/cjs/clients/SpokePoolClient.js.map +1 -1
  13. package/dist/cjs/constants.d.ts +1 -1
  14. package/dist/cjs/constants.js +2 -2
  15. package/dist/cjs/constants.js.map +1 -1
  16. package/dist/cjs/providers/index.d.ts +0 -1
  17. package/dist/cjs/providers/index.js +0 -2
  18. package/dist/cjs/providers/index.js.map +1 -1
  19. package/dist/cjs/utils/AddressUtils.d.ts +0 -1
  20. package/dist/cjs/utils/AddressUtils.js +1 -14
  21. package/dist/cjs/utils/AddressUtils.js.map +1 -1
  22. package/dist/cjs/utils/CachingUtils.js +1 -1
  23. package/dist/cjs/utils/CachingUtils.js.map +1 -1
  24. package/dist/cjs/utils/DepositUtils.d.ts +1 -2
  25. package/dist/cjs/utils/DepositUtils.js +4 -13
  26. package/dist/cjs/utils/DepositUtils.js.map +1 -1
  27. package/dist/cjs/utils/NetworkUtils.d.ts +0 -1
  28. package/dist/cjs/utils/NetworkUtils.js +1 -6
  29. package/dist/cjs/utils/NetworkUtils.js.map +1 -1
  30. package/dist/cjs/utils/SpokeUtils.js +3 -3
  31. package/dist/cjs/utils/SpokeUtils.js.map +1 -1
  32. package/dist/esm/clients/BundleDataClient/BundleDataClient.d.ts +4 -5
  33. package/dist/esm/clients/BundleDataClient/BundleDataClient.js +186 -382
  34. package/dist/esm/clients/BundleDataClient/BundleDataClient.js.map +1 -1
  35. package/dist/esm/clients/BundleDataClient/utils/DataworkerUtils.d.ts +2 -1
  36. package/dist/esm/clients/BundleDataClient/utils/DataworkerUtils.js +3 -2
  37. package/dist/esm/clients/BundleDataClient/utils/DataworkerUtils.js.map +1 -1
  38. package/dist/esm/clients/BundleDataClient/utils/FillUtils.d.ts +1 -3
  39. package/dist/esm/clients/BundleDataClient/utils/FillUtils.js +1 -42
  40. package/dist/esm/clients/BundleDataClient/utils/FillUtils.js.map +1 -1
  41. package/dist/esm/clients/SpokePoolClient.d.ts +0 -8
  42. package/dist/esm/clients/SpokePoolClient.js +4 -20
  43. package/dist/esm/clients/SpokePoolClient.js.map +1 -1
  44. package/dist/esm/constants.d.ts +1 -1
  45. package/dist/esm/constants.js +2 -2
  46. package/dist/esm/constants.js.map +1 -1
  47. package/dist/esm/providers/index.d.ts +0 -1
  48. package/dist/esm/providers/index.js +0 -2
  49. package/dist/esm/providers/index.js.map +1 -1
  50. package/dist/esm/utils/AddressUtils.d.ts +0 -1
  51. package/dist/esm/utils/AddressUtils.js +0 -16
  52. package/dist/esm/utils/AddressUtils.js.map +1 -1
  53. package/dist/esm/utils/CachingUtils.js +1 -1
  54. package/dist/esm/utils/CachingUtils.js.map +1 -1
  55. package/dist/esm/utils/DepositUtils.d.ts +1 -2
  56. package/dist/esm/utils/DepositUtils.js +5 -14
  57. package/dist/esm/utils/DepositUtils.js.map +1 -1
  58. package/dist/esm/utils/NetworkUtils.d.ts +0 -6
  59. package/dist/esm/utils/NetworkUtils.js +0 -10
  60. package/dist/esm/utils/NetworkUtils.js.map +1 -1
  61. package/dist/esm/utils/SpokeUtils.js +4 -4
  62. package/dist/esm/utils/SpokeUtils.js.map +1 -1
  63. package/dist/types/clients/BundleDataClient/BundleDataClient.d.ts +4 -5
  64. package/dist/types/clients/BundleDataClient/BundleDataClient.d.ts.map +1 -1
  65. package/dist/types/clients/BundleDataClient/utils/DataworkerUtils.d.ts +2 -1
  66. package/dist/types/clients/BundleDataClient/utils/DataworkerUtils.d.ts.map +1 -1
  67. package/dist/types/clients/BundleDataClient/utils/FillUtils.d.ts +1 -3
  68. package/dist/types/clients/BundleDataClient/utils/FillUtils.d.ts.map +1 -1
  69. package/dist/types/clients/SpokePoolClient.d.ts +0 -8
  70. package/dist/types/clients/SpokePoolClient.d.ts.map +1 -1
  71. package/dist/types/constants.d.ts +1 -1
  72. package/dist/types/constants.d.ts.map +1 -1
  73. package/dist/types/providers/index.d.ts +0 -1
  74. package/dist/types/providers/index.d.ts.map +1 -1
  75. package/dist/types/utils/AddressUtils.d.ts +0 -1
  76. package/dist/types/utils/AddressUtils.d.ts.map +1 -1
  77. package/dist/types/utils/DepositUtils.d.ts +1 -2
  78. package/dist/types/utils/DepositUtils.d.ts.map +1 -1
  79. package/dist/types/utils/NetworkUtils.d.ts +0 -6
  80. package/dist/types/utils/NetworkUtils.d.ts.map +1 -1
  81. package/dist/types/utils/SpokeUtils.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/clients/BundleDataClient/BundleDataClient.ts +179 -382
  84. package/src/clients/BundleDataClient/utils/DataworkerUtils.ts +8 -0
  85. package/src/clients/BundleDataClient/utils/FillUtils.ts +2 -47
  86. package/src/clients/SpokePoolClient.ts +6 -19
  87. package/src/constants.ts +3 -3
  88. package/src/providers/index.ts +0 -1
  89. package/src/utils/AddressUtils.ts +0 -16
  90. package/src/utils/CachingUtils.ts +1 -1
  91. package/src/utils/DepositUtils.ts +5 -14
  92. package/src/utils/NetworkUtils.ts +0 -11
  93. package/src/utils/SpokeUtils.ts +6 -6
  94. package/dist/cjs/providers/mockProvider.d.ts +0 -19
  95. package/dist/cjs/providers/mockProvider.js +0 -70
  96. package/dist/cjs/providers/mockProvider.js.map +0 -1
  97. package/dist/esm/providers/mockProvider.d.ts +0 -23
  98. package/dist/esm/providers/mockProvider.js +0 -73
  99. package/dist/esm/providers/mockProvider.js.map +0 -1
  100. package/dist/types/providers/mockProvider.d.ts +0 -24
  101. package/dist/types/providers/mockProvider.d.ts.map +0 -1
  102. package/src/providers/mockProvider.ts +0 -77
@@ -33,13 +33,10 @@ import {
33
33
  getImpliedBundleBlockRanges,
34
34
  isSlowFill,
35
35
  mapAsync,
36
- filterAsync,
37
36
  bnUint32Max,
38
37
  isZeroValueDeposit,
39
38
  findFillEvent,
40
39
  isZeroValueFillOrSlowFillRequest,
41
- chainIsEvm,
42
- isValidEvmAddress,
43
40
  } from "../../utils";
44
41
  import winston from "winston";
45
42
  import {
@@ -54,9 +51,7 @@ import {
54
51
  prettyPrintV3SpokePoolEvents,
55
52
  V3DepositWithBlock,
56
53
  V3FillWithBlock,
57
- verifyFillRepayment,
58
54
  } from "./utils";
59
- import { PRE_FILL_MIN_CONFIG_STORE_VERSION } from "../../constants";
60
55
 
61
56
  // max(uint256) - 1
62
57
  export const INFINITE_FILL_DEADLINE = bnUint32Max;
@@ -65,10 +60,6 @@ type DataCache = Record<string, Promise<LoadDataReturnValue>>;
65
60
 
66
61
  // V3 dictionary helper functions
67
62
  function updateExpiredDepositsV3(dict: ExpiredDepositsToRefundV3, deposit: V3DepositWithBlock): void {
68
- // A deposit refund for a deposit is invalid if the depositor has a bytes32 address input for an EVM chain. It is valid otherwise.
69
- if (chainIsEvm(deposit.originChainId) && !isValidEvmAddress(deposit.depositor)) {
70
- return;
71
- }
72
63
  const { originChainId, inputToken } = deposit;
73
64
  if (!dict?.[originChainId]?.[inputToken]) {
74
65
  assign(dict, [originChainId, inputToken], []);
@@ -89,13 +80,8 @@ function updateBundleFillsV3(
89
80
  fill: V3FillWithBlock,
90
81
  lpFeePct: BigNumber,
91
82
  repaymentChainId: number,
92
- repaymentToken: string,
93
- repaymentAddress: string
83
+ repaymentToken: string
94
84
  ): void {
95
- // It is impossible to refund a deposit if the repayment chain is EVM and the relayer is a non-evm address.
96
- if (chainIsEvm(repaymentChainId) && !isValidEvmAddress(repaymentAddress)) {
97
- return;
98
- }
99
85
  if (!dict?.[repaymentChainId]?.[repaymentToken]) {
100
86
  assign(dict, [repaymentChainId, repaymentToken], {
101
87
  fills: [],
@@ -105,19 +91,19 @@ function updateBundleFillsV3(
105
91
  });
106
92
  }
107
93
 
108
- const bundleFill: BundleFillV3 = { ...fill, lpFeePct, relayer: repaymentAddress };
94
+ const bundleFill: BundleFillV3 = { ...fill, lpFeePct };
109
95
 
110
96
  // Add all fills, slow and fast, to dictionary.
111
97
  assign(dict, [repaymentChainId, repaymentToken, "fills"], [bundleFill]);
112
98
 
113
99
  // All fills update the bundle LP fees.
114
100
  const refundObj = dict[repaymentChainId][repaymentToken];
115
- const realizedLpFee = bundleFill.inputAmount.mul(bundleFill.lpFeePct).div(fixedPointAdjustment);
101
+ const realizedLpFee = fill.inputAmount.mul(bundleFill.lpFeePct).div(fixedPointAdjustment);
116
102
  refundObj.realizedLpFees = refundObj.realizedLpFees ? refundObj.realizedLpFees.add(realizedLpFee) : realizedLpFee;
117
103
 
118
104
  // Only fast fills get refunded.
119
- if (!isSlowFill(bundleFill)) {
120
- const refundAmount = bundleFill.inputAmount.mul(fixedPointAdjustment.sub(lpFeePct)).div(fixedPointAdjustment);
105
+ if (!isSlowFill(fill)) {
106
+ const refundAmount = fill.inputAmount.mul(fixedPointAdjustment.sub(lpFeePct)).div(fixedPointAdjustment);
121
107
  refundObj.totalRefundAmount = refundObj.totalRefundAmount
122
108
  ? refundObj.totalRefundAmount.add(refundAmount)
123
109
  : refundAmount;
@@ -125,10 +111,10 @@ function updateBundleFillsV3(
125
111
  // Instantiate dictionary if it doesn't exist.
126
112
  refundObj.refunds ??= {};
127
113
 
128
- if (refundObj.refunds[bundleFill.relayer]) {
129
- refundObj.refunds[bundleFill.relayer] = refundObj.refunds[bundleFill.relayer].add(refundAmount);
114
+ if (refundObj.refunds[fill.relayer]) {
115
+ refundObj.refunds[fill.relayer] = refundObj.refunds[fill.relayer].add(refundAmount);
130
116
  } else {
131
- refundObj.refunds[bundleFill.relayer] = refundAmount;
117
+ refundObj.refunds[fill.relayer] = refundAmount;
132
118
  }
133
119
  }
134
120
  }
@@ -248,6 +234,7 @@ export class BundleDataClient {
248
234
  bundleData: prettyPrintV3SpokePoolEvents(
249
235
  bundleData.bundleDepositsV3,
250
236
  bundleData.bundleFillsV3,
237
+ [], // Invalid fills are not persisted to Arweave.
251
238
  bundleData.bundleSlowFillsV3,
252
239
  bundleData.expiredDepositsToRefundV3,
253
240
  bundleData.unexecutableSlowFills
@@ -295,7 +282,7 @@ export class BundleDataClient {
295
282
  // so as not to affect this approximate refund count.
296
283
  const arweaveData = await this.loadArweaveData(bundleEvaluationBlockRanges);
297
284
  if (arweaveData === undefined) {
298
- combinedRefunds = await this.getApproximateRefundsForBlockRange(chainIds, bundleEvaluationBlockRanges);
285
+ combinedRefunds = this.getApproximateRefundsForBlockRange(chainIds, bundleEvaluationBlockRanges);
299
286
  } else {
300
287
  const { bundleFillsV3, expiredDepositsToRefundV3 } = arweaveData;
301
288
  combinedRefunds = getRefundsFromBundle(bundleFillsV3, expiredDepositsToRefundV3);
@@ -316,72 +303,50 @@ export class BundleDataClient {
316
303
  }
317
304
 
318
305
  // @dev This helper function should probably be moved to the InventoryClient
319
- async getApproximateRefundsForBlockRange(chainIds: number[], blockRanges: number[][]): Promise<CombinedRefunds> {
306
+ getApproximateRefundsForBlockRange(chainIds: number[], blockRanges: number[][]): CombinedRefunds {
320
307
  const refundsForChain: CombinedRefunds = {};
321
308
  for (const chainId of chainIds) {
322
309
  if (this.spokePoolClients[chainId] === undefined) {
323
310
  continue;
324
311
  }
325
312
  const chainIndex = chainIds.indexOf(chainId);
326
- // @dev This function does not account for pre-fill refunds as it is optimized for speed. The way to detect
327
- // pre-fill refunds is to load all deposits that are unmatched by fills in the spoke pool client's memory
328
- // and then query the FillStatus on-chain, but that might slow this function down too much. For now, we
329
- // will live with this expected inaccuracy as it should be small. The pre-fill would have to precede the deposit
330
- // by more than the caller's event lookback window which is expected to be unlikely.
331
- const fillsToCount = await filterAsync(this.spokePoolClients[chainId].getFills(), async (fill) => {
332
- if (
333
- fill.blockNumber < blockRanges[chainIndex][0] ||
334
- fill.blockNumber > blockRanges[chainIndex][1] ||
335
- isZeroValueFillOrSlowFillRequest(fill)
336
- ) {
337
- return false;
338
- }
313
+ this.spokePoolClients[chainId]
314
+ .getFills()
315
+ .filter((fill) => {
316
+ if (fill.blockNumber < blockRanges[chainIndex][0] || fill.blockNumber > blockRanges[chainIndex][1]) {
317
+ return false;
318
+ }
339
319
 
340
- // If origin spoke pool client isn't defined, we can't validate it.
341
- if (this.spokePoolClients[fill.originChainId] === undefined) {
342
- return false;
343
- }
344
- const matchingDeposit = this.spokePoolClients[fill.originChainId].getDeposit(fill.depositId);
345
- const hasMatchingDeposit =
346
- matchingDeposit !== undefined &&
347
- this.getRelayHashFromEvent(fill) === this.getRelayHashFromEvent(matchingDeposit);
348
- if (hasMatchingDeposit) {
349
- const validRepayment = await verifyFillRepayment(
350
- fill,
351
- this.spokePoolClients[fill.destinationChainId].spokePool.provider,
352
- matchingDeposit,
353
- // @dev: to get valid repayment chain ID's, get all chain IDs for the bundle block range and remove
354
- // disabled block ranges.
355
- this.clients.configStoreClient
356
- .getChainIdIndicesForBlock(blockRanges[0][1])
357
- .filter((_chainId, i) => !isChainDisabled(blockRanges[i]))
358
- );
359
- if (!isDefined(validRepayment)) {
320
+ // If origin spoke pool client isn't defined, we can't validate it.
321
+ if (this.spokePoolClients[fill.originChainId] === undefined) {
360
322
  return false;
361
323
  }
362
- }
363
- return hasMatchingDeposit;
364
- });
365
- fillsToCount.forEach((fill) => {
366
- const matchingDeposit = this.spokePoolClients[fill.originChainId].getDeposit(fill.depositId);
367
- assert(isDefined(matchingDeposit), "Deposit not found for fill.");
368
- const { chainToSendRefundTo, repaymentToken } = getRefundInformationFromFill(
369
- fill,
370
- this.clients.hubPoolClient,
371
- blockRanges,
372
- this.chainIdListForBundleEvaluationBlockNumbers,
373
- matchingDeposit!.fromLiteChain // Use ! because we've already asserted that matchingDeposit is defined.
374
- );
375
- // Assume that lp fees are 0 for the sake of speed. In the future we could batch compute
376
- // these or make hardcoded assumptions based on the origin-repayment chain direction. This might result
377
- // in slight over estimations of refunds, but its not clear whether underestimating or overestimating is
378
- // worst from the relayer's perspective.
379
- const { relayer, inputAmount: refundAmount } = fill;
380
- refundsForChain[chainToSendRefundTo] ??= {};
381
- refundsForChain[chainToSendRefundTo][repaymentToken] ??= {};
382
- const existingRefundAmount = refundsForChain[chainToSendRefundTo][repaymentToken][relayer] ?? bnZero;
383
- refundsForChain[chainToSendRefundTo][repaymentToken][relayer] = existingRefundAmount.add(refundAmount);
384
- });
324
+ const matchingDeposit = this.spokePoolClients[fill.originChainId].getDeposit(fill.depositId);
325
+ const hasMatchingDeposit =
326
+ matchingDeposit !== undefined &&
327
+ this.getRelayHashFromEvent(fill) === this.getRelayHashFromEvent(matchingDeposit);
328
+ return hasMatchingDeposit;
329
+ })
330
+ .forEach((fill) => {
331
+ const matchingDeposit = this.spokePoolClients[fill.originChainId].getDeposit(fill.depositId);
332
+ assert(isDefined(matchingDeposit), "Deposit not found for fill.");
333
+ const { chainToSendRefundTo, repaymentToken } = getRefundInformationFromFill(
334
+ fill,
335
+ this.clients.hubPoolClient,
336
+ blockRanges,
337
+ this.chainIdListForBundleEvaluationBlockNumbers,
338
+ matchingDeposit!.fromLiteChain // Use ! because we've already asserted that matchingDeposit is defined.
339
+ );
340
+ // Assume that lp fees are 0 for the sake of speed. In the future we could batch compute
341
+ // these or make hardcoded assumptions based on the origin-repayment chain direction. This might result
342
+ // in slight over estimations of refunds, but its not clear whether underestimating or overestimating is
343
+ // worst from the relayer's perspective.
344
+ const { relayer, inputAmount: refundAmount } = fill;
345
+ refundsForChain[chainToSendRefundTo] ??= {};
346
+ refundsForChain[chainToSendRefundTo][repaymentToken] ??= {};
347
+ const existingRefundAmount = refundsForChain[chainToSendRefundTo][repaymentToken][relayer] ?? bnZero;
348
+ refundsForChain[chainToSendRefundTo][repaymentToken][relayer] = existingRefundAmount.add(refundAmount);
349
+ });
385
350
  }
386
351
  return refundsForChain;
387
352
  }
@@ -508,7 +473,7 @@ export class BundleDataClient {
508
473
  // ok for this use case.
509
474
  const arweaveData = await this.loadArweaveData(pendingBundleBlockRanges);
510
475
  if (arweaveData === undefined) {
511
- combinedRefunds.push(await this.getApproximateRefundsForBlockRange(chainIds, pendingBundleBlockRanges));
476
+ combinedRefunds.push(this.getApproximateRefundsForBlockRange(chainIds, pendingBundleBlockRanges));
512
477
  } else {
513
478
  const { bundleFillsV3, expiredDepositsToRefundV3 } = arweaveData;
514
479
  combinedRefunds.push(getRefundsFromBundle(bundleFillsV3, expiredDepositsToRefundV3));
@@ -523,7 +488,7 @@ export class BundleDataClient {
523
488
  // - Only look up fills sent after the pending bundle's end blocks
524
489
  // - Skip LP fee computations and just assume the relayer is being refunded the full deposit.inputAmount
525
490
  const start = performance.now();
526
- combinedRefunds.push(await this.getApproximateRefundsForBlockRange(chainIds, widestBundleBlockRanges));
491
+ combinedRefunds.push(this.getApproximateRefundsForBlockRange(chainIds, widestBundleBlockRanges));
527
492
  this.logger.debug({
528
493
  at: "BundleDataClient#getNextBundleRefunds",
529
494
  message: `Loading approximate refunds for next bundle in ${Math.round(performance.now() - start) / 1000}s.`,
@@ -690,7 +655,6 @@ export class BundleDataClient {
690
655
  const bundleDepositsV3: BundleDepositsV3 = {}; // Deposits in bundle block range.
691
656
  const bundleFillsV3: BundleFillsV3 = {}; // Fills to refund in bundle block range.
692
657
  const bundleInvalidFillsV3: V3FillWithBlock[] = []; // Fills that are not valid in this bundle.
693
- const bundleUnrepayableFillsV3: V3FillWithBlock[] = []; // Fills that are not repayable in this bundle.
694
658
  const bundleSlowFillsV3: BundleSlowFills = {}; // Deposits that we need to send slow fills
695
659
  // for in this bundle.
696
660
  const expiredDepositsToRefundV3: ExpiredDepositsToRefundV3 = {};
@@ -777,7 +741,7 @@ export class BundleDataClient {
777
741
  // Note: Since there are no partial fills in v3, there should only be one fill per relay hash.
778
742
  // Moreover, the SpokePool blocks multiple slow fill requests, so
779
743
  // there should also only be one slow fill request per relay hash.
780
- deposits?: V3DepositWithBlock[];
744
+ deposit?: V3DepositWithBlock;
781
745
  fill?: V3FillWithBlock;
782
746
  slowFillRequest?: SlowFillRequestWithBlock;
783
747
  };
@@ -788,27 +752,6 @@ export class BundleDataClient {
788
752
  const bundleDepositHashes: string[] = [];
789
753
  const olderDepositHashes: string[] = [];
790
754
 
791
- const decodeBundleDepositHash = (depositHash: string): { relayDataHash: string; index: number } => {
792
- const [relayDataHash, i] = depositHash.split("@");
793
- return { relayDataHash, index: Number(i) };
794
- };
795
-
796
- // We use the following toggle to aid with the migration to pre-fills. The first bundle proposed using this
797
- // pre-fill logic can double refund pre-fills that have already been filled in the last bundle, because the
798
- // last bundle did not recognize a fill as a pre-fill. Therefore the developer should ensure that the version
799
- // is bumped to the PRE_FILL_MIN_CONFIG_STORE_VERSION version before the first pre-fill bundle is proposed.
800
- // To test the following bundle after this, the developer can set the FORCE_REFUND_PREFILLS environment variable
801
- // to "true" simulate the bundle with pre-fill refunds.
802
- // @todo Remove this logic once we have advanced sufficiently past the pre-fill migration.
803
- const startBlockForMainnet = getBlockRangeForChain(
804
- blockRangesForChains,
805
- this.clients.hubPoolClient.chainId,
806
- this.chainIdListForBundleEvaluationBlockNumbers
807
- )[0];
808
- const versionAtProposalBlock = this.clients.configStoreClient.getConfigStoreVersionForBlock(startBlockForMainnet);
809
- const canRefundPrefills =
810
- versionAtProposalBlock >= PRE_FILL_MIN_CONFIG_STORE_VERSION || process.env.FORCE_REFUND_PREFILLS === "true";
811
-
812
755
  let depositCounter = 0;
813
756
  for (const originChainId of allChainIds) {
814
757
  const originClient = spokePoolClients[originChainId];
@@ -827,15 +770,12 @@ export class BundleDataClient {
827
770
  }
828
771
  depositCounter++;
829
772
  const relayDataHash = this.getRelayHashFromEvent(deposit);
830
-
831
773
  if (!v3RelayHashes[relayDataHash]) {
832
774
  v3RelayHashes[relayDataHash] = {
833
- deposits: [deposit],
775
+ deposit: deposit,
834
776
  fill: undefined,
835
777
  slowFillRequest: undefined,
836
778
  };
837
- } else {
838
- v3RelayHashes[relayDataHash].deposits!.push(deposit);
839
779
  }
840
780
 
841
781
  // Once we've saved the deposit hash into v3RelayHashes, then we can exit early here if the inputAmount
@@ -846,20 +786,11 @@ export class BundleDataClient {
846
786
  return;
847
787
  }
848
788
 
849
- // Evaluate all expired deposits after fetching fill statuses,
850
- // since we can't know for certain whether an expired deposit was filled a long time ago.
851
- const newBundleDepositHash = `${relayDataHash}@${v3RelayHashes[relayDataHash].deposits!.length - 1}`;
852
- const decodedBundleDepositHash = decodeBundleDepositHash(newBundleDepositHash);
853
- assert(
854
- decodedBundleDepositHash.relayDataHash === relayDataHash &&
855
- decodedBundleDepositHash.index === v3RelayHashes[relayDataHash].deposits!.length - 1,
856
- "Not using correct bundle deposit hash key"
857
- );
858
789
  if (deposit.blockNumber >= originChainBlockRange[0]) {
859
- bundleDepositHashes.push(newBundleDepositHash);
790
+ bundleDepositHashes.push(relayDataHash);
860
791
  updateBundleDepositsV3(bundleDepositsV3, deposit);
861
792
  } else if (deposit.blockNumber < originChainBlockRange[0]) {
862
- olderDepositHashes.push(newBundleDepositHash);
793
+ olderDepositHashes.push(relayDataHash);
863
794
  }
864
795
  });
865
796
  }
@@ -884,7 +815,6 @@ export class BundleDataClient {
884
815
 
885
816
  const destinationClient = spokePoolClients[destinationChainId];
886
817
  const destinationChainBlockRange = getBlockRangeForChain(blockRangesForChains, destinationChainId, chainIds);
887
- const originChainBlockRange = getBlockRangeForChain(blockRangesForChains, originChainId, chainIds);
888
818
 
889
819
  // Keep track of fast fills that replaced slow fills, which we'll use to create "unexecutable" slow fills
890
820
  // if the slow fill request was sent in a prior bundle.
@@ -899,79 +829,43 @@ export class BundleDataClient {
899
829
  (fill) => fill.blockNumber <= destinationChainBlockRange[1] && !isZeroValueFillOrSlowFillRequest(fill)
900
830
  ),
901
831
  async (fill) => {
902
- fillCounter++;
903
832
  const relayDataHash = this.getRelayHashFromEvent(fill);
833
+ fillCounter++;
834
+
904
835
  if (v3RelayHashes[relayDataHash]) {
905
836
  if (!v3RelayHashes[relayDataHash].fill) {
906
837
  assert(
907
- isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0,
838
+ isDefined(v3RelayHashes[relayDataHash].deposit),
908
839
  "Deposit should exist in relay hash dictionary."
909
840
  );
910
841
  // At this point, the v3RelayHashes entry already existed meaning that there is a matching deposit,
911
- // so this fill can no longer be filled on-chain.
842
+ // so this fill is validated.
912
843
  v3RelayHashes[relayDataHash].fill = fill;
913
844
  if (fill.blockNumber >= destinationChainBlockRange[0]) {
914
- const fillToRefund = await verifyFillRepayment(
915
- fill,
916
- destinationClient.spokePool.provider,
917
- v3RelayHashes[relayDataHash].deposits![0],
918
- allChainIds
919
- );
920
- if (!isDefined(fillToRefund)) {
921
- bundleUnrepayableFillsV3.push(fill);
922
- // We don't return here yet because we still need to mark unexecutable slow fill leaves
923
- // or duplicate deposits. However, we won't issue a fast fill refund.
924
- } else {
925
- v3RelayHashes[relayDataHash].fill = fillToRefund;
926
- validatedBundleV3Fills.push({
927
- ...fillToRefund,
928
- quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp, // ! due to assert above
929
- });
930
-
931
- // Now that we know this deposit has been filled on-chain, identify any duplicate deposits
932
- // sent for this fill and refund them to the filler, because this value would not be paid out
933
- // otherwise. These deposits can no longer expire and get refunded as an expired deposit,
934
- // and they won't trigger a pre-fill refund because the fill is in this bundle.
935
- // Pre-fill refunds only happen when deposits are sent in this bundle and the
936
- // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how
937
- // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or
938
- // a current bundle fill. If the fill is a slow fill,
939
- const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1);
940
- duplicateDeposits.forEach((duplicateDeposit) => {
941
- // If fill is a slow fill, refund deposit to depositor, otherwise refund to filler.
942
- if (isSlowFill(fill)) {
943
- updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit);
944
- } else {
945
- validatedBundleV3Fills.push({
946
- ...fillToRefund,
947
- quoteTimestamp: duplicateDeposit.quoteTimestamp,
948
- });
949
- }
950
- });
951
- }
952
-
845
+ validatedBundleV3Fills.push({
846
+ ...fill,
847
+ quoteTimestamp: v3RelayHashes[relayDataHash].deposit!.quoteTimestamp, // ! due to assert above
848
+ });
953
849
  // If fill replaced a slow fill request, then mark it as one that might have created an
954
850
  // unexecutable slow fill. We can't know for sure until we check the slow fill request
955
851
  // events.
956
852
  // slow fill requests for deposits from or to lite chains are considered invalid
957
853
  if (
958
854
  fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill &&
959
- _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0])
855
+ _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposit!)
960
856
  ) {
961
857
  fastFillsReplacingSlowFills.push(relayDataHash);
962
858
  }
963
859
  }
964
- } else {
965
- throw new Error("Duplicate fill detected");
966
860
  }
967
861
  return;
968
862
  }
969
863
 
970
864
  // At this point, there is no relay hash dictionary entry for this fill, so we need to
971
- // instantiate the entry. We won't modify the fill.relayer until we match it with a deposit.
865
+ // instantiate the entry.
972
866
  v3RelayHashes[relayDataHash] = {
973
- deposits: undefined,
974
- fill,
867
+ deposit: undefined,
868
+ fill: fill,
975
869
  slowFillRequest: undefined,
976
870
  };
977
871
 
@@ -999,41 +893,16 @@ export class BundleDataClient {
999
893
  bundleInvalidFillsV3.push(fill);
1000
894
  } else {
1001
895
  const matchedDeposit = historicalDeposit.deposit;
1002
- // If deposit is in a following bundle, then this fill will have to refunded once that deposit
1003
- // is in the current bundle.
1004
- if (matchedDeposit.blockNumber > originChainBlockRange[1]) {
1005
- bundleInvalidFillsV3.push(fill);
1006
- return;
1007
- }
1008
- v3RelayHashes[relayDataHash].deposits = [matchedDeposit];
1009
-
1010
- const fillToRefund = await verifyFillRepayment(
1011
- fill,
1012
- destinationClient.spokePool.provider,
1013
- matchedDeposit,
1014
- allChainIds
1015
- );
1016
- if (!isDefined(fillToRefund)) {
1017
- bundleUnrepayableFillsV3.push(fill);
1018
- // Don't return yet as we still need to mark down any unexecutable slow fill leaves
1019
- // in case this fast fill replaced a slow fill request.
1020
- } else {
1021
- // @dev Since queryHistoricalDepositForFill validates the fill by checking individual
1022
- // object property values against the deposit's, we
1023
- // sanity check it here by comparing the full relay hashes. If there's an error here then the
1024
- // historical deposit query is not working as expected.
1025
- assert(this.getRelayHashFromEvent(matchedDeposit) === relayDataHash, "Relay hashes should match.");
1026
- validatedBundleV3Fills.push({
1027
- ...fillToRefund,
1028
- quoteTimestamp: matchedDeposit.quoteTimestamp,
1029
- });
1030
- v3RelayHashes[relayDataHash].fill = fillToRefund;
1031
-
1032
- // No need to check for duplicate deposits here since we would have seen them in memory if they
1033
- // had a non-infinite fill deadline, and duplicate deposits with infinite deadlines are impossible
1034
- // to send.
1035
- }
1036
-
896
+ // @dev Since queryHistoricalDepositForFill validates the fill by checking individual
897
+ // object property values against the deposit's, we
898
+ // sanity check it here by comparing the full relay hashes. If there's an error here then the
899
+ // historical deposit query is not working as expected.
900
+ assert(this.getRelayHashFromEvent(matchedDeposit) === relayDataHash, "Relay hashes should match.");
901
+ validatedBundleV3Fills.push({
902
+ ...fill,
903
+ quoteTimestamp: matchedDeposit.quoteTimestamp,
904
+ });
905
+ v3RelayHashes[relayDataHash].deposit = matchedDeposit;
1037
906
  // slow fill requests for deposits from or to lite chains are considered invalid
1038
907
  if (
1039
908
  fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill &&
@@ -1065,17 +934,15 @@ export class BundleDataClient {
1065
934
  v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest;
1066
935
  if (v3RelayHashes[relayDataHash].fill) {
1067
936
  // If there is a fill matching the relay hash, then this slow fill request can't be used
1068
- // to create a slow fill for a filled deposit. This takes advantage of the fact that
1069
- // slow fill requests must precede fills, so if there is a matching fill for this request's
1070
- // relay data, then this slow fill will be unexecutable.
937
+ // to create a slow fill for a filled deposit.
1071
938
  return;
1072
939
  }
1073
940
  assert(
1074
- isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0,
941
+ isDefined(v3RelayHashes[relayDataHash].deposit),
1075
942
  "Deposit should exist in relay hash dictionary."
1076
943
  );
1077
944
  // The ! is safe here because we've already checked that the deposit exists in the relay hash dictionary.
1078
- const matchedDeposit = v3RelayHashes[relayDataHash].deposits![0];
945
+ const matchedDeposit = v3RelayHashes[relayDataHash].deposit!;
1079
946
 
1080
947
  // If there is no fill matching the relay hash, then this might be a valid slow fill request
1081
948
  // that we should produce a slow fill leaf for. Check if the slow fill request is in the
@@ -1090,15 +957,13 @@ export class BundleDataClient {
1090
957
  // so this slow fill request relay data is correct.
1091
958
  validatedBundleSlowFills.push(matchedDeposit);
1092
959
  }
1093
- } else {
1094
- throw new Error("Duplicate slow fill request detected.");
1095
960
  }
1096
961
  return;
1097
962
  }
1098
963
 
1099
964
  // Instantiate dictionary if there is neither a deposit nor fill matching it.
1100
965
  v3RelayHashes[relayDataHash] = {
1101
- deposits: undefined,
966
+ deposit: undefined,
1102
967
  fill: undefined,
1103
968
  slowFillRequest: slowFillRequest,
1104
969
  };
@@ -1116,8 +981,8 @@ export class BundleDataClient {
1116
981
  // found using such a method) because infinite fill deadlines cannot be produced from the unsafeDepositV3()
1117
982
  // function.
1118
983
  if (
1119
- INFINITE_FILL_DEADLINE.eq(slowFillRequest.fillDeadline) &&
1120
- slowFillRequest.blockNumber >= destinationChainBlockRange[0]
984
+ slowFillRequest.blockNumber >= destinationChainBlockRange[0] &&
985
+ INFINITE_FILL_DEADLINE.eq(slowFillRequest.fillDeadline)
1121
986
  ) {
1122
987
  const historicalDeposit = await queryHistoricalDepositForFill(originClient, slowFillRequest);
1123
988
  if (!historicalDeposit.found) {
@@ -1125,11 +990,6 @@ export class BundleDataClient {
1125
990
  return;
1126
991
  }
1127
992
  const matchedDeposit: V3DepositWithBlock = historicalDeposit.deposit;
1128
- // If deposit is in a following bundle, then this slow fill request will have to be created
1129
- // once that deposit is in the current bundle.
1130
- if (matchedDeposit.blockNumber > originChainBlockRange[1]) {
1131
- return;
1132
- }
1133
993
  // @dev Since queryHistoricalDepositForFill validates the slow fill request by checking individual
1134
994
  // object property values against the deposit's, we
1135
995
  // sanity check it here by comparing the full relay hashes. If there's an error here then the
@@ -1138,7 +998,7 @@ export class BundleDataClient {
1138
998
  this.getRelayHashFromEvent(matchedDeposit) === relayDataHash,
1139
999
  "Deposit relay hashes should match."
1140
1000
  );
1141
- v3RelayHashes[relayDataHash].deposits = [matchedDeposit];
1001
+ v3RelayHashes[relayDataHash].deposit = matchedDeposit;
1142
1002
 
1143
1003
  if (
1144
1004
  !_canCreateSlowFillLeaf(matchedDeposit) ||
@@ -1159,139 +1019,122 @@ export class BundleDataClient {
1159
1019
  // - Or, has the deposit expired in this bundle? If so, then we need to issue an expiry refund.
1160
1020
  // - And finally, has the deposit been slow filled? If so, then we need to issue a slow fill leaf
1161
1021
  // for this "pre-slow-fill-request" if this request took place in a previous bundle.
1162
- await mapAsync(bundleDepositHashes, async (depositHash) => {
1163
- // We don't need to call verifyFillRepayment() here to replace the fill.relayer because this value should already
1164
- // be overwritten because the deposit and fill both exist.
1165
- const { relayDataHash, index } = decodeBundleDepositHash(depositHash);
1166
- const { deposits, fill, slowFillRequest } = v3RelayHashes[relayDataHash];
1167
- if (!deposits || deposits.length === 0) {
1168
- throw new Error("Deposits should exist in relay hash dictionary.");
1169
- }
1170
- const deposit = deposits[index];
1171
- if (!deposit) throw new Error("Deposit should exist in relay hash dictionary.");
1172
- if (deposit.originChainId !== originChainId || deposit.destinationChainId !== destinationChainId) {
1173
- return;
1174
- }
1175
-
1176
- // We are willing to refund a pre-fill multiple times for each duplicate deposit.
1177
- // This is because a duplicate deposit for a pre-fill cannot get
1178
- // refunded to the depositor anymore because its fill status on-chain has changed to Filled. Therefore
1179
- // any duplicate deposits result in a net loss of funds for the depositor and effectively pay out
1180
- // the pre-filler.
1181
-
1182
- // If fill exists in memory, then the only case in which we need to create a refund is if the
1183
- // the fill occurred in a previous bundle. There are no expiry refunds for filled deposits.
1184
- if (fill) {
1185
- if (canRefundPrefills && fill.blockNumber < destinationChainBlockRange[0]) {
1186
- // If fill is in the current bundle then we can assume there is already a refund for it, so only
1187
- // include this pre fill if the fill is in an older bundle. If fill is after this current bundle, then
1188
- // we won't consider it, following the previous treatment of fills after the bundle block range.
1189
- if (!isSlowFill(fill)) {
1022
+ const originBlockRange = getBlockRangeForChain(blockRangesForChains, originChainId, chainIds);
1023
+
1024
+ await mapAsync(
1025
+ bundleDepositHashes.filter((depositHash) => {
1026
+ const { deposit } = v3RelayHashes[depositHash];
1027
+ return (
1028
+ deposit &&
1029
+ deposit.originChainId === originChainId &&
1030
+ deposit.destinationChainId === destinationChainId &&
1031
+ deposit.blockNumber >= originBlockRange[0] &&
1032
+ deposit.blockNumber <= originBlockRange[1] &&
1033
+ !isZeroValueDeposit(deposit)
1034
+ );
1035
+ }),
1036
+ async (depositHash) => {
1037
+ const { deposit, fill, slowFillRequest } = v3RelayHashes[depositHash];
1038
+ if (!deposit) throw new Error("Deposit should exist in relay hash dictionary.");
1039
+
1040
+ // We are willing to refund a pre-fill multiple times for each duplicate deposit.
1041
+ // This is because a duplicate deposit for a pre-fill cannot get
1042
+ // refunded to the depositor anymore because its fill status on-chain has changed to Filled. Therefore
1043
+ // any duplicate deposits result in a net loss of funds for the depositor and effectively pay out
1044
+ // the pre-filler.
1045
+
1046
+ // If fill exists in memory, then the only case in which we need to create a refund is if the
1047
+ // the fill occurred in a previous bundle. There are no expiry refunds for filled deposits.
1048
+ if (fill) {
1049
+ if (fill.blockNumber < destinationChainBlockRange[0] && !isSlowFill(fill)) {
1050
+ // If fill is in the current bundle then we can assume there is already a refund for it, so only
1051
+ // include this pre fill if the fill is in an older bundle. If fill is after this current bundle, then
1052
+ // we won't consider it, following the previous treatment of fills after the bundle block range.
1190
1053
  validatedBundleV3Fills.push({
1191
1054
  ...fill,
1192
1055
  quoteTimestamp: deposit.quoteTimestamp,
1193
1056
  });
1194
- } else {
1195
- // Slow fills cannot result in refunds to a relayer to refund the deposit. Slow fills also
1196
- // were created after the deposit was sent, so we can assume this deposit is a duplicate.
1197
- updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
1198
1057
  }
1058
+ return;
1199
1059
  }
1200
- return;
1201
- }
1202
1060
 
1203
- // If a slow fill request exists in memory, then we know the deposit has not been filled because fills
1204
- // must follow slow fill requests and we would have seen the fill already if it existed. Therefore,
1205
- // we can conclude that either the deposit has expired and we need to create a deposit expiry refund, or
1206
- // we need to create a slow fill leaf for the deposit. The latter should only happen if the slow fill request
1207
- // took place in a prior bundle otherwise we would have already created a slow fill leaf for it.
1208
- if (slowFillRequest) {
1209
- if (_depositIsExpired(deposit)) {
1210
- updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
1211
- } else if (
1212
- canRefundPrefills &&
1213
- slowFillRequest.blockNumber < destinationChainBlockRange[0] &&
1214
- _canCreateSlowFillLeaf(deposit)
1215
- ) {
1216
- validatedBundleSlowFills.push(deposit);
1061
+ // If a slow fill request exists in memory, then we know the deposit has not been filled because fills
1062
+ // must follow slow fill requests and we would have seen the fill already if it existed. Therefore,
1063
+ // we can conclude that either the deposit has expired and we need to create a deposit expiry refund, or
1064
+ // we need to create a slow fill leaf for the deposit. The latter should only happen if the slow fill request
1065
+ // took place in a prior bundle otherwise we would have already created a slow fill leaf for it.
1066
+ if (slowFillRequest) {
1067
+ if (_depositIsExpired(deposit)) {
1068
+ updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
1069
+ } else if (
1070
+ slowFillRequest.blockNumber < destinationChainBlockRange[0] &&
1071
+ _canCreateSlowFillLeaf(deposit)
1072
+ ) {
1073
+ validatedBundleSlowFills.push(deposit);
1074
+ }
1075
+ return;
1217
1076
  }
1218
- return;
1219
- }
1220
1077
 
1221
- // So at this point in the code, there is no fill or slow fill request in memory for this deposit.
1222
- // We need to check its fill status on-chain to figure out whether to issue a refund or a slow fill leaf.
1223
- // We can assume at this point that all fills or slow fill requests, if found, were in previous bundles
1224
- // because the spoke pool client lookback would have returned this entire bundle of events and stored
1225
- // them into the relay hash dictionary.
1226
- const fillStatus = await _getFillStatusForDeposit(deposit, destinationChainBlockRange[1]);
1227
-
1228
- // If deposit was filled, then we need to issue a refund for the fill and also any duplicate deposits
1229
- // in the same bundle.
1230
- if (fillStatus === FillStatus.Filled) {
1231
- // We need to find the fill event to issue a refund to the right relayer and repayment chain,
1232
- // or msg.sender if relayer address is invalid for the repayment chain. We don't need to
1233
- // verify the fill block is before the bundle end block on the destination chain because
1234
- // we queried the fillStatus at the end block. Therefore, if the fill took place after the end block,
1235
- // then we wouldn't be in this branch of the code.
1236
- const prefill = await this.findMatchingFillEvent(deposit, destinationClient);
1237
- assert(isDefined(prefill), `findFillEvent# Cannot find prefill: ${relayDataHash}`);
1238
- assert(this.getRelayHashFromEvent(prefill!) === relayDataHash, "Relay hashes should match.");
1239
- if (canRefundPrefills) {
1240
- const verifiedFill = await verifyFillRepayment(
1241
- prefill!,
1242
- destinationClient.spokePool.provider,
1078
+ // So at this point in the code, there is no fill or slow fill request in memory for this deposit.
1079
+ // We need to check its fill status on-chain to figure out whether to issue a refund or a slow fill leaf.
1080
+ // We can assume at this point that all fills or slow fill requests, if found, were in previous bundles
1081
+ // because the spoke pool client lookback would have returned this entire bundle of events and stored
1082
+ // them into the relay hash dictionary.
1083
+ const fillStatus = await _getFillStatusForDeposit(deposit, destinationChainBlockRange[1]);
1084
+
1085
+ // If deposit was filled, then we need to issue a refund for it.
1086
+ if (fillStatus === FillStatus.Filled) {
1087
+ // We need to find the fill event to issue a refund to the right relayer and repayment chain,
1088
+ // or msg.sender if relayer address is invalid for the repayment chain.
1089
+ const prefill = (await findFillEvent(
1090
+ destinationClient.spokePool,
1243
1091
  deposit,
1244
- allChainIds
1245
- );
1246
- if (!isDefined(verifiedFill)) {
1247
- bundleUnrepayableFillsV3.push(prefill!);
1248
- } else if (!isSlowFill(verifiedFill)) {
1092
+ destinationClient.deploymentBlock,
1093
+ destinationClient.latestBlockSearched
1094
+ )) as unknown as FillWithBlock;
1095
+ if (!isSlowFill(prefill)) {
1249
1096
  validatedBundleV3Fills.push({
1250
- ...verifiedFill!,
1097
+ ...prefill,
1251
1098
  quoteTimestamp: deposit.quoteTimestamp,
1252
1099
  });
1253
- } else {
1254
- // Slow fills cannot result in refunds to a relayer to refund the deposit. Slow fills also
1255
- // were created after the deposit was sent, so we can assume this deposit is a duplicate.
1256
- updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
1257
1100
  }
1258
1101
  }
1259
- }
1260
- // If deposit is not filled and its newly expired, we can create a deposit refund for it.
1261
- // We don't check that fillDeadline >= bundleBlockTimestamps[destinationChainId][0] because
1262
- // that would eliminate any deposits in this bundle with a very low fillDeadline like equal to 0
1263
- // for example. Those should be included in this bundle of refunded deposits.
1264
- else if (_depositIsExpired(deposit)) {
1265
- updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
1266
- }
1267
- // If slow fill requested, then issue a slow fill leaf for the deposit.
1268
- else if (fillStatus === FillStatus.RequestedSlowFill) {
1269
- // Input and Output tokens must be equivalent on the deposit for this to be slow filled.
1270
- // Slow fill requests for deposits from or to lite chains are considered invalid
1271
- if (canRefundPrefills && _canCreateSlowFillLeaf(deposit)) {
1272
- // If deposit newly expired, then we can't create a slow fill leaf for it but we can
1273
- // create a deposit refund for it.
1274
- validatedBundleSlowFills.push(deposit);
1102
+ // If deposit is not filled and its newly expired, we can create a deposit refund for it.
1103
+ // We don't check that fillDeadline >= bundleBlockTimestamps[destinationChainId][0] because
1104
+ // that would eliminate any deposits in this bundle with a very low fillDeadline like equal to 0
1105
+ // for example. Those should be included in this bundle of refunded deposits.
1106
+ else if (_depositIsExpired(deposit)) {
1107
+ updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
1108
+ }
1109
+ // If slow fill requested, then issue a slow fill leaf for the deposit.
1110
+ else if (fillStatus === FillStatus.RequestedSlowFill) {
1111
+ // Input and Output tokens must be equivalent on the deposit for this to be slow filled.
1112
+ // Slow fill requests for deposits from or to lite chains are considered invalid
1113
+ if (_canCreateSlowFillLeaf(deposit)) {
1114
+ // If deposit newly expired, then we can't create a slow fill leaf for it but we can
1115
+ // create a deposit refund for it.
1116
+ validatedBundleSlowFills.push(deposit);
1117
+ }
1275
1118
  }
1276
1119
  }
1277
- });
1120
+ );
1278
1121
 
1279
1122
  // For all fills that came after a slow fill request, we can now check if the slow fill request
1280
1123
  // was a valid one and whether it was created in a previous bundle. If so, then it created a slow fill
1281
1124
  // leaf that is now unexecutable.
1282
1125
  fastFillsReplacingSlowFills.forEach((relayDataHash) => {
1283
- const { deposits, slowFillRequest, fill } = v3RelayHashes[relayDataHash];
1126
+ const { deposit, slowFillRequest, fill } = v3RelayHashes[relayDataHash];
1284
1127
  assert(
1285
1128
  fill?.relayExecutionInfo.fillType === FillType.ReplacedSlowFill,
1286
1129
  "Fill type should be ReplacedSlowFill."
1287
1130
  );
1288
1131
  // Needed for TSC - are implicitely checking that deposit exists by making it to this point.
1289
- if (!deposits || deposits.length < 1) {
1132
+ if (!deposit) {
1290
1133
  throw new Error("Deposit should exist in relay hash dictionary.");
1291
1134
  }
1292
1135
  // We should never push fast fills involving lite chains here because slow fill requests for them are invalid:
1293
1136
  assert(
1294
- _canCreateSlowFillLeaf(deposits[0]),
1137
+ _canCreateSlowFillLeaf(deposit),
1295
1138
  "fastFillsReplacingSlowFills should contain only deposits that can be slow filled"
1296
1139
  );
1297
1140
  const destinationBlockRange = getBlockRangeForChain(blockRangesForChains, destinationChainId, chainIds);
@@ -1301,7 +1144,7 @@ export class BundleDataClient {
1301
1144
  !slowFillRequest ||
1302
1145
  slowFillRequest.blockNumber < destinationBlockRange[0]
1303
1146
  ) {
1304
- validatedBundleUnexecutableSlowFills.push(deposits[0]);
1147
+ validatedBundleUnexecutableSlowFills.push(deposit);
1305
1148
  }
1306
1149
  });
1307
1150
  }
@@ -1315,14 +1158,10 @@ export class BundleDataClient {
1315
1158
  // For all deposits older than this bundle, we need to check if they expired in this bundle and if they did,
1316
1159
  // whether there was a slow fill created for it in a previous bundle that is now unexecutable and replaced
1317
1160
  // by a new expired deposit refund.
1318
- await forEachAsync(olderDepositHashes, async (depositHash) => {
1319
- const { relayDataHash, index } = decodeBundleDepositHash(depositHash);
1320
- const { deposits, slowFillRequest, fill } = v3RelayHashes[relayDataHash];
1321
- if (!deposits || deposits.length < 1) {
1322
- throw new Error("Deposit should exist in relay hash dictionary.");
1323
- }
1324
- const deposit = deposits[index];
1325
- const { destinationChainId } = deposit;
1161
+ await forEachAsync(olderDepositHashes, async (relayDataHash) => {
1162
+ const { deposit, slowFillRequest, fill } = v3RelayHashes[relayDataHash];
1163
+ assert(isDefined(deposit), "Deposit should exist in relay hash dictionary.");
1164
+ const { destinationChainId } = deposit!;
1326
1165
  const destinationBlockRange = getBlockRangeForChain(blockRangesForChains, destinationChainId, chainIds);
1327
1166
 
1328
1167
  // Only look for deposits that were mined before this bundle and that are newly expired.
@@ -1371,7 +1210,7 @@ export class BundleDataClient {
1371
1210
  validatedBundleV3Fills.length > 0
1372
1211
  ? this.clients.hubPoolClient.batchComputeRealizedLpFeePct(
1373
1212
  validatedBundleV3Fills.map((fill) => {
1374
- const matchedDeposit = v3RelayHashes[this.getRelayHashFromEvent(fill)].deposits![0];
1213
+ const matchedDeposit = v3RelayHashes[this.getRelayHashFromEvent(fill)].deposit;
1375
1214
  assert(isDefined(matchedDeposit), "Deposit should exist in relay hash dictionary.");
1376
1215
  const { chainToSendRefundTo: paymentChainId } = getRefundInformationFromFill(
1377
1216
  fill,
@@ -1415,7 +1254,7 @@ export class BundleDataClient {
1415
1254
  });
1416
1255
  v3FillLpFees.forEach(({ realizedLpFeePct }, idx) => {
1417
1256
  const fill = validatedBundleV3Fills[idx];
1418
- const associatedDeposit = v3RelayHashes[this.getRelayHashFromEvent(fill)].deposits![0];
1257
+ const associatedDeposit = v3RelayHashes[this.getRelayHashFromEvent(fill)].deposit;
1419
1258
  assert(isDefined(associatedDeposit), "Deposit should exist in relay hash dictionary.");
1420
1259
  const { chainToSendRefundTo, repaymentToken } = getRefundInformationFromFill(
1421
1260
  fill,
@@ -1424,15 +1263,10 @@ export class BundleDataClient {
1424
1263
  chainIds,
1425
1264
  associatedDeposit!.fromLiteChain
1426
1265
  );
1427
- updateBundleFillsV3(bundleFillsV3, fill, realizedLpFeePct, chainToSendRefundTo, repaymentToken, fill.relayer);
1266
+ updateBundleFillsV3(bundleFillsV3, fill, realizedLpFeePct, chainToSendRefundTo, repaymentToken);
1428
1267
  });
1429
1268
  v3SlowFillLpFees.forEach(({ realizedLpFeePct: lpFeePct }, idx) => {
1430
1269
  const deposit = validatedBundleSlowFills[idx];
1431
- // We should not create slow fill leaves for duplicate deposit hashes:
1432
- const relayDataHash = this.getRelayHashFromEvent(deposit);
1433
- if (validatedBundleSlowFills.slice(0, idx).some((d) => this.getRelayHashFromEvent(d) === relayDataHash)) {
1434
- return;
1435
- }
1436
1270
  updateBundleSlowFills(bundleSlowFillsV3, { ...deposit, lpFeePct });
1437
1271
  });
1438
1272
  v3UnexecutableSlowFillLpFees.forEach(({ realizedLpFeePct: lpFeePct }, idx) => {
@@ -1443,6 +1277,7 @@ export class BundleDataClient {
1443
1277
  const v3SpokeEventsReadable = prettyPrintV3SpokePoolEvents(
1444
1278
  bundleDepositsV3,
1445
1279
  bundleFillsV3,
1280
+ bundleInvalidFillsV3,
1446
1281
  bundleSlowFillsV3,
1447
1282
  expiredDepositsToRefundV3,
1448
1283
  unexecutableSlowFills
@@ -1457,15 +1292,6 @@ export class BundleDataClient {
1457
1292
  });
1458
1293
  }
1459
1294
 
1460
- if (bundleUnrepayableFillsV3.length > 0) {
1461
- this.logger.debug({
1462
- at: "BundleDataClient#loadData",
1463
- message: "Finished loading V3 spoke pool data and found some unrepayable V3 fills in range",
1464
- blockRangesForChains,
1465
- bundleUnrepayableFillsV3,
1466
- });
1467
- }
1468
-
1469
1295
  this.logger.debug({
1470
1296
  at: "BundleDataClient#loadDataFromScratch",
1471
1297
  message: `Computed bundle data in ${Math.round(performance.now() - start) / 1000}s.`,
@@ -1485,24 +1311,8 @@ export class BundleDataClient {
1485
1311
  // keccak256 hash of the relay data, which can be used as input into the on-chain `fillStatuses()` function in the
1486
1312
  // spoke pool contract. However, this internal function is used to uniquely identify a bridging event
1487
1313
  // for speed since its easier to build a string from the event data than to hash it.
1488
- protected getRelayHashFromEvent(event: V3DepositWithBlock | V3FillWithBlock | SlowFillRequestWithBlock): string {
1489
- return `${event.depositor}-${event.recipient}-${event.exclusiveRelayer}-${event.inputToken}-${event.outputToken}-${
1490
- event.inputAmount
1491
- }-${event.outputAmount}-${event.originChainId}-${event.depositId.toString()}-${event.fillDeadline}-${
1492
- event.exclusivityDeadline
1493
- }-${event.message}-${event.destinationChainId}`;
1494
- }
1495
-
1496
- protected async findMatchingFillEvent(
1497
- deposit: DepositWithBlock,
1498
- spokePoolClient: SpokePoolClient
1499
- ): Promise<FillWithBlock | undefined> {
1500
- return await findFillEvent(
1501
- spokePoolClient.spokePool,
1502
- deposit,
1503
- spokePoolClient.deploymentBlock,
1504
- spokePoolClient.latestBlockSearched
1505
- );
1314
+ private getRelayHashFromEvent(event: V3DepositWithBlock | V3FillWithBlock | SlowFillRequestWithBlock): string {
1315
+ return `${event.depositor}-${event.recipient}-${event.exclusiveRelayer}-${event.inputToken}-${event.outputToken}-${event.inputAmount}-${event.outputAmount}-${event.originChainId}-${event.depositId}-${event.fillDeadline}-${event.exclusivityDeadline}-${event.message}-${event.destinationChainId}`;
1506
1316
  }
1507
1317
 
1508
1318
  async getBundleBlockTimestamps(
@@ -1530,26 +1340,13 @@ export class BundleDataClient {
1530
1340
  // will usually be called in production with block ranges that were validated by
1531
1341
  // DataworkerUtils.blockRangesAreInvalidForSpokeClients.
1532
1342
  const startBlockForChain = Math.min(_startBlockForChain, spokePoolClient.latestBlockSearched);
1533
- // @dev Add 1 to the bundle end block. The thinking here is that there can be a gap between
1534
- // block timestamps in subsequent blocks. The bundle data client assumes that fill deadlines expire
1535
- // in exactly one bundle, therefore we must make sure that the bundle block timestamp for one bundle's
1536
- // end block is exactly equal to the bundle block timestamp for the next bundle's start block. This way
1537
- // there are no gaps in block timestamps between bundles.
1538
- const endBlockForChain = Math.min(_endBlockForChain + 1, spokePoolClient.latestBlockSearched);
1539
- const [startTime, _endTime] = [
1343
+ const endBlockForChain = Math.min(_endBlockForChain, spokePoolClient.latestBlockSearched);
1344
+ const [startTime, endTime] = [
1540
1345
  await spokePoolClient.getTimestampForBlock(startBlockForChain),
1541
1346
  await spokePoolClient.getTimestampForBlock(endBlockForChain),
1542
1347
  ];
1543
- // @dev similar to reasoning above to ensure no gaps between bundle block range timestamps and also
1544
- // no overlap, subtract 1 from the end time.
1545
- const endBlockDelta = endBlockForChain > startBlockForChain ? 1 : 0;
1546
- const endTime = Math.max(0, _endTime - endBlockDelta);
1547
-
1548
1348
  // Sanity checks:
1549
- assert(
1550
- endTime >= startTime,
1551
- `End time for block ${endBlockForChain} should be greater than start time for block ${startBlockForChain}: ${endTime} >= ${startTime}.`
1552
- );
1349
+ assert(endTime >= startTime, "End time should be greater than start time.");
1553
1350
  assert(
1554
1351
  startBlockForChain === 0 || startTime > 0,
1555
1352
  "Start timestamp must be greater than 0 if the start block is greater than 0."