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