@across-protocol/sdk 4.0.0-beta.34 → 4.0.0-beta.4

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