@across-protocol/sdk 4.1.46-beta.0 → 4.1.46-beta.2

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 (128) hide show
  1. package/dist/cjs/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.d.ts +3 -3
  2. package/dist/cjs/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.js +15 -11
  3. package/dist/cjs/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.js.map +1 -1
  4. package/dist/cjs/clients/BundleDataClient/BundleDataClient.d.ts +0 -2
  5. package/dist/cjs/clients/BundleDataClient/BundleDataClient.js +75 -47
  6. package/dist/cjs/clients/BundleDataClient/BundleDataClient.js.map +1 -1
  7. package/dist/cjs/clients/BundleDataClient/utils/DataworkerUtils.js +10 -1
  8. package/dist/cjs/clients/BundleDataClient/utils/DataworkerUtils.js.map +1 -1
  9. package/dist/cjs/clients/BundleDataClient/utils/FillUtils.d.ts +7 -7
  10. package/dist/cjs/clients/BundleDataClient/utils/FillUtils.js +48 -37
  11. package/dist/cjs/clients/BundleDataClient/utils/FillUtils.js.map +1 -1
  12. package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts +1 -1
  13. package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.js +2 -2
  14. package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.js.map +1 -1
  15. package/dist/cjs/clients/BundleDataClient/utils/SuperstructUtils.d.ts +48 -20
  16. package/dist/cjs/clients/BundleDataClient/utils/SuperstructUtils.js +8 -6
  17. package/dist/cjs/clients/BundleDataClient/utils/SuperstructUtils.js.map +1 -1
  18. package/dist/cjs/clients/HubPoolClient.d.ts +3 -2
  19. package/dist/cjs/clients/HubPoolClient.js +65 -32
  20. package/dist/cjs/clients/HubPoolClient.js.map +1 -1
  21. package/dist/cjs/clients/SpokePoolClient/EVMSpokePoolClient.js +1 -1
  22. package/dist/cjs/clients/SpokePoolClient/EVMSpokePoolClient.js.map +1 -1
  23. package/dist/cjs/clients/SpokePoolClient/SpokePoolClient.d.ts +1 -0
  24. package/dist/cjs/clients/SpokePoolClient/SpokePoolClient.js +20 -6
  25. package/dist/cjs/clients/SpokePoolClient/SpokePoolClient.js.map +1 -1
  26. package/dist/cjs/clients/mocks/MockConfigStoreClient.js +2 -2
  27. package/dist/cjs/clients/mocks/MockConfigStoreClient.js.map +1 -1
  28. package/dist/cjs/clients/mocks/MockHubPoolClient.d.ts +3 -3
  29. package/dist/cjs/clients/mocks/MockHubPoolClient.js +35 -7
  30. package/dist/cjs/clients/mocks/MockHubPoolClient.js.map +1 -1
  31. package/dist/cjs/clients/mocks/MockSpokePoolClient.js +5 -5
  32. package/dist/cjs/clients/mocks/MockSpokePoolClient.js.map +1 -1
  33. package/dist/cjs/interfaces/Common.d.ts +2 -2
  34. package/dist/cjs/utils/AddressUtils.d.ts +1 -0
  35. package/dist/cjs/utils/AddressUtils.js +3 -0
  36. package/dist/cjs/utils/AddressUtils.js.map +1 -1
  37. package/dist/cjs/utils/DepositUtils.d.ts +1 -0
  38. package/dist/cjs/utils/DepositUtils.js +5 -1
  39. package/dist/cjs/utils/DepositUtils.js.map +1 -1
  40. package/dist/cjs/utils/EventUtils.d.ts +2 -1
  41. package/dist/cjs/utils/EventUtils.js +21 -12
  42. package/dist/cjs/utils/EventUtils.js.map +1 -1
  43. package/dist/esm/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.d.ts +3 -3
  44. package/dist/esm/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.js +15 -11
  45. package/dist/esm/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.js.map +1 -1
  46. package/dist/esm/clients/BundleDataClient/BundleDataClient.d.ts +0 -2
  47. package/dist/esm/clients/BundleDataClient/BundleDataClient.js +82 -52
  48. package/dist/esm/clients/BundleDataClient/BundleDataClient.js.map +1 -1
  49. package/dist/esm/clients/BundleDataClient/utils/DataworkerUtils.js +16 -1
  50. package/dist/esm/clients/BundleDataClient/utils/DataworkerUtils.js.map +1 -1
  51. package/dist/esm/clients/BundleDataClient/utils/FillUtils.d.ts +28 -7
  52. package/dist/esm/clients/BundleDataClient/utils/FillUtils.js +84 -51
  53. package/dist/esm/clients/BundleDataClient/utils/FillUtils.js.map +1 -1
  54. package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts +1 -1
  55. package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.js +2 -2
  56. package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.js.map +1 -1
  57. package/dist/esm/clients/BundleDataClient/utils/SuperstructUtils.d.ts +48 -20
  58. package/dist/esm/clients/BundleDataClient/utils/SuperstructUtils.js +5 -3
  59. package/dist/esm/clients/BundleDataClient/utils/SuperstructUtils.js.map +1 -1
  60. package/dist/esm/clients/HubPoolClient.d.ts +3 -16
  61. package/dist/esm/clients/HubPoolClient.js +77 -52
  62. package/dist/esm/clients/HubPoolClient.js.map +1 -1
  63. package/dist/esm/clients/SpokePoolClient/EVMSpokePoolClient.js +2 -2
  64. package/dist/esm/clients/SpokePoolClient/EVMSpokePoolClient.js.map +1 -1
  65. package/dist/esm/clients/SpokePoolClient/SpokePoolClient.d.ts +1 -0
  66. package/dist/esm/clients/SpokePoolClient/SpokePoolClient.js +29 -15
  67. package/dist/esm/clients/SpokePoolClient/SpokePoolClient.js.map +1 -1
  68. package/dist/esm/clients/mocks/MockConfigStoreClient.js +3 -3
  69. package/dist/esm/clients/mocks/MockConfigStoreClient.js.map +1 -1
  70. package/dist/esm/clients/mocks/MockHubPoolClient.d.ts +3 -3
  71. package/dist/esm/clients/mocks/MockHubPoolClient.js +35 -7
  72. package/dist/esm/clients/mocks/MockHubPoolClient.js.map +1 -1
  73. package/dist/esm/clients/mocks/MockSpokePoolClient.js +5 -5
  74. package/dist/esm/clients/mocks/MockSpokePoolClient.js.map +1 -1
  75. package/dist/esm/interfaces/Common.d.ts +2 -2
  76. package/dist/esm/utils/AddressUtils.d.ts +1 -0
  77. package/dist/esm/utils/AddressUtils.js +3 -0
  78. package/dist/esm/utils/AddressUtils.js.map +1 -1
  79. package/dist/esm/utils/DepositUtils.d.ts +1 -0
  80. package/dist/esm/utils/DepositUtils.js +5 -1
  81. package/dist/esm/utils/DepositUtils.js.map +1 -1
  82. package/dist/esm/utils/EventUtils.d.ts +2 -1
  83. package/dist/esm/utils/EventUtils.js +18 -10
  84. package/dist/esm/utils/EventUtils.js.map +1 -1
  85. package/dist/types/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.d.ts +3 -3
  86. package/dist/types/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.d.ts.map +1 -1
  87. package/dist/types/clients/BundleDataClient/BundleDataClient.d.ts +0 -2
  88. package/dist/types/clients/BundleDataClient/BundleDataClient.d.ts.map +1 -1
  89. package/dist/types/clients/BundleDataClient/utils/DataworkerUtils.d.ts.map +1 -1
  90. package/dist/types/clients/BundleDataClient/utils/FillUtils.d.ts +28 -7
  91. package/dist/types/clients/BundleDataClient/utils/FillUtils.d.ts.map +1 -1
  92. package/dist/types/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts +1 -1
  93. package/dist/types/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts.map +1 -1
  94. package/dist/types/clients/BundleDataClient/utils/SuperstructUtils.d.ts +48 -20
  95. package/dist/types/clients/BundleDataClient/utils/SuperstructUtils.d.ts.map +1 -1
  96. package/dist/types/clients/HubPoolClient.d.ts +3 -16
  97. package/dist/types/clients/HubPoolClient.d.ts.map +1 -1
  98. package/dist/types/clients/SpokePoolClient/EVMSpokePoolClient.d.ts.map +1 -1
  99. package/dist/types/clients/SpokePoolClient/SpokePoolClient.d.ts +1 -0
  100. package/dist/types/clients/SpokePoolClient/SpokePoolClient.d.ts.map +1 -1
  101. package/dist/types/clients/mocks/MockConfigStoreClient.d.ts.map +1 -1
  102. package/dist/types/clients/mocks/MockHubPoolClient.d.ts +3 -3
  103. package/dist/types/clients/mocks/MockHubPoolClient.d.ts.map +1 -1
  104. package/dist/types/interfaces/Common.d.ts +2 -2
  105. package/dist/types/interfaces/Common.d.ts.map +1 -1
  106. package/dist/types/utils/AddressUtils.d.ts +1 -0
  107. package/dist/types/utils/AddressUtils.d.ts.map +1 -1
  108. package/dist/types/utils/DepositUtils.d.ts +1 -0
  109. package/dist/types/utils/DepositUtils.d.ts.map +1 -1
  110. package/dist/types/utils/EventUtils.d.ts +2 -1
  111. package/dist/types/utils/EventUtils.d.ts.map +1 -1
  112. package/package.json +1 -1
  113. package/src/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.ts +17 -15
  114. package/src/clients/BundleDataClient/BundleDataClient.ts +144 -62
  115. package/src/clients/BundleDataClient/utils/DataworkerUtils.ts +36 -1
  116. package/src/clients/BundleDataClient/utils/FillUtils.ts +132 -72
  117. package/src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts +3 -2
  118. package/src/clients/BundleDataClient/utils/SuperstructUtils.ts +7 -3
  119. package/src/clients/HubPoolClient.ts +97 -62
  120. package/src/clients/SpokePoolClient/EVMSpokePoolClient.ts +2 -1
  121. package/src/clients/SpokePoolClient/SpokePoolClient.ts +55 -17
  122. package/src/clients/mocks/MockConfigStoreClient.ts +10 -3
  123. package/src/clients/mocks/MockHubPoolClient.ts +41 -10
  124. package/src/clients/mocks/MockSpokePoolClient.ts +5 -5
  125. package/src/interfaces/Common.ts +2 -2
  126. package/src/utils/AddressUtils.ts +4 -0
  127. package/src/utils/DepositUtils.ts +6 -1
  128. package/src/utils/EventUtils.ts +19 -13
@@ -146,6 +146,13 @@ export function _buildPoolRebalanceRoot(
146
146
  const repaymentChainId = Number(_repaymentChainId);
147
147
  Object.entries(fillsForChain).forEach(
148
148
  ([l2TokenAddress, { realizedLpFees: totalRealizedLpFee, totalRefundAmount }]) => {
149
+ // If the repayment token and repayment chain ID do not map to a PoolRebalanceRoute graph, then
150
+ // there are no relevant L1 running balances.
151
+ if (
152
+ !clients.hubPoolClient.l2TokenHasPoolRebalanceRoute(l2TokenAddress, repaymentChainId, mainnetBundleEndBlock)
153
+ ) {
154
+ return;
155
+ }
149
156
  const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock(
150
157
  l2TokenAddress,
151
158
  repaymentChainId,
@@ -215,7 +222,24 @@ export function _buildPoolRebalanceRoot(
215
222
  Object.entries(bundleV3Deposits).forEach(([, depositsForChain]) => {
216
223
  Object.entries(depositsForChain).forEach(([, deposits]) => {
217
224
  deposits.forEach((deposit) => {
218
- updateRunningBalanceForDeposit(runningBalances, clients.hubPoolClient, deposit, deposit.inputAmount.mul(-1));
225
+ // If the repayment token and repayment chain ID do not map to a PoolRebalanceRoute graph, then
226
+ // there are no relevant L1 running balances.
227
+ if (
228
+ !clients.hubPoolClient.l2TokenHasPoolRebalanceRoute(
229
+ deposit.inputToken,
230
+ deposit.originChainId,
231
+ mainnetBundleEndBlock
232
+ )
233
+ ) {
234
+ return;
235
+ }
236
+ updateRunningBalanceForDeposit(
237
+ runningBalances,
238
+ clients.hubPoolClient,
239
+ deposit,
240
+ deposit.inputAmount.mul(-1),
241
+ mainnetBundleEndBlock
242
+ );
219
243
  });
220
244
  });
221
245
  });
@@ -229,6 +253,17 @@ export function _buildPoolRebalanceRoot(
229
253
  const originChainId = Number(_originChainId);
230
254
  Object.entries(depositsForChain).forEach(([inputToken, deposits]) => {
231
255
  deposits.forEach((deposit) => {
256
+ // If the repayment token and repayment chain ID do not map to a PoolRebalanceRoute graph, then
257
+ // there are no relevant L1 running balances.
258
+ if (
259
+ !clients.hubPoolClient.l2TokenHasPoolRebalanceRoute(
260
+ deposit.inputToken,
261
+ deposit.originChainId,
262
+ mainnetBundleEndBlock
263
+ )
264
+ ) {
265
+ return;
266
+ }
232
267
  const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock(
233
268
  inputToken,
234
269
  originChainId,
@@ -1,124 +1,118 @@
1
1
  import _ from "lodash";
2
+ import assert from "assert";
2
3
  import { providers } from "ethers";
3
- import { Deposit, DepositWithBlock, Fill, FillWithBlock } from "../../../interfaces";
4
- import { getBlockRangeForChain, isSlowFill, isValidEvmAddress, isDefined, chainIsEvm } from "../../../utils";
4
+ import { DepositWithBlock, Fill, FillWithBlock } from "../../../interfaces";
5
+ import { isSlowFill, isValidEvmAddress, isDefined, chainIsEvm } from "../../../utils";
5
6
  import { HubPoolClient } from "../../HubPoolClient";
6
7
 
8
+ /**
9
+ * @notice FillRepaymentInformation is a fill with additional properties required to determine where it can
10
+ * be repaid.
11
+ */
12
+ type FillRepaymentInformation = Fill & { fromLiteChain: boolean };
13
+
14
+ /**
15
+ * @notice Return repayment chain and repayment token for a fill, but does not verify if the returned
16
+ * repayment information is valid for the desired repayment address.
17
+ * @dev The passed in fill should be verified first via verifyFillRepayment().
18
+ * @param fill The fill to get the repayment information for.
19
+ * @return The chain to send the refund to and the token to use for the refund.
20
+ */
7
21
  export function getRefundInformationFromFill(
8
- fill: Fill,
22
+ relayData: FillRepaymentInformation,
9
23
  hubPoolClient: HubPoolClient,
10
- blockRangesForChains: number[][],
11
- chainIdListForBundleEvaluationBlockNumbers: number[],
12
- fromLiteChain: boolean
24
+ bundleEndBlockForMainnet: number
13
25
  ): {
14
26
  chainToSendRefundTo: number;
15
27
  repaymentToken: string;
16
28
  } {
17
- // Handle slow relay where repaymentChainId = 0. Slow relays always pay recipient on destination chain.
18
- // So, save the slow fill under the destination chain, and save the fast fill under its repayment chain.
19
- let chainToSendRefundTo = isSlowFill(fill) ? fill.destinationChainId : fill.repaymentChainId;
20
- // If the fill is for a deposit originating from the lite chain, the repayment chain is the origin chain
21
- // regardless of whether it is a slow or fast fill (we ignore slow fills but this is for posterity).
22
- // @note fill.repaymentChainId should already be set to originChainId but reset it to be safe.
23
- if (fromLiteChain) {
24
- chainToSendRefundTo = fill.originChainId;
29
+ const chainToSendRefundTo = _getRepaymentChainId(relayData, hubPoolClient, bundleEndBlockForMainnet);
30
+ if (chainToSendRefundTo === relayData.originChainId) {
31
+ return {
32
+ chainToSendRefundTo,
33
+ repaymentToken: relayData.inputToken,
34
+ };
25
35
  }
26
36
 
27
- // Save fill data and associate with repayment chain and L2 token refund should be denominated in.
28
- const endBlockForMainnet = getBlockRangeForChain(
29
- blockRangesForChains,
30
- hubPoolClient.chainId,
31
- chainIdListForBundleEvaluationBlockNumbers
32
- )[1];
33
-
37
+ // Now figure out the equivalent L2 token for the repayment token. If the inputToken doesn't have a
38
+ // PoolRebalanceRoute, then the repayment chain would have been the originChainId after the getRepaymentChainId()
39
+ // call and we would have returned already, so the following call should always succeed.
34
40
  const l1TokenCounterpart = hubPoolClient.getL1TokenForL2TokenAtBlock(
35
- fill.inputToken,
36
- fill.originChainId,
37
- endBlockForMainnet
41
+ relayData.inputToken,
42
+ relayData.originChainId,
43
+ bundleEndBlockForMainnet
38
44
  );
39
45
 
40
46
  const repaymentToken = hubPoolClient.getL2TokenForL1TokenAtBlock(
41
47
  l1TokenCounterpart,
42
48
  chainToSendRefundTo,
43
- endBlockForMainnet
49
+ bundleEndBlockForMainnet
44
50
  );
45
51
  return {
46
52
  chainToSendRefundTo,
47
53
  repaymentToken,
48
54
  };
49
55
  }
50
-
51
- export function getRepaymentChainId(fill: Fill, matchedDeposit: Deposit): number {
52
- // Lite chain deposits force repayment on origin chain.
53
- return matchedDeposit.fromLiteChain ? matchedDeposit.originChainId : fill.repaymentChainId;
54
- }
55
-
56
- export function forceDestinationRepayment(
57
- repaymentChainId: number,
58
- matchedDeposit: Deposit & { quoteBlockNumber: number },
59
- hubPoolClient: HubPoolClient
60
- ): boolean {
61
- if (!matchedDeposit.fromLiteChain) {
62
- try {
63
- const l1TokenCounterpart = hubPoolClient.getL1TokenForL2TokenAtBlock(
64
- matchedDeposit.inputToken,
65
- matchedDeposit.originChainId,
66
- matchedDeposit.quoteBlockNumber
67
- );
68
- hubPoolClient.getL2TokenForL1TokenAtBlock(l1TokenCounterpart, repaymentChainId, matchedDeposit.quoteBlockNumber);
69
- // Repayment token could be found, this is a valid repayment chain.
70
- return false;
71
- } catch {
72
- // Repayment token doesn't exist on repayment chain via PoolRebalanceRoutes, impossible to repay filler there.
73
- return true;
74
- }
75
- } else {
76
- return false;
77
- }
78
- }
79
-
80
- // Verify that a fill sent to an EVM chain has a 20 byte address. If the fill does not, then attempt
81
- // to repay the `msg.sender` of the relay transaction. Otherwise, return undefined.
56
+ /**
57
+ * @notice Verifies that the fill can be repaid. If the repayment address is not
58
+ * valid for the requested repayment chain, then this function will attempt to change the fill's repayment chain
59
+ * to the destination chain and its repayment address to the msg.sender and if this is possible,
60
+ * return the fill. Otherwise, return undefined.
61
+ * @param _fill Fill with a requested repayment chain and address
62
+ * @return Fill with the applied repayment chain (depends on the validity of the requested repayment address)
63
+ * and applied repayment address, or undefined if the applied repayment address is not valid for the
64
+ * applied repayment chain.
65
+ */
82
66
  export async function verifyFillRepayment(
83
67
  _fill: FillWithBlock,
84
68
  destinationChainProvider: providers.Provider,
85
69
  matchedDeposit: DepositWithBlock,
86
- hubPoolClient: HubPoolClient
70
+ hubPoolClient: HubPoolClient,
71
+ bundleEndBlockForMainnet: number
87
72
  ): Promise<FillWithBlock | undefined> {
88
- const fill = _.cloneDeep(_fill);
73
+ const fill = {
74
+ ..._.cloneDeep(_fill),
75
+ fromLiteChain: matchedDeposit.fromLiteChain,
76
+ };
89
77
 
90
78
  // Slow fills don't result in repayments so they're always valid.
91
79
  if (isSlowFill(fill)) {
92
80
  return fill;
93
81
  }
94
82
 
95
- let repaymentChainId = getRepaymentChainId(fill, matchedDeposit);
96
-
97
- // If repayment chain doesn't have a Pool Rebalance Route for the input token, then change the repayment
98
- // chain to the destination chain.
99
- if (forceDestinationRepayment(repaymentChainId, matchedDeposit, hubPoolClient)) {
100
- repaymentChainId = fill.destinationChainId;
101
- }
83
+ let repaymentChainId = _getRepaymentChainId(fill, hubPoolClient, bundleEndBlockForMainnet);
102
84
 
103
- if (!isValidEvmAddress(fill.relayer)) {
85
+ // Repayments will always go to the fill.relayer address so check if its a valid EVM address. If its not, attempt
86
+ // to change it to the msg.sender of the FilledRelay.
87
+ if (_repaymentAddressNeedsToBeOverwritten(fill)) {
104
88
  // TODO: Handle case where fill was sent on non-EVM chain, in which case the following call would fail
105
89
  // or return something unexpected. We'd want to return undefined here.
106
- const fillTransaction = await destinationChainProvider.getTransaction(fill.transactionHash);
90
+ const fillTransaction = await destinationChainProvider.getTransaction(fill.txnRef);
107
91
  const destinationRelayer = fillTransaction?.from;
108
92
  // Repayment chain is still an EVM chain, but the msg.sender is a bytes32 address, so the fill is invalid.
109
93
  if (!isDefined(destinationRelayer) || !isValidEvmAddress(destinationRelayer)) {
110
94
  return undefined;
111
95
  }
112
- if (!matchedDeposit.fromLiteChain) {
96
+ // If we can switch the repayment chain to the destination chain, then do so. We should only switch if the
97
+ // destination chain has a valid repayment token that is equivalent to the deposited input token. This would
98
+ // also be the same mapping as the repayment token on the repayment chain.
99
+ if (
100
+ !matchedDeposit.fromLiteChain &&
101
+ hubPoolClient.areTokensEquivalent(fill.inputToken, fill.originChainId, fill.outputToken, fill.destinationChainId)
102
+ ) {
113
103
  repaymentChainId = fill.destinationChainId;
114
- } else {
115
- // We can't switch repayment chain for a lite chain deposit so just check whether the repayment chain,
116
- // which should be the origin chain, is an EVM chain.
104
+ }
105
+ // If we can't switch the chain, then we need to verify that the msg.sender is a valid address on the repayment chain.
106
+ // Because we already checked that the `destinationRelayer` was a valid EVM address above, we only need to check
107
+ // that the repayment chain is an EVM chain.
108
+ else {
117
109
  if (!chainIsEvm(repaymentChainId)) {
118
110
  return undefined;
119
111
  }
120
112
  }
121
113
  fill.relayer = destinationRelayer;
114
+
115
+ // @todo: If chainIsSvm:
122
116
  }
123
117
 
124
118
  // Repayment address is now valid and repayment chain is either origin chain for lite chain or the destination
@@ -126,3 +120,69 @@ export async function verifyFillRepayment(
126
120
  fill.repaymentChainId = repaymentChainId;
127
121
  return fill;
128
122
  }
123
+
124
+ function _getRepaymentChainId(
125
+ relayData: FillRepaymentInformation,
126
+ hubPoolClient: HubPoolClient,
127
+ bundleEndBlockForMainnet: number
128
+ ): number {
129
+ if (relayData.fromLiteChain) {
130
+ assert(!isSlowFill(relayData), "getRepaymentChainId: fromLiteChain and slow fill are mutually exclusive");
131
+ return relayData.originChainId;
132
+ }
133
+
134
+ // Handle slow relay where FilledRelay.repaymentChainId = 0, as hardcoded in the SpokePool contract.
135
+ // Slow relays always pay recipient on destination chain.
136
+ if (isSlowFill(relayData)) {
137
+ return relayData.destinationChainId;
138
+ }
139
+
140
+ // Repayment chain is valid if the input token and repayment chain are mapped to the same PoolRebalanceRoute.
141
+ const repaymentTokenIsValid = _repaymentChainTokenIsValid(relayData, hubPoolClient, bundleEndBlockForMainnet);
142
+ if (repaymentTokenIsValid) {
143
+ return relayData.repaymentChainId;
144
+ }
145
+
146
+ // If repayment chain is not valid, default to origin chain since we always know the input token can be refunded.
147
+ return relayData.originChainId;
148
+ }
149
+
150
+ function _repaymentChainTokenIsValid(
151
+ relayData: FillRepaymentInformation,
152
+ hubPoolClient: HubPoolClient,
153
+ bundleEndBlockForMainnet: number
154
+ ): boolean {
155
+ if (
156
+ !hubPoolClient.l2TokenHasPoolRebalanceRoute(relayData.inputToken, relayData.originChainId, bundleEndBlockForMainnet)
157
+ ) {
158
+ return false;
159
+ }
160
+ const l1TokenCounterpart = hubPoolClient.getL1TokenForL2TokenAtBlock(
161
+ relayData.inputToken,
162
+ relayData.originChainId,
163
+ bundleEndBlockForMainnet
164
+ );
165
+ if (
166
+ !hubPoolClient.l2TokenEnabledForL1TokenAtBlock(
167
+ l1TokenCounterpart,
168
+ relayData.repaymentChainId,
169
+ bundleEndBlockForMainnet
170
+ )
171
+ ) {
172
+ return false;
173
+ }
174
+ return true;
175
+ }
176
+
177
+ function _repaymentAddressNeedsToBeOverwritten(fill: Fill): boolean {
178
+ // Slow fills don't result in repayments so they're always valid.
179
+ if (isSlowFill(fill)) {
180
+ return false;
181
+ }
182
+
183
+ // @todo add Solana logic here:
184
+ // - i.e. If chainIsSvm && !isValidSvmAddress(fill.relayer) then return false
185
+ // If chainIsEvm && !isValidEvmAddress(fill.relayer) then return false
186
+ // If chainIsEvm && isValidEvmAddress(fill.relayer) then return true
187
+ return !isValidEvmAddress(fill.relayer);
188
+ }
@@ -163,12 +163,13 @@ export function updateRunningBalanceForDeposit(
163
163
  runningBalances: RunningBalances,
164
164
  hubPoolClient: HubPoolClient,
165
165
  deposit: V3DepositWithBlock,
166
- updateAmount: BigNumber
166
+ updateAmount: BigNumber,
167
+ mainnetBundleEndBlock: number
167
168
  ): void {
168
169
  const l1TokenCounterpart = hubPoolClient.getL1TokenForL2TokenAtBlock(
169
170
  deposit.inputToken,
170
171
  deposit.originChainId,
171
- deposit.quoteBlockNumber
172
+ mainnetBundleEndBlock
172
173
  );
173
174
  updateRunningBalance(runningBalances, deposit.originChainId, l1TokenCounterpart, updateAmount);
174
175
  }
@@ -48,11 +48,15 @@ const V3RelayDataSS = {
48
48
  message: string(),
49
49
  };
50
50
 
51
- const SortableEventSS = {
51
+ export const SortableEventSS = {
52
52
  blockNumber: number(),
53
- transactionIndex: number(),
54
53
  logIndex: number(),
55
- transactionHash: string(),
54
+
55
+ txnRef: optional(string()),
56
+ txnIndex: optional(number()),
57
+
58
+ transactionHash: optional(string()),
59
+ transactionIndex: optional(number()),
56
60
  };
57
61
 
58
62
  const V3DepositSS = {
@@ -2,7 +2,7 @@ import assert from "assert";
2
2
  import { Contract, EventFilter } from "ethers";
3
3
  import _ from "lodash";
4
4
  import winston from "winston";
5
- import { DEFAULT_CACHING_SAFE_LAG, DEFAULT_CACHING_TTL, ZERO_ADDRESS } from "../constants";
5
+ import { DEFAULT_CACHING_SAFE_LAG, DEFAULT_CACHING_TTL, TOKEN_SYMBOLS_MAP, ZERO_ADDRESS } from "../constants";
6
6
  import {
7
7
  CachingMechanismInterface,
8
8
  CancelledRootBundle,
@@ -44,6 +44,9 @@ import {
44
44
  getTokenInfo,
45
45
  getUsdcSymbol,
46
46
  compareAddressesSimple,
47
+ chainIsSvm,
48
+ getDeployedAddress,
49
+ SvmAddress,
47
50
  } from "../utils";
48
51
  import { AcrossConfigStoreClient as ConfigStoreClient } from "./AcrossConfigStoreClient/AcrossConfigStoreClient";
49
52
  import { BaseAbstractClient, isUpdateFailureReason, UpdateFailureReason } from "./BaseAbstractClient";
@@ -230,40 +233,41 @@ export class HubPoolClient extends BaseAbstractClient {
230
233
  return sortEventsDescending(l2Tokens)[0].l1Token;
231
234
  }
232
235
 
233
- /**
234
- * Returns the L1 token that should be used for an L2 Bridge event. This function is
235
- * designed to be used by the caller to associate the L2 token with its mapped L1 token
236
- * at the HubPool equivalent block number of the L2 event.
237
- * @param deposit Deposit event
238
- * @param returns string L1 token counterpart for Deposit
239
- */
240
- getL1TokenForDeposit(deposit: Pick<DepositWithBlock, "originChainId" | "inputToken" | "quoteBlockNumber">): string {
236
+ protected getL1TokenForDeposit(
237
+ deposit: Pick<DepositWithBlock, "originChainId" | "inputToken" | "quoteBlockNumber">
238
+ ): string {
241
239
  // L1-->L2 token mappings are set via PoolRebalanceRoutes which occur on mainnet,
242
240
  // so we use the latest token mapping. This way if a very old deposit is filled, the relayer can use the
243
241
  // latest L2 token mapping to find the L1 token counterpart.
244
242
  return this.getL1TokenForL2TokenAtBlock(deposit.inputToken, deposit.originChainId, deposit.quoteBlockNumber);
245
243
  }
246
244
 
247
- /**
248
- * Returns the L2 token that should be used as a counterpart to a deposit event. For example, the caller
249
- * might want to know what the refund token will be on l2ChainId for the deposit event.
250
- * @param l2ChainId Chain where caller wants to get L2 token counterpart for
251
- * @param event Deposit event
252
- * @returns string L2 token counterpart on l2ChainId
253
- */
254
- getL2TokenForDeposit(
255
- deposit: Pick<DepositWithBlock, "originChainId" | "destinationChainId" | "inputToken" | "quoteBlockNumber">,
256
- l2ChainId = deposit.destinationChainId
257
- ): string {
258
- const l1Token = this.getL1TokenForDeposit(deposit);
259
- // Use the latest hub block number to find the L2 token counterpart.
260
- return this.getL2TokenForL1TokenAtBlock(l1Token, l2ChainId, deposit.quoteBlockNumber);
261
- }
262
-
263
245
  l2TokenEnabledForL1Token(l1Token: string, destinationChainId: number): boolean {
264
246
  return this.l1TokensToDestinationTokens?.[l1Token]?.[destinationChainId] != undefined;
265
247
  }
266
248
 
249
+ l2TokenEnabledForL1TokenAtBlock(l1Token: string, destinationChainId: number, hubBlockNumber: number): boolean {
250
+ // Find the last mapping published before the target block.
251
+ const l2Token: DestinationTokenWithBlock | undefined = sortEventsDescending(
252
+ this.l1TokensToDestinationTokensWithBlock?.[l1Token]?.[destinationChainId] ?? []
253
+ ).find((mapping: DestinationTokenWithBlock) => mapping.blockNumber <= hubBlockNumber);
254
+ return l2Token !== undefined;
255
+ }
256
+
257
+ l2TokenHasPoolRebalanceRoute(l2Token: string, l2ChainId: number, hubPoolBlock = this.latestBlockSearched): boolean {
258
+ return Object.values(this.l1TokensToDestinationTokensWithBlock).some((destinationTokenMapping) => {
259
+ return Object.entries(destinationTokenMapping).some(([_l2ChainId, setPoolRebalanceRouteEvents]) => {
260
+ return setPoolRebalanceRouteEvents.some((e) => {
261
+ return (
262
+ e.blockNumber <= hubPoolBlock &&
263
+ compareAddressesSimple(e.l2Token, l2Token) &&
264
+ Number(_l2ChainId) === l2ChainId
265
+ );
266
+ });
267
+ });
268
+ });
269
+ }
270
+
267
271
  /**
268
272
  * @dev If tokenAddress + chain do not exist in TOKEN_SYMBOLS_MAP then this will throw.
269
273
  * @param tokenAddress Token address on `chain`
@@ -506,21 +510,23 @@ export class HubPoolClient extends BaseAbstractClient {
506
510
  chainIdB: number,
507
511
  hubPoolBlock = this.latestBlockSearched
508
512
  ): boolean {
509
- try {
510
- // Resolve both SpokePool tokens back to their respective HubPool tokens and verify that they match.
511
- const l1TokenA = this.getL1TokenForL2TokenAtBlock(tokenA, chainIdA, hubPoolBlock);
512
- const l1TokenB = this.getL1TokenForL2TokenAtBlock(tokenB, chainIdB, hubPoolBlock);
513
- if (l1TokenA !== l1TokenB) {
514
- return false;
515
- }
516
-
517
- // Resolve both HubPool tokens back to a current SpokePool token and verify that they match.
518
- const _tokenA = this.getL2TokenForL1TokenAtBlock(l1TokenA, chainIdA, hubPoolBlock);
519
- const _tokenB = this.getL2TokenForL1TokenAtBlock(l1TokenB, chainIdB, hubPoolBlock);
520
- return tokenA === _tokenA && tokenB === _tokenB;
521
- } catch {
522
- return false; // One or both input tokens were not recognised.
513
+ if (
514
+ !this.l2TokenHasPoolRebalanceRoute(tokenA, chainIdA, hubPoolBlock) ||
515
+ !this.l2TokenHasPoolRebalanceRoute(tokenB, chainIdB, hubPoolBlock)
516
+ ) {
517
+ return false;
523
518
  }
519
+ // Resolve both SpokePool tokens back to their respective HubPool tokens and verify that they match.
520
+ const l1TokenA = this.getL1TokenForL2TokenAtBlock(tokenA, chainIdA, hubPoolBlock);
521
+ const l1TokenB = this.getL1TokenForL2TokenAtBlock(tokenB, chainIdB, hubPoolBlock);
522
+ if (l1TokenA !== l1TokenB) {
523
+ return false;
524
+ }
525
+
526
+ // Resolve both HubPool tokens back to a current SpokePool token and verify that they match.
527
+ const _tokenA = this.getL2TokenForL1TokenAtBlock(l1TokenA, chainIdA, hubPoolBlock);
528
+ const _tokenB = this.getL2TokenForL1TokenAtBlock(l1TokenB, chainIdB, hubPoolBlock);
529
+ return tokenA === _tokenA && tokenB === _tokenB;
524
530
  }
525
531
 
526
532
  getSpokeActivationBlockForChain(chainId: number): number {
@@ -878,39 +884,73 @@ export class HubPoolClient extends BaseAbstractClient {
878
884
  if (eventsToQuery.includes("CrossChainContractsSet")) {
879
885
  for (const event of events["CrossChainContractsSet"]) {
880
886
  const args = spreadEventWithBlockNumber(event) as CrossChainContractsSet;
881
- assign(
882
- this.crossChainContracts,
883
- [args.l2ChainId],
884
- [
885
- {
886
- spokePool: args.spokePool,
887
- blockNumber: args.blockNumber,
888
- transactionIndex: args.transactionIndex,
889
- logIndex: args.logIndex,
890
- },
891
- ]
892
- );
887
+ const dataToAdd: CrossChainContractsSet = {
888
+ spokePool: args.spokePool,
889
+ blockNumber: args.blockNumber,
890
+ txnRef: args.txnRef,
891
+ logIndex: args.logIndex,
892
+ txnIndex: args.txnIndex,
893
+ l2ChainId: args.l2ChainId,
894
+ };
895
+ // If the chain is SVM then our `args.spokePool` will be set to the `solanaSpokePool.toAddressUnchecked()` in the
896
+ // hubpool event because our hub deals with `address` types and not byte32. Therefore, we should confirm that the
897
+ // `args.spokePool` is the same as the `solanaSpokePool.toAddressUnchecked()`. We can derive the `solanaSpokePool`
898
+ // address by using the `getDeployedAddress` function.
899
+ if (chainIsSvm(args.l2ChainId)) {
900
+ const solanaSpokePool = getDeployedAddress("SvmSpoke", args.l2ChainId);
901
+ if (!solanaSpokePool) {
902
+ throw new Error(`SVM spoke pool not found for chain ${args.l2ChainId}`);
903
+ }
904
+ const truncatedAddress = SvmAddress.from(solanaSpokePool).toEvmAddress();
905
+ // Verify the event address matches our expected truncated address
906
+ if (args.spokePool.toLowerCase() !== truncatedAddress.toLowerCase()) {
907
+ throw new Error(
908
+ `SVM spoke pool address mismatch for chain ${args.l2ChainId}. ` +
909
+ `Expected ${truncatedAddress}, got ${args.spokePool}`
910
+ );
911
+ }
912
+ // Store the full Solana address
913
+ dataToAdd.spokePool = SvmAddress.from(solanaSpokePool).toBytes32();
914
+ }
915
+ assign(this.crossChainContracts, [args.l2ChainId], [dataToAdd]);
893
916
  }
894
917
  }
895
918
 
896
919
  if (eventsToQuery.includes("SetPoolRebalanceRoute")) {
897
920
  for (const event of events["SetPoolRebalanceRoute"]) {
898
921
  const args = spreadEventWithBlockNumber(event) as SetPoolRebalanceRoot;
922
+
923
+ // If the destination chain is SVM, then we need to convert the destination token to the Solana address.
924
+ // This is because the HubPool contract only holds a truncated address for the USDC token and currently
925
+ // only supports USDC as a destination token for Solana.
926
+ let destinationToken = args.destinationToken;
927
+ if (chainIsSvm(args.destinationChainId)) {
928
+ const usdcTokenSol = TOKEN_SYMBOLS_MAP.USDC.addresses[args.destinationChainId];
929
+ const truncatedAddress = SvmAddress.from(usdcTokenSol).toEvmAddress();
930
+ if (destinationToken.toLowerCase() !== truncatedAddress.toLowerCase()) {
931
+ throw new Error(
932
+ `SVM USDC address mismatch for chain ${args.destinationChainId}. ` +
933
+ `Expected ${truncatedAddress}, got ${destinationToken}`
934
+ );
935
+ }
936
+ destinationToken = SvmAddress.from(usdcTokenSol).toBytes32();
937
+ }
938
+
899
939
  // If the destination token is set to the zero address in an event, then this means Across should no longer
900
940
  // rebalance to this chain.
901
- if (args.destinationToken !== ZERO_ADDRESS) {
902
- assign(this.l1TokensToDestinationTokens, [args.l1Token, args.destinationChainId], args.destinationToken);
941
+ if (destinationToken !== ZERO_ADDRESS) {
942
+ assign(this.l1TokensToDestinationTokens, [args.l1Token, args.destinationChainId], destinationToken);
903
943
  assign(
904
944
  this.l1TokensToDestinationTokensWithBlock,
905
945
  [args.l1Token, args.destinationChainId],
906
946
  [
907
947
  {
908
948
  l1Token: args.l1Token,
909
- l2Token: args.destinationToken,
949
+ l2Token: destinationToken,
910
950
  blockNumber: args.blockNumber,
911
- transactionIndex: args.transactionIndex,
951
+ txnIndex: args.txnIndex,
912
952
  logIndex: args.logIndex,
913
- transactionHash: args.transactionHash,
953
+ txnRef: args.txnRef,
914
954
  },
915
955
  ]
916
956
  );
@@ -956,12 +996,7 @@ export class HubPoolClient extends BaseAbstractClient {
956
996
  this.proposedRootBundles.push(
957
997
  ...events["ProposeRootBundle"]
958
998
  .filter((event) => !this.configOverride.ignoredHubProposedBundles.includes(event.blockNumber))
959
- .map((event) => {
960
- return {
961
- ...spreadEventWithBlockNumber(event),
962
- transactionHash: event.transactionHash,
963
- } as ProposedRootBundle;
964
- })
999
+ .map((event) => spreadEventWithBlockNumber(event) as ProposedRootBundle)
965
1000
  );
966
1001
  }
967
1002
 
@@ -19,6 +19,7 @@ import {
19
19
  } from "../../utils";
20
20
  import {
21
21
  EventSearchConfig,
22
+ logToSortableEvent,
22
23
  paginatedEventQuery,
23
24
  sortEventsAscendingInPlace,
24
25
  spreadEventWithBlockNumber,
@@ -127,7 +128,7 @@ export class EVMSpokePoolClient extends SpokePoolClient {
127
128
  }
128
129
 
129
130
  // Sort all events to ensure they are stored in a consistent order.
130
- events.forEach((events) => sortEventsAscendingInPlace(events));
131
+ events.forEach((events) => sortEventsAscendingInPlace(events.map(logToSortableEvent)));
131
132
 
132
133
  return {
133
134
  success: true,