@across-protocol/sdk 4.1.9 → 4.1.10-beta.0

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 (68) hide show
  1. package/dist/cjs/clients/BundleDataClient/BundleDataClient.d.ts +2 -6
  2. package/dist/cjs/clients/BundleDataClient/BundleDataClient.js +94 -114
  3. package/dist/cjs/clients/BundleDataClient/BundleDataClient.js.map +1 -1
  4. package/dist/cjs/clients/HubPoolClient.d.ts +1 -0
  5. package/dist/cjs/clients/HubPoolClient.js +10 -3
  6. package/dist/cjs/clients/HubPoolClient.js.map +1 -1
  7. package/dist/cjs/clients/SpokePoolClient.d.ts +3 -1
  8. package/dist/cjs/clients/SpokePoolClient.js +61 -38
  9. package/dist/cjs/clients/SpokePoolClient.js.map +1 -1
  10. package/dist/cjs/interfaces/HubPool.d.ts +4 -1
  11. package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.js +2 -3
  12. package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.js.map +1 -1
  13. package/dist/cjs/utils/CachingUtils.js +3 -3
  14. package/dist/cjs/utils/CachingUtils.js.map +1 -1
  15. package/dist/cjs/utils/EventUtils.d.ts +1 -0
  16. package/dist/cjs/utils/EventUtils.js +5 -1
  17. package/dist/cjs/utils/EventUtils.js.map +1 -1
  18. package/dist/cjs/utils/LogUtils.d.ts +1 -0
  19. package/dist/cjs/utils/LogUtils.js +7 -1
  20. package/dist/cjs/utils/LogUtils.js.map +1 -1
  21. package/dist/esm/clients/BundleDataClient/BundleDataClient.d.ts +2 -6
  22. package/dist/esm/clients/BundleDataClient/BundleDataClient.js +89 -110
  23. package/dist/esm/clients/BundleDataClient/BundleDataClient.js.map +1 -1
  24. package/dist/esm/clients/HubPoolClient.d.ts +1 -0
  25. package/dist/esm/clients/HubPoolClient.js +10 -3
  26. package/dist/esm/clients/HubPoolClient.js.map +1 -1
  27. package/dist/esm/clients/SpokePoolClient.d.ts +5 -3
  28. package/dist/esm/clients/SpokePoolClient.js +71 -44
  29. package/dist/esm/clients/SpokePoolClient.js.map +1 -1
  30. package/dist/esm/interfaces/HubPool.d.ts +4 -1
  31. package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.js +1 -2
  32. package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.js.map +1 -1
  33. package/dist/esm/utils/CachingUtils.js +1 -1
  34. package/dist/esm/utils/CachingUtils.js.map +1 -1
  35. package/dist/esm/utils/EventUtils.d.ts +1 -0
  36. package/dist/esm/utils/EventUtils.js +3 -0
  37. package/dist/esm/utils/EventUtils.js.map +1 -1
  38. package/dist/esm/utils/LogUtils.d.ts +7 -0
  39. package/dist/esm/utils/LogUtils.js +11 -0
  40. package/dist/esm/utils/LogUtils.js.map +1 -1
  41. package/dist/esm/utils/abi/typechain/Multicall3.d.ts +1 -4
  42. package/dist/esm/utils/abi/typechain/factories/Multicall3__factory.js.map +1 -1
  43. package/dist/types/clients/BundleDataClient/BundleDataClient.d.ts +2 -6
  44. package/dist/types/clients/BundleDataClient/BundleDataClient.d.ts.map +1 -1
  45. package/dist/types/clients/HubPoolClient.d.ts +1 -0
  46. package/dist/types/clients/HubPoolClient.d.ts.map +1 -1
  47. package/dist/types/clients/SpokePoolClient.d.ts +5 -3
  48. package/dist/types/clients/SpokePoolClient.d.ts.map +1 -1
  49. package/dist/types/interfaces/HubPool.d.ts +4 -1
  50. package/dist/types/interfaces/HubPool.d.ts.map +1 -1
  51. package/dist/types/relayFeeCalculator/chain-queries/baseQuery.d.ts.map +1 -1
  52. package/dist/types/utils/EventUtils.d.ts +1 -0
  53. package/dist/types/utils/EventUtils.d.ts.map +1 -1
  54. package/dist/types/utils/LogUtils.d.ts +7 -0
  55. package/dist/types/utils/LogUtils.d.ts.map +1 -1
  56. package/dist/types/utils/abi/typechain/Multicall3.d.ts +1 -4
  57. package/dist/types/utils/abi/typechain/Multicall3.d.ts.map +1 -1
  58. package/dist/types/utils/abi/typechain/common.d.ts.map +1 -1
  59. package/dist/types/utils/abi/typechain/factories/Multicall3__factory.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/src/clients/BundleDataClient/BundleDataClient.ts +127 -153
  62. package/src/clients/HubPoolClient.ts +17 -11
  63. package/src/clients/SpokePoolClient.ts +44 -10
  64. package/src/interfaces/HubPool.ts +4 -1
  65. package/src/relayFeeCalculator/chain-queries/baseQuery.ts +1 -1
  66. package/src/utils/CachingUtils.ts +1 -1
  67. package/src/utils/EventUtils.ts +4 -0
  68. package/src/utils/LogUtils.ts +12 -0
@@ -1,4 +1,3 @@
1
- import assert from "assert";
2
1
  import _ from "lodash";
3
2
  import {
4
3
  ProposedRootBundle,
@@ -19,12 +18,13 @@ import {
19
18
  Deposit,
20
19
  DepositWithBlock,
21
20
  } from "../../interfaces";
22
- import { AcrossConfigStoreClient, SpokePoolClient } from "..";
21
+ import { SpokePoolClient } from "..";
23
22
  import {
24
23
  BigNumber,
25
24
  bnZero,
26
25
  queryHistoricalDepositForFill,
27
26
  assign,
27
+ assert,
28
28
  fixedPointAdjustment,
29
29
  isDefined,
30
30
  toBN,
@@ -41,10 +41,10 @@ import {
41
41
  isZeroValueFillOrSlowFillRequest,
42
42
  chainIsEvm,
43
43
  isValidEvmAddress,
44
+ duplicateEvent,
44
45
  } from "../../utils";
45
46
  import winston from "winston";
46
47
  import {
47
- _buildPoolRebalanceRoot,
48
48
  BundleData,
49
49
  BundleDataSS,
50
50
  getEndBlockBuffers,
@@ -52,7 +52,6 @@ import {
52
52
  getRefundsFromBundle,
53
53
  getWidestPossibleExpectedBlockRange,
54
54
  isChainDisabled,
55
- PoolRebalanceRoot,
56
55
  prettyPrintV3SpokePoolEvents,
57
56
  V3DepositWithBlock,
58
57
  V3FillWithBlock,
@@ -240,13 +239,7 @@ export class BundleDataClient {
240
239
  );
241
240
  }
242
241
 
243
- private async loadPersistedDataFromArweave(
244
- blockRangesForChains: number[][]
245
- ): Promise<LoadDataReturnValue | undefined> {
246
- if (!isDefined(this.clients?.arweaveClient)) {
247
- return undefined;
248
- }
249
- const start = performance.now();
242
+ private async getBundleDataFromArweave(blockRangesForChains: number[][]) {
250
243
  const persistedData = await this.clients.arweaveClient.getByTopic(
251
244
  this.getArweaveBundleDataClientKey(blockRangesForChains),
252
245
  BundleDataSS
@@ -256,6 +249,20 @@ export class BundleDataClient {
256
249
  if (!isDefined(persistedData) || persistedData.length < 1) {
257
250
  return undefined;
258
251
  }
252
+ return persistedData;
253
+ }
254
+
255
+ private async loadPersistedDataFromArweave(
256
+ blockRangesForChains: number[][]
257
+ ): Promise<LoadDataReturnValue | undefined> {
258
+ if (!isDefined(this.clients?.arweaveClient)) {
259
+ return undefined;
260
+ }
261
+ const start = performance.now();
262
+ const persistedData = await this.getBundleDataFromArweave(blockRangesForChains);
263
+ if (!isDefined(persistedData)) {
264
+ return undefined;
265
+ }
259
266
 
260
267
  // A converter function to account for the fact that our SuperStruct schema does not support numeric
261
268
  // keys in records. Fundamentally, this is a limitation of superstruct itself.
@@ -392,7 +399,7 @@ export class BundleDataClient {
392
399
  const fill = await verifyFillRepayment(
393
400
  _fill,
394
401
  this.spokePoolClients[_fill.destinationChainId].spokePool.provider,
395
- matchingDeposit,
402
+ matchingDeposit!,
396
403
  this.clients.hubPoolClient
397
404
  );
398
405
  if (!isDefined(fill)) {
@@ -403,7 +410,7 @@ export class BundleDataClient {
403
410
  this.clients.hubPoolClient,
404
411
  blockRanges,
405
412
  this.chainIdListForBundleEvaluationBlockNumbers,
406
- matchingDeposit.fromLiteChain
413
+ matchingDeposit!.fromLiteChain // Use ! because we've already asserted that matchingDeposit is defined.
407
414
  );
408
415
  // Assume that lp fees are 0 for the sake of speed. In the future we could batch compute
409
416
  // these or make hardcoded assumptions based on the origin-repayment chain direction. This might result
@@ -431,52 +438,6 @@ export class BundleDataClient {
431
438
  }, toBN(0));
432
439
  }
433
440
 
434
- private async getLatestProposedBundleData(): Promise<{ bundleData: LoadDataReturnValue; blockRanges: number[][] }> {
435
- const hubPoolClient = this.clients.hubPoolClient;
436
- // Determine which bundle we should fetch from arweave, either the pending bundle or the latest
437
- // executed one. Both should have arweave data but if for some reason the arweave data is missing,
438
- // this function will have to compute the bundle data from scratch which will be slow. We have to fallback
439
- // to computing the bundle from scratch since this function needs to return the full bundle data so that
440
- // it can be used to get the running balance proposed using its data.
441
- const bundleBlockRanges = getImpliedBundleBlockRanges(
442
- hubPoolClient,
443
- this.clients.configStoreClient,
444
- hubPoolClient.hasPendingProposal()
445
- ? hubPoolClient.getLatestProposedRootBundle()
446
- : hubPoolClient.getLatestFullyExecutedRootBundle(hubPoolClient.latestBlockSearched)! // ! because we know there is a bundle
447
- );
448
- return {
449
- blockRanges: bundleBlockRanges,
450
- bundleData: await this.loadData(
451
- bundleBlockRanges,
452
- this.spokePoolClients,
453
- true // this bundle data should have been published to arweave
454
- ),
455
- };
456
- }
457
-
458
- async getLatestPoolRebalanceRoot(): Promise<{ root: PoolRebalanceRoot; blockRanges: number[][] }> {
459
- const { bundleData, blockRanges } = await this.getLatestProposedBundleData();
460
- const hubPoolClient = this.clients.hubPoolClient;
461
- const root = _buildPoolRebalanceRoot(
462
- hubPoolClient.latestBlockSearched,
463
- blockRanges[0][1],
464
- bundleData.bundleDepositsV3,
465
- bundleData.bundleFillsV3,
466
- bundleData.bundleSlowFillsV3,
467
- bundleData.unexecutableSlowFills,
468
- bundleData.expiredDepositsToRefundV3,
469
- {
470
- hubPoolClient,
471
- configStoreClient: hubPoolClient.configStoreClient as AcrossConfigStoreClient,
472
- }
473
- );
474
- return {
475
- root,
476
- blockRanges,
477
- };
478
- }
479
-
480
441
  // @dev This function should probably be moved to the InventoryClient since it bypasses loadData completely now.
481
442
  // Return refunds from the next valid bundle. This will contain any refunds that have been sent but are not included
482
443
  // in a valid bundle with all of its leaves executed. This contains refunds from:
@@ -850,23 +811,27 @@ export class BundleDataClient {
850
811
  slowFillRequest: undefined,
851
812
  };
852
813
  } else {
853
- const { deposits } = v3RelayHashes[relayDataHash];
854
- assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary.");
855
- deposits.push(deposit);
814
+ v3RelayHashes[relayDataHash].deposits!.push(deposit);
856
815
  }
857
816
 
858
817
  // Account for duplicate deposits by concatenating the relayDataHash with the count of the number of times
859
818
  // we have seen it so far.
860
- const { deposits } = v3RelayHashes[relayDataHash];
861
- assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary.");
862
- const newBundleDepositHash = `${relayDataHash}@${deposits.length - 1}`;
819
+ const newBundleDepositHash = `${relayDataHash}@${v3RelayHashes[relayDataHash].deposits!.length - 1}`;
863
820
  const decodedBundleDepositHash = decodeBundleDepositHash(newBundleDepositHash);
864
821
  assert(
865
822
  decodedBundleDepositHash.relayDataHash === relayDataHash &&
866
- decodedBundleDepositHash.index === deposits.length - 1,
823
+ decodedBundleDepositHash.index === v3RelayHashes[relayDataHash].deposits!.length - 1,
867
824
  "Not using correct bundle deposit hash key"
868
825
  );
869
826
  if (deposit.blockNumber >= originChainBlockRange[0]) {
827
+ if (bundleDepositsV3?.[originChainId]?.[deposit.inputToken]?.find((d) => duplicateEvent(deposit, d))) {
828
+ this.logger.debug({
829
+ at: "BundleDataClient#loadData",
830
+ message: "Duplicate deposit detected",
831
+ deposit,
832
+ });
833
+ throw new Error("Duplicate deposit detected");
834
+ }
870
835
  bundleDepositHashes.push(newBundleDepositHash);
871
836
  updateBundleDepositsV3(bundleDepositsV3, deposit);
872
837
  } else if (deposit.blockNumber < originChainBlockRange[0]) {
@@ -924,62 +889,69 @@ export class BundleDataClient {
924
889
  fillCounter++;
925
890
  const relayDataHash = getRelayEventKey(fill);
926
891
  if (v3RelayHashes[relayDataHash]) {
927
- if (!v3RelayHashes[relayDataHash].fill) {
928
- const { deposits } = v3RelayHashes[relayDataHash];
929
- assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary.");
930
- v3RelayHashes[relayDataHash].fill = fill;
931
- if (fill.blockNumber >= destinationChainBlockRange[0]) {
932
- const fillToRefund = await verifyFillRepayment(
933
- fill,
934
- destinationClient.spokePool.provider,
935
- deposits[0],
936
- this.clients.hubPoolClient
937
- );
938
- if (!isDefined(fillToRefund)) {
939
- bundleUnrepayableFillsV3.push(fill);
940
- // We don't return here yet because we still need to mark unexecutable slow fill leaves
941
- // or duplicate deposits. However, we won't issue a fast fill refund.
942
- } else {
943
- v3RelayHashes[relayDataHash].fill = fillToRefund;
944
- validatedBundleV3Fills.push({
945
- ...fillToRefund,
946
- quoteTimestamp: deposits[0].quoteTimestamp,
947
- });
948
-
949
- // Now that we know this deposit has been filled on-chain, identify any duplicate deposits
950
- // sent for this fill and refund them to the filler, because this value would not be paid out
951
- // otherwise. These deposits can no longer expire and get refunded as an expired deposit,
952
- // and they won't trigger a pre-fill refund because the fill is in this bundle.
953
- // Pre-fill refunds only happen when deposits are sent in this bundle and the
954
- // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how
955
- // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or
956
- // a current bundle fill.
957
- const duplicateDeposits = deposits.slice(1);
958
- duplicateDeposits.forEach((duplicateDeposit) => {
959
- if (isSlowFill(fill)) {
960
- updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit);
961
- } else {
962
- validatedBundleV3Fills.push({
963
- ...fillToRefund,
964
- quoteTimestamp: duplicateDeposit.quoteTimestamp,
965
- });
966
- }
967
- });
968
- }
969
-
970
- // If fill replaced a slow fill request, then mark it as one that might have created an
971
- // unexecutable slow fill. We can't know for sure until we check the slow fill request
972
- // events.
973
- if (
974
- fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill &&
975
- _canCreateSlowFillLeaf(deposits[0])
976
- ) {
977
- fastFillsReplacingSlowFills.push(relayDataHash);
978
- }
979
- }
980
- } else {
892
+ if (v3RelayHashes[relayDataHash].fill) {
893
+ this.logger.debug({
894
+ at: "BundleDataClient#loadData",
895
+ message: "Duplicate fill detected",
896
+ fill,
897
+ });
981
898
  throw new Error("Duplicate fill detected");
982
899
  }
900
+ assert(
901
+ isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0,
902
+ "Deposit should exist in relay hash dictionary."
903
+ );
904
+ v3RelayHashes[relayDataHash].fill = fill;
905
+ if (fill.blockNumber >= destinationChainBlockRange[0]) {
906
+ const fillToRefund = await verifyFillRepayment(
907
+ fill,
908
+ destinationClient.spokePool.provider,
909
+ v3RelayHashes[relayDataHash].deposits![0],
910
+ this.clients.hubPoolClient
911
+ );
912
+ if (!isDefined(fillToRefund)) {
913
+ bundleUnrepayableFillsV3.push(fill);
914
+ // We don't return here yet because we still need to mark unexecutable slow fill leaves
915
+ // or duplicate deposits. However, we won't issue a fast fill refund.
916
+ } else {
917
+ v3RelayHashes[relayDataHash].fill = fillToRefund;
918
+ validatedBundleV3Fills.push({
919
+ ...fillToRefund,
920
+ quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp,
921
+ });
922
+
923
+ // Now that we know this deposit has been filled on-chain, identify any duplicate deposits
924
+ // sent for this fill and refund them to the filler, because this value would not be paid out
925
+ // otherwise. These deposits can no longer expire and get refunded as an expired deposit,
926
+ // and they won't trigger a pre-fill refund because the fill is in this bundle.
927
+ // Pre-fill refunds only happen when deposits are sent in this bundle and the
928
+ // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how
929
+ // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or
930
+ // a current bundle fill.
931
+ const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1);
932
+ duplicateDeposits.forEach((duplicateDeposit) => {
933
+ if (isSlowFill(fill)) {
934
+ updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit);
935
+ } else {
936
+ validatedBundleV3Fills.push({
937
+ ...fillToRefund,
938
+ quoteTimestamp: duplicateDeposit.quoteTimestamp,
939
+ });
940
+ }
941
+ });
942
+ }
943
+
944
+ // If fill replaced a slow fill request, then mark it as one that might have created an
945
+ // unexecutable slow fill. We can't know for sure until we check the slow fill request
946
+ // events.
947
+ if (
948
+ fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill &&
949
+ _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0])
950
+ ) {
951
+ fastFillsReplacingSlowFills.push(relayDataHash);
952
+ }
953
+ }
954
+
983
955
  return;
984
956
  }
985
957
 
@@ -1079,28 +1051,34 @@ export class BundleDataClient {
1079
1051
  const relayDataHash = getRelayEventKey(slowFillRequest);
1080
1052
 
1081
1053
  if (v3RelayHashes[relayDataHash]) {
1082
- if (!v3RelayHashes[relayDataHash].slowFillRequest) {
1083
- v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest;
1084
- const { deposits, fill } = v3RelayHashes[relayDataHash];
1085
- if (fill) {
1086
- // Exiting here assumes that slow fill requests must precede fills, so if there was a fill
1087
- // following this slow fill request, then we would have already seen it. We don't need to check
1088
- // for a fill older than this slow fill request.
1089
- return;
1090
- }
1091
- assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary.");
1092
- const matchedDeposit = deposits[0];
1093
-
1094
- if (
1095
- slowFillRequest.blockNumber >= destinationChainBlockRange[0] &&
1096
- _canCreateSlowFillLeaf(matchedDeposit) &&
1097
- !_depositIsExpired(matchedDeposit)
1098
- ) {
1099
- validatedBundleSlowFills.push(matchedDeposit);
1100
- }
1101
- } else {
1054
+ if (v3RelayHashes[relayDataHash].slowFillRequest) {
1055
+ this.logger.debug({
1056
+ at: "BundleDataClient#loadData",
1057
+ message: "Duplicate slow fill request detected",
1058
+ slowFillRequest,
1059
+ });
1102
1060
  throw new Error("Duplicate slow fill request detected.");
1103
1061
  }
1062
+ v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest;
1063
+ if (v3RelayHashes[relayDataHash].fill) {
1064
+ // Exiting here assumes that slow fill requests must precede fills, so if there was a fill
1065
+ // following this slow fill request, then we would have already seen it. We don't need to check
1066
+ // for a fill older than this slow fill request.
1067
+ return;
1068
+ }
1069
+ assert(
1070
+ isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0,
1071
+ "Deposit should exist in relay hash dictionary."
1072
+ );
1073
+ const matchedDeposit = v3RelayHashes[relayDataHash].deposits![0];
1074
+
1075
+ if (
1076
+ slowFillRequest.blockNumber >= destinationChainBlockRange[0] &&
1077
+ _canCreateSlowFillLeaf(matchedDeposit) &&
1078
+ !_depositIsExpired(matchedDeposit)
1079
+ ) {
1080
+ validatedBundleSlowFills.push(matchedDeposit);
1081
+ }
1104
1082
  return;
1105
1083
  }
1106
1084
 
@@ -1195,7 +1173,7 @@ export class BundleDataClient {
1195
1173
  const fillToRefund = await verifyFillRepayment(
1196
1174
  fill,
1197
1175
  destinationClient.spokePool.provider,
1198
- deposits[0],
1176
+ v3RelayHashes[relayDataHash].deposits![0],
1199
1177
  this.clients.hubPoolClient
1200
1178
  );
1201
1179
  if (!isDefined(fillToRefund)) {
@@ -1243,18 +1221,18 @@ export class BundleDataClient {
1243
1221
  // then we wouldn't be in this branch of the code.
1244
1222
  const prefill = await this.findMatchingFillEvent(deposit, destinationClient);
1245
1223
  assert(isDefined(prefill), `findFillEvent# Cannot find prefill: ${relayDataHash}`);
1246
- assert(getRelayEventKey(prefill) === relayDataHash, "Relay hashes should match.");
1224
+ assert(getRelayEventKey(prefill!) === relayDataHash, "Relay hashes should match.");
1247
1225
  const verifiedFill = await verifyFillRepayment(
1248
- prefill,
1226
+ prefill!,
1249
1227
  destinationClient.spokePool.provider,
1250
1228
  deposit,
1251
1229
  this.clients.hubPoolClient
1252
1230
  );
1253
1231
  if (!isDefined(verifiedFill)) {
1254
- bundleUnrepayableFillsV3.push(prefill);
1232
+ bundleUnrepayableFillsV3.push(prefill!);
1255
1233
  } else if (!isSlowFill(verifiedFill)) {
1256
1234
  validatedBundleV3Fills.push({
1257
- ...verifiedFill,
1235
+ ...verifiedFill!,
1258
1236
  quoteTimestamp: deposit.quoteTimestamp,
1259
1237
  });
1260
1238
  } else {
@@ -1377,16 +1355,14 @@ export class BundleDataClient {
1377
1355
  validatedBundleV3Fills.length > 0
1378
1356
  ? this.clients.hubPoolClient.batchComputeRealizedLpFeePct(
1379
1357
  validatedBundleV3Fills.map((fill) => {
1380
- const { deposits } = v3RelayHashes[getRelayEventKey(fill)];
1381
- assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary.");
1382
- const matchedDeposit = deposits[0];
1358
+ const matchedDeposit = v3RelayHashes[getRelayEventKey(fill)].deposits![0];
1383
1359
  assert(isDefined(matchedDeposit), "Deposit should exist in relay hash dictionary.");
1384
1360
  const { chainToSendRefundTo: paymentChainId } = getRefundInformationFromFill(
1385
1361
  fill,
1386
1362
  this.clients.hubPoolClient,
1387
1363
  blockRangesForChains,
1388
1364
  chainIds,
1389
- matchedDeposit.fromLiteChain
1365
+ matchedDeposit!.fromLiteChain
1390
1366
  );
1391
1367
  return {
1392
1368
  ...fill,
@@ -1423,16 +1399,14 @@ export class BundleDataClient {
1423
1399
  });
1424
1400
  v3FillLpFees.forEach(({ realizedLpFeePct }, idx) => {
1425
1401
  const fill = validatedBundleV3Fills[idx];
1426
- const { deposits } = v3RelayHashes[getRelayEventKey(fill)];
1427
- assert(isDefined(deposits) && deposits.length > 0, "Deposit should exist in relay hash dictionary.");
1428
- const associatedDeposit = deposits[0];
1402
+ const associatedDeposit = v3RelayHashes[getRelayEventKey(fill)].deposits![0];
1429
1403
  assert(isDefined(associatedDeposit), "Deposit should exist in relay hash dictionary.");
1430
1404
  const { chainToSendRefundTo, repaymentToken } = getRefundInformationFromFill(
1431
1405
  fill,
1432
1406
  this.clients.hubPoolClient,
1433
1407
  blockRangesForChains,
1434
1408
  chainIds,
1435
- associatedDeposit.fromLiteChain
1409
+ associatedDeposit!.fromLiteChain
1436
1410
  );
1437
1411
  updateBundleFillsV3(bundleFillsV3, fill, realizedLpFeePct, chainToSendRefundTo, repaymentToken, fill.relayer);
1438
1412
  });
@@ -776,31 +776,35 @@ export class HubPoolClient extends BaseAbstractClient {
776
776
  return endBlock > 0 ? endBlock + 1 : 0;
777
777
  }
778
778
 
779
- getRunningBalanceBeforeBlockForChain(block: number, chain: number, l1Token: string): TokenRunningBalance {
779
+ getLatestExecutedRootBundleContainingL1Token(block: number, chain: number, l1Token: string): ExecutedRootBundle {
780
780
  // Search ExecutedRootBundles in descending block order to find the most recent event before the target block.
781
- const executedRootBundle = sortEventsDescending(this.executedRootBundles).find(
782
- (executedLeaf: ExecutedRootBundle) => {
783
- return (
784
- executedLeaf.blockNumber <= block &&
785
- executedLeaf.chainId === chain &&
786
- executedLeaf.l1Tokens.map((l1Token) => l1Token.toLowerCase()).includes(l1Token.toLowerCase())
787
- );
788
- }
789
- ) as ExecutedRootBundle;
781
+ return sortEventsDescending(this.executedRootBundles).find((executedLeaf: ExecutedRootBundle) => {
782
+ return (
783
+ executedLeaf.blockNumber <= block &&
784
+ executedLeaf.chainId === chain &&
785
+ executedLeaf.l1Tokens.map((l1Token) => l1Token.toLowerCase()).includes(l1Token.toLowerCase())
786
+ );
787
+ }) as ExecutedRootBundle;
788
+ }
789
+
790
+ getRunningBalanceBeforeBlockForChain(block: number, chain: number, l1Token: string): TokenRunningBalance {
791
+ const executedRootBundle = this.getLatestExecutedRootBundleContainingL1Token(block, chain, l1Token);
790
792
 
791
793
  return this.getRunningBalanceForToken(l1Token, executedRootBundle);
792
794
  }
793
795
 
794
796
  public getRunningBalanceForToken(l1Token: string, executedRootBundle: ExecutedRootBundle): TokenRunningBalance {
795
797
  let runningBalance = toBN(0);
798
+ let incentiveBalance = toBN(0);
796
799
  if (executedRootBundle) {
797
800
  const indexOfL1Token = executedRootBundle.l1Tokens
798
801
  .map((l1Token) => l1Token.toLowerCase())
799
802
  .indexOf(l1Token.toLowerCase());
800
803
  runningBalance = executedRootBundle.runningBalances[indexOfL1Token];
804
+ incentiveBalance = executedRootBundle.incentiveBalances[indexOfL1Token];
801
805
  }
802
806
 
803
- return { runningBalance };
807
+ return { runningBalance, incentiveBalance };
804
808
  }
805
809
 
806
810
  async _update(eventNames: HubPoolEvent[]): Promise<HubPoolUpdate> {
@@ -1005,6 +1009,8 @@ export class HubPoolClient extends BaseAbstractClient {
1005
1009
  );
1006
1010
  }
1007
1011
  executedRootBundle.runningBalances = runningBalances.slice(0, nTokens);
1012
+ executedRootBundle.incentiveBalances =
1013
+ runningBalances.length > nTokens ? runningBalances.slice(nTokens) : runningBalances.map(() => toBN(0));
1008
1014
  this.executedRootBundles.push(executedRootBundle);
1009
1015
  }
1010
1016
  }
@@ -1,11 +1,9 @@
1
- import assert from "assert";
2
1
  import { Contract, EventFilter } from "ethers";
3
2
  import winston from "winston";
4
3
  import {
5
4
  AnyObject,
6
5
  BigNumber,
7
6
  bnZero,
8
- bnUint32Max,
9
7
  DefaultLogLevels,
10
8
  EventSearchConfig,
11
9
  MAX_BIG_INT,
@@ -23,6 +21,7 @@ import {
23
21
  toAddress,
24
22
  } from "../utils";
25
23
  import {
24
+ duplicateEvent,
26
25
  paginatedEventQuery,
27
26
  sortEventsAscendingInPlace,
28
27
  spreadEvent,
@@ -55,6 +54,7 @@ import { getRepaymentChainId, forceDestinationRepayment } from "./BundleDataClie
55
54
  type SpokePoolUpdateSuccess = {
56
55
  success: true;
57
56
  currentTime: number;
57
+ oldestTime: number;
58
58
  firstDepositId: BigNumber;
59
59
  latestDepositId: BigNumber;
60
60
  events: Log[][];
@@ -72,6 +72,7 @@ export type SpokePoolUpdate = SpokePoolUpdateSuccess | SpokePoolUpdateFailure;
72
72
  */
73
73
  export class SpokePoolClient extends BaseAbstractClient {
74
74
  protected currentTime = 0;
75
+ protected oldestTime = 0;
75
76
  protected depositHashes: { [depositHash: string]: DepositWithBlock } = {};
76
77
  protected duplicateDepositHashes: { [depositHash: string]: DepositWithBlock[] } = {};
77
78
  protected depositHashesToFills: { [depositHash: string]: FillWithBlock[] } = {};
@@ -542,11 +543,12 @@ export class SpokePoolClient extends BaseAbstractClient {
542
543
 
543
544
  const timerStart = Date.now();
544
545
  const multicallFunctions = ["getCurrentTime", "numberOfDeposits"];
545
- const [multicallOutput, ...events] = await Promise.all([
546
+ const [multicallOutput, oldestTime, ...events] = await Promise.all([
546
547
  spokePool.callStatic.multicall(
547
548
  multicallFunctions.map((f) => spokePool.interface.encodeFunctionData(f)),
548
549
  { blockTag: searchConfig.toBlock }
549
550
  ),
551
+ this.spokePool.getCurrentTime({ blockTag: Math.max(searchConfig.fromBlock, this.deploymentBlock) }),
550
552
  ...eventSearchConfigs.map((config) => paginatedEventQuery(this.spokePool, config.filter, config.searchConfig)),
551
553
  ]);
552
554
  this.log("debug", `Time to query new events from RPC for ${this.chainId}: ${Date.now() - timerStart} ms`);
@@ -569,6 +571,7 @@ export class SpokePoolClient extends BaseAbstractClient {
569
571
  return {
570
572
  success: true,
571
573
  currentTime: currentTime.toNumber(), // uint32
574
+ oldestTime: oldestTime.toNumber(),
572
575
  firstDepositId,
573
576
  latestDepositId: _latestDepositId.gt(bnZero) ? _latestDepositId : bnZero,
574
577
  searchEndBlock: searchConfig.toBlock,
@@ -585,6 +588,7 @@ export class SpokePoolClient extends BaseAbstractClient {
585
588
  * @see _update
586
589
  */
587
590
  public async update(eventsToQuery = this.queryableEventNames): Promise<void> {
591
+ const duplicateEvents: Log[] = [];
588
592
  if (this.hubPoolClient !== null && !this.hubPoolClient.isUpdated) {
589
593
  throw new Error("HubPoolClient not updated");
590
594
  }
@@ -593,7 +597,7 @@ export class SpokePoolClient extends BaseAbstractClient {
593
597
  if (!update.success) {
594
598
  return;
595
599
  }
596
- const { events: queryResults, currentTime, searchEndBlock } = update;
600
+ const { events: queryResults, currentTime, oldestTime, searchEndBlock } = update;
597
601
 
598
602
  if (eventsToQuery.includes("TokensBridged")) {
599
603
  // Temporarily query old spoke pool events as well to ease migration:
@@ -654,6 +658,16 @@ export class SpokePoolClient extends BaseAbstractClient {
654
658
  }
655
659
 
656
660
  if (this.depositHashes[getRelayEventKey(deposit)] !== undefined) {
661
+ // Sanity check that this event is not a duplicate, even though the relay data hash is a duplicate.
662
+ const allDeposits = this._getDuplicateDeposits(deposit).concat(this.depositHashes[getRelayEventKey(deposit)]);
663
+ if (
664
+ allDeposits.some((e) => {
665
+ return duplicateEvent(deposit, e);
666
+ })
667
+ ) {
668
+ duplicateEvents.push(event);
669
+ continue;
670
+ }
657
671
  assign(this.duplicateDepositHashes, [getRelayEventKey(deposit)], [deposit]);
658
672
  continue;
659
673
  }
@@ -716,6 +730,13 @@ export class SpokePoolClient extends BaseAbstractClient {
716
730
  }
717
731
 
718
732
  const depositHash = getRelayEventKey({ ...slowFillRequest, destinationChainId: this.chainId });
733
+
734
+ // Sanity check that this event is not a duplicate.
735
+ if (this.slowFillRequests[depositHash] !== undefined) {
736
+ duplicateEvents.push(event);
737
+ continue;
738
+ }
739
+
719
740
  this.slowFillRequests[depositHash] ??= slowFillRequest;
720
741
  }
721
742
  };
@@ -749,6 +770,13 @@ export class SpokePoolClient extends BaseAbstractClient {
749
770
  fill.relayExecutionInfo.updatedMessageHash = getMessageHash(event.args.relayExecutionInfo.updatedMessage);
750
771
  }
751
772
 
773
+ // Sanity check that this event is not a duplicate.
774
+ const duplicateFill = this.fills[fill.originChainId]?.find((f) => duplicateEvent(fill, f));
775
+ if (duplicateFill) {
776
+ duplicateEvents.push(event);
777
+ continue;
778
+ }
779
+
752
780
  assign(this.fills, [fill.originChainId], [fill]);
753
781
  assign(this.depositHashesToFills, [this.getDepositHash(fill)], [fill]);
754
782
  }
@@ -793,8 +821,16 @@ export class SpokePoolClient extends BaseAbstractClient {
793
821
  }
794
822
  }
795
823
 
824
+ if (duplicateEvents.length > 0) {
825
+ this.log("debug", "Duplicate events listed", {
826
+ duplicateEvents,
827
+ });
828
+ this.log("error", "Duplicate events detected, check debug logs");
829
+ }
830
+
796
831
  // Next iteration should start off from where this one ended.
797
832
  this.currentTime = currentTime;
833
+ if (this.oldestTime === 0) this.oldestTime = oldestTime; // Set oldest time only after the first update.
798
834
  this.firstDepositIdForSpokePool = update.firstDepositId;
799
835
  this.latestBlockSearched = searchEndBlock;
800
836
  this.lastDepositIdForSpokePool = update.latestDepositId;
@@ -884,13 +920,11 @@ export class SpokePoolClient extends BaseAbstractClient {
884
920
  }
885
921
 
886
922
  /**
887
- * Retrieves the time from the SpokePool contract at a particular block.
888
- * @returns The time at the specified block tag.
923
+ * Retrieves the oldest time searched on the SpokePool contract.
924
+ * @returns The oldest time searched, which will be 0 if there has been no update() yet.
889
925
  */
890
- public async getTimeAt(blockNumber: number): Promise<number> {
891
- const currentTime = await this.spokePool.getCurrentTime({ blockTag: blockNumber });
892
- assert(BigNumber.isBigNumber(currentTime) && currentTime.lt(bnUint32Max));
893
- return currentTime.toNumber();
926
+ public getOldestTime(): number {
927
+ return this.oldestTime;
894
928
  }
895
929
 
896
930
  async findDeposit(depositId: BigNumber, destinationChainId: number): Promise<DepositWithBlock> {
@@ -54,6 +54,7 @@ export interface ExecutedRootBundle extends SortableEvent {
54
54
  bundleLpFees: BigNumber[];
55
55
  netSendAmounts: BigNumber[];
56
56
  runningBalances: BigNumber[];
57
+ incentiveBalances: BigNumber[];
57
58
  leafId: number;
58
59
  l1Tokens: string[];
59
60
  proof: string[];
@@ -61,15 +62,17 @@ export interface ExecutedRootBundle extends SortableEvent {
61
62
 
62
63
  export type ExecutedRootBundleStringified = Omit<
63
64
  ExecutedRootBundle,
64
- "bundleLpFees" | "netSendAmounts" | "runningBalances"
65
+ "bundleLpFees" | "netSendAmounts" | "runningBalances" | "incentiveBalances"
65
66
  > & {
66
67
  bundleLpFees: string[];
67
68
  netSendAmounts: string[];
68
69
  runningBalances: string[];
70
+ incentiveBalances: string[];
69
71
  };
70
72
 
71
73
  export type TokenRunningBalance = {
72
74
  runningBalance: BigNumber;
75
+ incentiveBalance: BigNumber;
73
76
  };
74
77
 
75
78
  export interface RelayerRefundLeafWithGroup extends RelayerRefundLeaf {
@@ -13,10 +13,10 @@ import {
13
13
  BigNumber,
14
14
  toBNWei,
15
15
  bnZero,
16
+ assert,
16
17
  chainIsOPStack,
17
18
  fixedPointAdjustment,
18
19
  } from "../../utils";
19
- import assert from "assert";
20
20
  import { Logger, QueryInterface } from "../relayFeeCalculator";
21
21
  import { Transport } from "viem";
22
22
  import { getGasPriceEstimate } from "../../gasPriceOracle/oracle";