@across-protocol/sdk 4.1.8 → 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 (59) hide show
  1. package/dist/cjs/clients/BundleDataClient/BundleDataClient.d.ts +2 -6
  2. package/dist/cjs/clients/BundleDataClient/BundleDataClient.js +72 -87
  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 +5 -2
  6. package/dist/cjs/clients/HubPoolClient.js.map +1 -1
  7. package/dist/cjs/clients/SpokePoolClient.js +50 -20
  8. package/dist/cjs/clients/SpokePoolClient.js.map +1 -1
  9. package/dist/cjs/constants.d.ts +0 -1
  10. package/dist/cjs/constants.js +1 -2
  11. package/dist/cjs/constants.js.map +1 -1
  12. package/dist/cjs/utils/BlockUtils.js +1 -1
  13. package/dist/cjs/utils/BlockUtils.js.map +1 -1
  14. package/dist/cjs/utils/EventUtils.d.ts +1 -0
  15. package/dist/cjs/utils/EventUtils.js +5 -1
  16. package/dist/cjs/utils/EventUtils.js.map +1 -1
  17. package/dist/cjs/utils/NetworkUtils.js +1 -1
  18. package/dist/cjs/utils/NetworkUtils.js.map +1 -1
  19. package/dist/esm/clients/BundleDataClient/BundleDataClient.d.ts +2 -6
  20. package/dist/esm/clients/BundleDataClient/BundleDataClient.js +80 -97
  21. package/dist/esm/clients/BundleDataClient/BundleDataClient.js.map +1 -1
  22. package/dist/esm/clients/HubPoolClient.d.ts +1 -0
  23. package/dist/esm/clients/HubPoolClient.js +5 -2
  24. package/dist/esm/clients/HubPoolClient.js.map +1 -1
  25. package/dist/esm/clients/SpokePoolClient.js +57 -23
  26. package/dist/esm/clients/SpokePoolClient.js.map +1 -1
  27. package/dist/esm/constants.d.ts +0 -1
  28. package/dist/esm/constants.js +0 -1
  29. package/dist/esm/constants.js.map +1 -1
  30. package/dist/esm/utils/BlockUtils.js +1 -1
  31. package/dist/esm/utils/BlockUtils.js.map +1 -1
  32. package/dist/esm/utils/EventUtils.d.ts +1 -0
  33. package/dist/esm/utils/EventUtils.js +3 -0
  34. package/dist/esm/utils/EventUtils.js.map +1 -1
  35. package/dist/esm/utils/NetworkUtils.js +1 -1
  36. package/dist/esm/utils/NetworkUtils.js.map +1 -1
  37. package/dist/esm/utils/abi/typechain/Multicall3.d.ts +1 -4
  38. package/dist/esm/utils/abi/typechain/factories/Multicall3__factory.js.map +1 -1
  39. package/dist/types/clients/BundleDataClient/BundleDataClient.d.ts +2 -6
  40. package/dist/types/clients/BundleDataClient/BundleDataClient.d.ts.map +1 -1
  41. package/dist/types/clients/HubPoolClient.d.ts +1 -0
  42. package/dist/types/clients/HubPoolClient.d.ts.map +1 -1
  43. package/dist/types/clients/SpokePoolClient.d.ts.map +1 -1
  44. package/dist/types/constants.d.ts +0 -1
  45. package/dist/types/constants.d.ts.map +1 -1
  46. package/dist/types/utils/EventUtils.d.ts +1 -0
  47. package/dist/types/utils/EventUtils.d.ts.map +1 -1
  48. package/dist/types/utils/abi/typechain/Multicall3.d.ts +1 -4
  49. package/dist/types/utils/abi/typechain/Multicall3.d.ts.map +1 -1
  50. package/dist/types/utils/abi/typechain/common.d.ts.map +1 -1
  51. package/dist/types/utils/abi/typechain/factories/Multicall3__factory.d.ts.map +1 -1
  52. package/package.json +2 -2
  53. package/src/clients/BundleDataClient/BundleDataClient.ts +129 -170
  54. package/src/clients/HubPoolClient.ts +12 -10
  55. package/src/clients/SpokePoolClient.ts +33 -0
  56. package/src/constants.ts +0 -2
  57. package/src/utils/BlockUtils.ts +1 -1
  58. package/src/utils/EventUtils.ts +4 -0
  59. package/src/utils/NetworkUtils.ts +1 -1
@@ -18,7 +18,7 @@ import {
18
18
  Deposit,
19
19
  DepositWithBlock,
20
20
  } from "../../interfaces";
21
- import { AcrossConfigStoreClient, SpokePoolClient } from "..";
21
+ import { SpokePoolClient } from "..";
22
22
  import {
23
23
  BigNumber,
24
24
  bnZero,
@@ -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,13 +52,12 @@ import {
52
52
  getRefundsFromBundle,
53
53
  getWidestPossibleExpectedBlockRange,
54
54
  isChainDisabled,
55
- PoolRebalanceRoot,
56
55
  prettyPrintV3SpokePoolEvents,
57
56
  V3DepositWithBlock,
58
57
  V3FillWithBlock,
59
58
  verifyFillRepayment,
60
59
  } from "./utils";
61
- import { PRE_FILL_MIN_CONFIG_STORE_VERSION, UNDEFINED_MESSAGE_HASH } from "../../constants";
60
+ import { UNDEFINED_MESSAGE_HASH } from "../../constants";
62
61
 
63
62
  // max(uint256) - 1
64
63
  export const INFINITE_FILL_DEADLINE = bnUint32Max;
@@ -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.
@@ -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:
@@ -827,22 +788,6 @@ export class BundleDataClient {
827
788
  return { relayDataHash, index: Number(i) };
828
789
  };
829
790
 
830
- // We use the following toggle to aid with the migration to pre-fills. The first bundle proposed using this
831
- // pre-fill logic can double refund pre-fills that have already been filled in the last bundle, because the
832
- // last bundle did not recognize a fill as a pre-fill. Therefore the developer should ensure that the version
833
- // is bumped to the PRE_FILL_MIN_CONFIG_STORE_VERSION version before the first pre-fill bundle is proposed.
834
- // To test the following bundle after this, the developer can set the FORCE_REFUND_PREFILLS environment variable
835
- // to "true" simulate the bundle with pre-fill refunds.
836
- // @todo Remove this logic once we have advanced sufficiently past the pre-fill migration.
837
- const startBlockForMainnet = getBlockRangeForChain(
838
- blockRangesForChains,
839
- this.clients.hubPoolClient.chainId,
840
- this.chainIdListForBundleEvaluationBlockNumbers
841
- )[0];
842
- const versionAtProposalBlock = this.clients.configStoreClient.getConfigStoreVersionForBlock(startBlockForMainnet);
843
- const canRefundPrefills =
844
- versionAtProposalBlock >= PRE_FILL_MIN_CONFIG_STORE_VERSION || process.env.FORCE_REFUND_PREFILLS === "true";
845
-
846
791
  // Prerequisite step: Load all deposit events from the current or older bundles into the v3RelayHashes dictionary
847
792
  // for convenient matching with fills.
848
793
  for (const originChainId of allChainIds) {
@@ -879,6 +824,14 @@ export class BundleDataClient {
879
824
  "Not using correct bundle deposit hash key"
880
825
  );
881
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
+ }
882
835
  bundleDepositHashes.push(newBundleDepositHash);
883
836
  updateBundleDepositsV3(bundleDepositsV3, deposit);
884
837
  } else if (deposit.blockNumber < originChainBlockRange[0]) {
@@ -936,64 +889,69 @@ export class BundleDataClient {
936
889
  fillCounter++;
937
890
  const relayDataHash = getRelayEventKey(fill);
938
891
  if (v3RelayHashes[relayDataHash]) {
939
- if (!v3RelayHashes[relayDataHash].fill) {
940
- assert(
941
- isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0,
942
- "Deposit should exist in relay hash dictionary."
892
+ if (v3RelayHashes[relayDataHash].fill) {
893
+ this.logger.debug({
894
+ at: "BundleDataClient#loadData",
895
+ message: "Duplicate fill detected",
896
+ fill,
897
+ });
898
+ throw new Error("Duplicate fill detected");
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
943
911
  );
944
- v3RelayHashes[relayDataHash].fill = fill;
945
- if (fill.blockNumber >= destinationChainBlockRange[0]) {
946
- const fillToRefund = await verifyFillRepayment(
947
- fill,
948
- destinationClient.spokePool.provider,
949
- v3RelayHashes[relayDataHash].deposits![0],
950
- this.clients.hubPoolClient
951
- );
952
- if (!isDefined(fillToRefund)) {
953
- bundleUnrepayableFillsV3.push(fill);
954
- // We don't return here yet because we still need to mark unexecutable slow fill leaves
955
- // or duplicate deposits. However, we won't issue a fast fill refund.
956
- } else {
957
- v3RelayHashes[relayDataHash].fill = fillToRefund;
958
- validatedBundleV3Fills.push({
959
- ...fillToRefund,
960
- quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp,
961
- });
962
-
963
- // Now that we know this deposit has been filled on-chain, identify any duplicate deposits
964
- // sent for this fill and refund them to the filler, because this value would not be paid out
965
- // otherwise. These deposits can no longer expire and get refunded as an expired deposit,
966
- // and they won't trigger a pre-fill refund because the fill is in this bundle.
967
- // Pre-fill refunds only happen when deposits are sent in this bundle and the
968
- // fill is from a prior bundle. Paying out the filler keeps the behavior consistent for how
969
- // we deal with duplicate deposits regardless if the deposit is matched with a pre-fill or
970
- // a current bundle fill.
971
- const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1);
972
- duplicateDeposits.forEach((duplicateDeposit) => {
973
- if (isSlowFill(fill)) {
974
- updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit);
975
- } else {
976
- validatedBundleV3Fills.push({
977
- ...fillToRefund,
978
- quoteTimestamp: duplicateDeposit.quoteTimestamp,
979
- });
980
- }
981
- });
982
- }
983
-
984
- // If fill replaced a slow fill request, then mark it as one that might have created an
985
- // unexecutable slow fill. We can't know for sure until we check the slow fill request
986
- // events.
987
- if (
988
- fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill &&
989
- _canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0])
990
- ) {
991
- fastFillsReplacingSlowFills.push(relayDataHash);
992
- }
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);
993
952
  }
994
- } else {
995
- throw new Error("Duplicate fill detected");
996
953
  }
954
+
997
955
  return;
998
956
  }
999
957
 
@@ -1093,30 +1051,34 @@ export class BundleDataClient {
1093
1051
  const relayDataHash = getRelayEventKey(slowFillRequest);
1094
1052
 
1095
1053
  if (v3RelayHashes[relayDataHash]) {
1096
- if (!v3RelayHashes[relayDataHash].slowFillRequest) {
1097
- v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest;
1098
- if (v3RelayHashes[relayDataHash].fill) {
1099
- // Exiting here assumes that slow fill requests must precede fills, so if there was a fill
1100
- // following this slow fill request, then we would have already seen it. We don't need to check
1101
- // for a fill older than this slow fill request.
1102
- return;
1103
- }
1104
- assert(
1105
- isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0,
1106
- "Deposit should exist in relay hash dictionary."
1107
- );
1108
- const matchedDeposit = v3RelayHashes[relayDataHash].deposits![0];
1109
-
1110
- if (
1111
- slowFillRequest.blockNumber >= destinationChainBlockRange[0] &&
1112
- _canCreateSlowFillLeaf(matchedDeposit) &&
1113
- !_depositIsExpired(matchedDeposit)
1114
- ) {
1115
- validatedBundleSlowFills.push(matchedDeposit);
1116
- }
1117
- } else {
1054
+ if (v3RelayHashes[relayDataHash].slowFillRequest) {
1055
+ this.logger.debug({
1056
+ at: "BundleDataClient#loadData",
1057
+ message: "Duplicate slow fill request detected",
1058
+ slowFillRequest,
1059
+ });
1118
1060
  throw new Error("Duplicate slow fill request detected.");
1119
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
+ }
1120
1082
  return;
1121
1083
  }
1122
1084
 
@@ -1207,7 +1169,7 @@ export class BundleDataClient {
1207
1169
  // If fill is in the current bundle then we can assume there is already a refund for it, so only
1208
1170
  // include this pre fill if the fill is in an older bundle.
1209
1171
  if (fill) {
1210
- if (canRefundPrefills && fill.blockNumber < destinationChainBlockRange[0]) {
1172
+ if (fill.blockNumber < destinationChainBlockRange[0]) {
1211
1173
  const fillToRefund = await verifyFillRepayment(
1212
1174
  fill,
1213
1175
  destinationClient.spokePool.provider,
@@ -1238,7 +1200,6 @@ export class BundleDataClient {
1238
1200
  if (_depositIsExpired(deposit)) {
1239
1201
  updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
1240
1202
  } else if (
1241
- canRefundPrefills &&
1242
1203
  slowFillRequest.blockNumber < destinationChainBlockRange[0] &&
1243
1204
  _canCreateSlowFillLeaf(deposit) &&
1244
1205
  validatedBundleSlowFills.every((d) => getRelayEventKey(d) !== relayDataHash)
@@ -1261,23 +1222,21 @@ export class BundleDataClient {
1261
1222
  const prefill = await this.findMatchingFillEvent(deposit, destinationClient);
1262
1223
  assert(isDefined(prefill), `findFillEvent# Cannot find prefill: ${relayDataHash}`);
1263
1224
  assert(getRelayEventKey(prefill!) === relayDataHash, "Relay hashes should match.");
1264
- if (canRefundPrefills) {
1265
- const verifiedFill = await verifyFillRepayment(
1266
- prefill!,
1267
- destinationClient.spokePool.provider,
1268
- deposit,
1269
- this.clients.hubPoolClient
1270
- );
1271
- if (!isDefined(verifiedFill)) {
1272
- bundleUnrepayableFillsV3.push(prefill!);
1273
- } else if (!isSlowFill(verifiedFill)) {
1274
- validatedBundleV3Fills.push({
1275
- ...verifiedFill!,
1276
- quoteTimestamp: deposit.quoteTimestamp,
1277
- });
1278
- } else {
1279
- updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
1280
- }
1225
+ const verifiedFill = await verifyFillRepayment(
1226
+ prefill!,
1227
+ destinationClient.spokePool.provider,
1228
+ deposit,
1229
+ this.clients.hubPoolClient
1230
+ );
1231
+ if (!isDefined(verifiedFill)) {
1232
+ bundleUnrepayableFillsV3.push(prefill!);
1233
+ } else if (!isSlowFill(verifiedFill)) {
1234
+ validatedBundleV3Fills.push({
1235
+ ...verifiedFill!,
1236
+ quoteTimestamp: deposit.quoteTimestamp,
1237
+ });
1238
+ } else {
1239
+ updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
1281
1240
  }
1282
1241
  } else if (_depositIsExpired(deposit)) {
1283
1242
  updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
@@ -1286,7 +1245,7 @@ export class BundleDataClient {
1286
1245
  // Don't create duplicate slow fill requests for the same deposit.
1287
1246
  validatedBundleSlowFills.every((d) => getRelayEventKey(d) !== relayDataHash)
1288
1247
  ) {
1289
- if (canRefundPrefills && _canCreateSlowFillLeaf(deposit)) {
1248
+ if (_canCreateSlowFillLeaf(deposit)) {
1290
1249
  validatedBundleSlowFills.push(deposit);
1291
1250
  }
1292
1251
  }
@@ -776,17 +776,19 @@ 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
  }
@@ -21,6 +21,7 @@ import {
21
21
  toAddress,
22
22
  } from "../utils";
23
23
  import {
24
+ duplicateEvent,
24
25
  paginatedEventQuery,
25
26
  sortEventsAscendingInPlace,
26
27
  spreadEvent,
@@ -587,6 +588,7 @@ export class SpokePoolClient extends BaseAbstractClient {
587
588
  * @see _update
588
589
  */
589
590
  public async update(eventsToQuery = this.queryableEventNames): Promise<void> {
591
+ const duplicateEvents: Log[] = [];
590
592
  if (this.hubPoolClient !== null && !this.hubPoolClient.isUpdated) {
591
593
  throw new Error("HubPoolClient not updated");
592
594
  }
@@ -656,6 +658,16 @@ export class SpokePoolClient extends BaseAbstractClient {
656
658
  }
657
659
 
658
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
+ }
659
671
  assign(this.duplicateDepositHashes, [getRelayEventKey(deposit)], [deposit]);
660
672
  continue;
661
673
  }
@@ -718,6 +730,13 @@ export class SpokePoolClient extends BaseAbstractClient {
718
730
  }
719
731
 
720
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
+
721
740
  this.slowFillRequests[depositHash] ??= slowFillRequest;
722
741
  }
723
742
  };
@@ -751,6 +770,13 @@ export class SpokePoolClient extends BaseAbstractClient {
751
770
  fill.relayExecutionInfo.updatedMessageHash = getMessageHash(event.args.relayExecutionInfo.updatedMessage);
752
771
  }
753
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
+
754
780
  assign(this.fills, [fill.originChainId], [fill]);
755
781
  assign(this.depositHashesToFills, [this.getDepositHash(fill)], [fill]);
756
782
  }
@@ -795,6 +821,13 @@ export class SpokePoolClient extends BaseAbstractClient {
795
821
  }
796
822
  }
797
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
+
798
831
  // Next iteration should start off from where this one ended.
799
832
  this.currentTime = currentTime;
800
833
  if (this.oldestTime === 0) this.oldestTime = oldestTime; // Set oldest time only after the first update.
package/src/constants.ts CHANGED
@@ -29,8 +29,6 @@ export const HUBPOOL_CHAIN_ID = 1;
29
29
  // List of versions where certain UMIP features were deprecated or activated
30
30
  export const TRANSFER_THRESHOLD_MAX_CONFIG_STORE_VERSION = 1;
31
31
 
32
- export const PRE_FILL_MIN_CONFIG_STORE_VERSION = 5;
33
-
34
32
  // A hardcoded identifier used, by default, to tag all Arweave records.
35
33
  export const ARWEAVE_TAG_APP_NAME = "across-protocol";
36
34
 
@@ -34,11 +34,11 @@ const defaultHighBlockOffset = 10;
34
34
  const cacheTTL = 60 * 15;
35
35
  const now = getCurrentTime(); // Seed the cache with initial values.
36
36
  const blockTimes: { [chainId: number]: BlockTimeAverage } = {
37
- [CHAIN_IDs.DOCTOR_WHO]: { average: 1, timestamp: now, blockRange: 1 },
38
37
  [CHAIN_IDs.INK]: { average: 1, timestamp: now, blockRange: 1 },
39
38
  [CHAIN_IDs.LINEA]: { average: 3, timestamp: now, blockRange: 1 },
40
39
  [CHAIN_IDs.MAINNET]: { average: 12.5, timestamp: now, blockRange: 1 },
41
40
  [CHAIN_IDs.OPTIMISM]: { average: 2, timestamp: now, blockRange: 1 },
41
+ [CHAIN_IDs.UNICHAIN]: { average: 1, timestamp: now, blockRange: 1 },
42
42
  };
43
43
 
44
44
  /**
@@ -266,3 +266,7 @@ export function isEventOlder<T extends SortableEvent>(ex: T, ey: T): boolean {
266
266
  export function getTransactionHashes(events: SortableEvent[]): string[] {
267
267
  return [...Array.from(new Set(events.map((e) => e.transactionHash)))];
268
268
  }
269
+
270
+ export function duplicateEvent(a: SortableEvent, b: SortableEvent): boolean {
271
+ return a.transactionHash === b.transactionHash && a.logIndex === b.logIndex;
272
+ }
@@ -144,9 +144,9 @@ export function chainIsCCTPEnabled(chainId: number): boolean {
144
144
  // Mainnets
145
145
  CHAIN_IDs.ARBITRUM,
146
146
  CHAIN_IDs.BASE,
147
- CHAIN_IDs.DOCTOR_WHO,
148
147
  CHAIN_IDs.OPTIMISM,
149
148
  CHAIN_IDs.POLYGON,
149
+ CHAIN_IDs.UNICHAIN,
150
150
  // Testnets
151
151
  CHAIN_IDs.BASE_SEPOLIA,
152
152
  CHAIN_IDs.OPTIMISM_SEPOLIA,