@across-protocol/sdk 4.0.0-beta.2 → 4.0.0-beta.20
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 +320 -179
- 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 +3 -1
- package/dist/cjs/clients/BundleDataClient/utils/FillUtils.js +33 -1
- package/dist/cjs/clients/BundleDataClient/utils/FillUtils.js.map +1 -1
- 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/constants.d.ts +1 -0
- package/dist/cjs/constants.js +2 -1
- 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 +1 -0
- package/dist/cjs/utils/AddressUtils.js +14 -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 +12 -3
- package/dist/cjs/utils/DepositUtils.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.js +3 -3
- package/dist/cjs/utils/SpokeUtils.js.map +1 -1
- package/dist/esm/clients/BundleDataClient/BundleDataClient.d.ts +5 -4
- package/dist/esm/clients/BundleDataClient/BundleDataClient.js +382 -218
- 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 +3 -1
- package/dist/esm/clients/BundleDataClient/utils/FillUtils.js +42 -1
- package/dist/esm/clients/BundleDataClient/utils/FillUtils.js.map +1 -1
- 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/constants.d.ts +1 -0
- package/dist/esm/constants.js +2 -1
- 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 +1 -0
- package/dist/esm/utils/AddressUtils.js +16 -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 +12 -3
- package/dist/esm/utils/DepositUtils.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.js +4 -4
- package/dist/esm/utils/SpokeUtils.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 +3 -1
- package/dist/types/clients/BundleDataClient/utils/FillUtils.d.ts.map +1 -1
- package/dist/types/clients/SpokePoolClient.d.ts +8 -0
- package/dist/types/clients/SpokePoolClient.d.ts.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- 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 +1 -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/NetworkUtils.d.ts +6 -0
- package/dist/types/utils/NetworkUtils.d.ts.map +1 -1
- package/dist/types/utils/SpokeUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/clients/BundleDataClient/BundleDataClient.ts +383 -217
- package/src/clients/BundleDataClient/utils/DataworkerUtils.ts +0 -8
- package/src/clients/BundleDataClient/utils/FillUtils.ts +47 -2
- package/src/clients/SpokePoolClient.ts +19 -6
- package/src/constants.ts +3 -1
- package/src/providers/index.ts +1 -0
- package/src/providers/mockProvider.ts +77 -0
- package/src/utils/AddressUtils.ts +16 -0
- package/src/utils/CachingUtils.ts +1 -1
- package/src/utils/DepositUtils.ts +12 -3
- package/src/utils/NetworkUtils.ts +11 -0
- package/src/utils/SpokeUtils.ts +6 -5
|
@@ -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 {
|
|
@@ -51,7 +54,9 @@ import {
|
|
|
51
54
|
prettyPrintV3SpokePoolEvents,
|
|
52
55
|
V3DepositWithBlock,
|
|
53
56
|
V3FillWithBlock,
|
|
57
|
+
verifyFillRepayment,
|
|
54
58
|
} from "./utils";
|
|
59
|
+
import { PRE_FILL_MIN_CONFIG_STORE_VERSION } from "../../constants";
|
|
55
60
|
|
|
56
61
|
// max(uint256) - 1
|
|
57
62
|
export const INFINITE_FILL_DEADLINE = bnUint32Max;
|
|
@@ -60,6 +65,10 @@ type DataCache = Record<string, Promise<LoadDataReturnValue>>;
|
|
|
60
65
|
|
|
61
66
|
// V3 dictionary helper functions
|
|
62
67
|
function updateExpiredDepositsV3(dict: ExpiredDepositsToRefundV3, deposit: V3DepositWithBlock): void {
|
|
68
|
+
// A deposit refund for a deposit is invalid if the depositor has a bytes32 address input for an EVM chain. It is valid otherwise.
|
|
69
|
+
if (chainIsEvm(deposit.originChainId) && !isValidEvmAddress(deposit.depositor)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
63
72
|
const { originChainId, inputToken } = deposit;
|
|
64
73
|
if (!dict?.[originChainId]?.[inputToken]) {
|
|
65
74
|
assign(dict, [originChainId, inputToken], []);
|
|
@@ -80,8 +89,13 @@ function updateBundleFillsV3(
|
|
|
80
89
|
fill: V3FillWithBlock,
|
|
81
90
|
lpFeePct: BigNumber,
|
|
82
91
|
repaymentChainId: number,
|
|
83
|
-
repaymentToken: string
|
|
92
|
+
repaymentToken: string,
|
|
93
|
+
repaymentAddress: string
|
|
84
94
|
): void {
|
|
95
|
+
// It is impossible to refund a deposit if the repayment chain is EVM and the relayer is a non-evm address.
|
|
96
|
+
if (chainIsEvm(repaymentChainId) && !isValidEvmAddress(repaymentAddress)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
85
99
|
if (!dict?.[repaymentChainId]?.[repaymentToken]) {
|
|
86
100
|
assign(dict, [repaymentChainId, repaymentToken], {
|
|
87
101
|
fills: [],
|
|
@@ -91,19 +105,19 @@ function updateBundleFillsV3(
|
|
|
91
105
|
});
|
|
92
106
|
}
|
|
93
107
|
|
|
94
|
-
const bundleFill: BundleFillV3 = { ...fill, lpFeePct };
|
|
108
|
+
const bundleFill: BundleFillV3 = { ...fill, lpFeePct, relayer: repaymentAddress };
|
|
95
109
|
|
|
96
110
|
// Add all fills, slow and fast, to dictionary.
|
|
97
111
|
assign(dict, [repaymentChainId, repaymentToken, "fills"], [bundleFill]);
|
|
98
112
|
|
|
99
113
|
// All fills update the bundle LP fees.
|
|
100
114
|
const refundObj = dict[repaymentChainId][repaymentToken];
|
|
101
|
-
const realizedLpFee =
|
|
115
|
+
const realizedLpFee = bundleFill.inputAmount.mul(bundleFill.lpFeePct).div(fixedPointAdjustment);
|
|
102
116
|
refundObj.realizedLpFees = refundObj.realizedLpFees ? refundObj.realizedLpFees.add(realizedLpFee) : realizedLpFee;
|
|
103
117
|
|
|
104
118
|
// Only fast fills get refunded.
|
|
105
|
-
if (!isSlowFill(
|
|
106
|
-
const refundAmount =
|
|
119
|
+
if (!isSlowFill(bundleFill)) {
|
|
120
|
+
const refundAmount = bundleFill.inputAmount.mul(fixedPointAdjustment.sub(lpFeePct)).div(fixedPointAdjustment);
|
|
107
121
|
refundObj.totalRefundAmount = refundObj.totalRefundAmount
|
|
108
122
|
? refundObj.totalRefundAmount.add(refundAmount)
|
|
109
123
|
: refundAmount;
|
|
@@ -111,10 +125,10 @@ function updateBundleFillsV3(
|
|
|
111
125
|
// Instantiate dictionary if it doesn't exist.
|
|
112
126
|
refundObj.refunds ??= {};
|
|
113
127
|
|
|
114
|
-
if (refundObj.refunds[
|
|
115
|
-
refundObj.refunds[
|
|
128
|
+
if (refundObj.refunds[bundleFill.relayer]) {
|
|
129
|
+
refundObj.refunds[bundleFill.relayer] = refundObj.refunds[bundleFill.relayer].add(refundAmount);
|
|
116
130
|
} else {
|
|
117
|
-
refundObj.refunds[
|
|
131
|
+
refundObj.refunds[bundleFill.relayer] = refundAmount;
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
}
|
|
@@ -234,7 +248,6 @@ export class BundleDataClient {
|
|
|
234
248
|
bundleData: prettyPrintV3SpokePoolEvents(
|
|
235
249
|
bundleData.bundleDepositsV3,
|
|
236
250
|
bundleData.bundleFillsV3,
|
|
237
|
-
[], // Invalid fills are not persisted to Arweave.
|
|
238
251
|
bundleData.bundleSlowFillsV3,
|
|
239
252
|
bundleData.expiredDepositsToRefundV3,
|
|
240
253
|
bundleData.unexecutableSlowFills
|
|
@@ -282,7 +295,7 @@ export class BundleDataClient {
|
|
|
282
295
|
// so as not to affect this approximate refund count.
|
|
283
296
|
const arweaveData = await this.loadArweaveData(bundleEvaluationBlockRanges);
|
|
284
297
|
if (arweaveData === undefined) {
|
|
285
|
-
combinedRefunds = this.getApproximateRefundsForBlockRange(chainIds, bundleEvaluationBlockRanges);
|
|
298
|
+
combinedRefunds = await this.getApproximateRefundsForBlockRange(chainIds, bundleEvaluationBlockRanges);
|
|
286
299
|
} else {
|
|
287
300
|
const { bundleFillsV3, expiredDepositsToRefundV3 } = arweaveData;
|
|
288
301
|
combinedRefunds = getRefundsFromBundle(bundleFillsV3, expiredDepositsToRefundV3);
|
|
@@ -303,50 +316,72 @@ export class BundleDataClient {
|
|
|
303
316
|
}
|
|
304
317
|
|
|
305
318
|
// @dev This helper function should probably be moved to the InventoryClient
|
|
306
|
-
getApproximateRefundsForBlockRange(chainIds: number[], blockRanges: number[][]): CombinedRefunds {
|
|
319
|
+
async getApproximateRefundsForBlockRange(chainIds: number[], blockRanges: number[][]): Promise<CombinedRefunds> {
|
|
307
320
|
const refundsForChain: CombinedRefunds = {};
|
|
308
321
|
for (const chainId of chainIds) {
|
|
309
322
|
if (this.spokePoolClients[chainId] === undefined) {
|
|
310
323
|
continue;
|
|
311
324
|
}
|
|
312
325
|
const chainIndex = chainIds.indexOf(chainId);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
326
|
+
// @todo This function does not account for pre-fill refunds as it is optimized for speed. The way to detect
|
|
327
|
+
// pre-fill refunds is to load all deposits that are unmatched by fills in the spoke pool client's memory
|
|
328
|
+
// and then query the FillStatus on-chain, but that might slow this function down too much. For now, we
|
|
329
|
+
// will live with this expected inaccuracy as it should be small. The pre-fill would have to precede the deposit
|
|
330
|
+
// by more than the caller's event lookback window which is expected to be unlikely.
|
|
331
|
+
const fillsToCount = await filterAsync(this.spokePoolClients[chainId].getFills(), async (fill) => {
|
|
332
|
+
if (
|
|
333
|
+
fill.blockNumber < blockRanges[chainIndex][0] ||
|
|
334
|
+
fill.blockNumber > blockRanges[chainIndex][1] ||
|
|
335
|
+
isZeroValueFillOrSlowFillRequest(fill)
|
|
336
|
+
) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
319
339
|
|
|
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(
|
|
340
|
+
// If origin spoke pool client isn't defined, we can't validate it.
|
|
341
|
+
if (this.spokePoolClients[fill.originChainId] === undefined) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
const matchingDeposit = this.spokePoolClients[fill.originChainId].getDeposit(fill.depositId);
|
|
345
|
+
const hasMatchingDeposit =
|
|
346
|
+
matchingDeposit !== undefined &&
|
|
347
|
+
this.getRelayHashFromEvent(fill) === this.getRelayHashFromEvent(matchingDeposit);
|
|
348
|
+
if (hasMatchingDeposit) {
|
|
349
|
+
const validRepayment = await verifyFillRepayment(
|
|
334
350
|
fill,
|
|
335
|
-
this.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
351
|
+
this.spokePoolClients[fill.destinationChainId].spokePool.provider,
|
|
352
|
+
matchingDeposit,
|
|
353
|
+
// @dev: to get valid repayment chain ID's, get all chain IDs for the bundle block range and remove
|
|
354
|
+
// disabled block ranges.
|
|
355
|
+
this.clients.configStoreClient
|
|
356
|
+
.getChainIdIndicesForBlock(blockRanges[0][1])
|
|
357
|
+
.filter((_chainId, i) => !isChainDisabled(blockRanges[i]))
|
|
339
358
|
);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
359
|
+
if (!isDefined(validRepayment)) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return hasMatchingDeposit;
|
|
364
|
+
});
|
|
365
|
+
fillsToCount.forEach((fill) => {
|
|
366
|
+
const matchingDeposit = this.spokePoolClients[fill.originChainId].getDeposit(fill.depositId);
|
|
367
|
+
assert(isDefined(matchingDeposit), "Deposit not found for fill.");
|
|
368
|
+
const { chainToSendRefundTo, repaymentToken } = getRefundInformationFromFill(
|
|
369
|
+
fill,
|
|
370
|
+
this.clients.hubPoolClient,
|
|
371
|
+
blockRanges,
|
|
372
|
+
this.chainIdListForBundleEvaluationBlockNumbers,
|
|
373
|
+
matchingDeposit!.fromLiteChain // Use ! because we've already asserted that matchingDeposit is defined.
|
|
374
|
+
);
|
|
375
|
+
// Assume that lp fees are 0 for the sake of speed. In the future we could batch compute
|
|
376
|
+
// these or make hardcoded assumptions based on the origin-repayment chain direction. This might result
|
|
377
|
+
// in slight over estimations of refunds, but its not clear whether underestimating or overestimating is
|
|
378
|
+
// worst from the relayer's perspective.
|
|
379
|
+
const { relayer, inputAmount: refundAmount } = fill;
|
|
380
|
+
refundsForChain[chainToSendRefundTo] ??= {};
|
|
381
|
+
refundsForChain[chainToSendRefundTo][repaymentToken] ??= {};
|
|
382
|
+
const existingRefundAmount = refundsForChain[chainToSendRefundTo][repaymentToken][relayer] ?? bnZero;
|
|
383
|
+
refundsForChain[chainToSendRefundTo][repaymentToken][relayer] = existingRefundAmount.add(refundAmount);
|
|
384
|
+
});
|
|
350
385
|
}
|
|
351
386
|
return refundsForChain;
|
|
352
387
|
}
|
|
@@ -473,7 +508,7 @@ export class BundleDataClient {
|
|
|
473
508
|
// ok for this use case.
|
|
474
509
|
const arweaveData = await this.loadArweaveData(pendingBundleBlockRanges);
|
|
475
510
|
if (arweaveData === undefined) {
|
|
476
|
-
combinedRefunds.push(this.getApproximateRefundsForBlockRange(chainIds, pendingBundleBlockRanges));
|
|
511
|
+
combinedRefunds.push(await this.getApproximateRefundsForBlockRange(chainIds, pendingBundleBlockRanges));
|
|
477
512
|
} else {
|
|
478
513
|
const { bundleFillsV3, expiredDepositsToRefundV3 } = arweaveData;
|
|
479
514
|
combinedRefunds.push(getRefundsFromBundle(bundleFillsV3, expiredDepositsToRefundV3));
|
|
@@ -488,7 +523,7 @@ export class BundleDataClient {
|
|
|
488
523
|
// - Only look up fills sent after the pending bundle's end blocks
|
|
489
524
|
// - Skip LP fee computations and just assume the relayer is being refunded the full deposit.inputAmount
|
|
490
525
|
const start = performance.now();
|
|
491
|
-
combinedRefunds.push(this.getApproximateRefundsForBlockRange(chainIds, widestBundleBlockRanges));
|
|
526
|
+
combinedRefunds.push(await this.getApproximateRefundsForBlockRange(chainIds, widestBundleBlockRanges));
|
|
492
527
|
this.logger.debug({
|
|
493
528
|
at: "BundleDataClient#getNextBundleRefunds",
|
|
494
529
|
message: `Loading approximate refunds for next bundle in ${Math.round(performance.now() - start) / 1000}s.`,
|
|
@@ -655,6 +690,7 @@ export class BundleDataClient {
|
|
|
655
690
|
const bundleDepositsV3: BundleDepositsV3 = {}; // Deposits in bundle block range.
|
|
656
691
|
const bundleFillsV3: BundleFillsV3 = {}; // Fills to refund in bundle block range.
|
|
657
692
|
const bundleInvalidFillsV3: V3FillWithBlock[] = []; // Fills that are not valid in this bundle.
|
|
693
|
+
const bundleUnrepayableFillsV3: V3FillWithBlock[] = []; // Fills that are not repayable in this bundle.
|
|
658
694
|
const bundleSlowFillsV3: BundleSlowFills = {}; // Deposits that we need to send slow fills
|
|
659
695
|
// for in this bundle.
|
|
660
696
|
const expiredDepositsToRefundV3: ExpiredDepositsToRefundV3 = {};
|
|
@@ -686,6 +722,10 @@ export class BundleDataClient {
|
|
|
686
722
|
);
|
|
687
723
|
};
|
|
688
724
|
|
|
725
|
+
const _depositIsExpired = (deposit: DepositWithBlock): boolean => {
|
|
726
|
+
return deposit.fillDeadline < bundleBlockTimestamps[deposit.destinationChainId][1];
|
|
727
|
+
};
|
|
728
|
+
|
|
689
729
|
const _getFillStatusForDeposit = (deposit: Deposit, queryBlock: number): Promise<FillStatus> => {
|
|
690
730
|
return spokePoolClients[deposit.destinationChainId].relayFillStatus(
|
|
691
731
|
deposit,
|
|
@@ -737,7 +777,7 @@ export class BundleDataClient {
|
|
|
737
777
|
// Note: Since there are no partial fills in v3, there should only be one fill per relay hash.
|
|
738
778
|
// Moreover, the SpokePool blocks multiple slow fill requests, so
|
|
739
779
|
// there should also only be one slow fill request per relay hash.
|
|
740
|
-
|
|
780
|
+
deposits?: V3DepositWithBlock[];
|
|
741
781
|
fill?: V3FillWithBlock;
|
|
742
782
|
slowFillRequest?: SlowFillRequestWithBlock;
|
|
743
783
|
};
|
|
@@ -748,6 +788,27 @@ export class BundleDataClient {
|
|
|
748
788
|
const bundleDepositHashes: string[] = [];
|
|
749
789
|
const olderDepositHashes: string[] = [];
|
|
750
790
|
|
|
791
|
+
const decodeBundleDepositHash = (depositHash: string): { relayDataHash: string; index: number } => {
|
|
792
|
+
const [relayDataHash, i] = depositHash.split("@");
|
|
793
|
+
return { relayDataHash, index: Number(i) };
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// We use the following toggle to aid with the migration to pre-fills. The first bundle proposed using this
|
|
797
|
+
// pre-fill logic can double refund pre-fills that have already been filled in the last bundle, because the
|
|
798
|
+
// last bundle did not recognize a fill as a pre-fill. Therefore the developer should ensure that the version
|
|
799
|
+
// is bumped to the PRE_FILL_MIN_CONFIG_STORE_VERSION version before the first pre-fill bundle is proposed.
|
|
800
|
+
// To test the following bundle after this, the developer can set the FORCE_REFUND_PREFILLS environment variable
|
|
801
|
+
// to "true" simulate the bundle with pre-fill refunds.
|
|
802
|
+
// @todo Remove this logic once we have advanced sufficiently past the pre-fill migration.
|
|
803
|
+
const startBlockForMainnet = getBlockRangeForChain(
|
|
804
|
+
blockRangesForChains,
|
|
805
|
+
this.clients.hubPoolClient.chainId,
|
|
806
|
+
this.chainIdListForBundleEvaluationBlockNumbers
|
|
807
|
+
)[0];
|
|
808
|
+
const versionAtProposalBlock = this.clients.configStoreClient.getConfigStoreVersionForBlock(startBlockForMainnet);
|
|
809
|
+
const canRefundPrefills =
|
|
810
|
+
versionAtProposalBlock >= PRE_FILL_MIN_CONFIG_STORE_VERSION || process.env.FORCE_REFUND_PREFILLS === "true";
|
|
811
|
+
|
|
751
812
|
let depositCounter = 0;
|
|
752
813
|
for (const originChainId of allChainIds) {
|
|
753
814
|
const originClient = spokePoolClients[originChainId];
|
|
@@ -761,17 +822,20 @@ export class BundleDataClient {
|
|
|
761
822
|
// Only evaluate deposits that are in this bundle or in previous bundles. This means we cannot issue fill
|
|
762
823
|
// refunds or slow fills here for deposits that are in future bundles (i.e. "pre-fills"). Instead, we'll
|
|
763
824
|
// evaluate these pre-fills once the deposit is inside the "current" bundle block range.
|
|
764
|
-
if (
|
|
825
|
+
if (deposit.blockNumber > originChainBlockRange[1] || isZeroValueDeposit(deposit)) {
|
|
765
826
|
return;
|
|
766
827
|
}
|
|
767
828
|
depositCounter++;
|
|
768
829
|
const relayDataHash = this.getRelayHashFromEvent(deposit);
|
|
830
|
+
|
|
769
831
|
if (!v3RelayHashes[relayDataHash]) {
|
|
770
832
|
v3RelayHashes[relayDataHash] = {
|
|
771
|
-
|
|
833
|
+
deposits: [deposit],
|
|
772
834
|
fill: undefined,
|
|
773
835
|
slowFillRequest: undefined,
|
|
774
836
|
};
|
|
837
|
+
} else {
|
|
838
|
+
v3RelayHashes[relayDataHash].deposits!.push(deposit);
|
|
775
839
|
}
|
|
776
840
|
|
|
777
841
|
// Once we've saved the deposit hash into v3RelayHashes, then we can exit early here if the inputAmount
|
|
@@ -782,11 +846,20 @@ export class BundleDataClient {
|
|
|
782
846
|
return;
|
|
783
847
|
}
|
|
784
848
|
|
|
849
|
+
// Evaluate all expired deposits after fetching fill statuses,
|
|
850
|
+
// since we can't know for certain whether an expired deposit was filled a long time ago.
|
|
851
|
+
const newBundleDepositHash = `${relayDataHash}@${v3RelayHashes[relayDataHash].deposits!.length - 1}`;
|
|
852
|
+
const decodedBundleDepositHash = decodeBundleDepositHash(newBundleDepositHash);
|
|
853
|
+
assert(
|
|
854
|
+
decodedBundleDepositHash.relayDataHash === relayDataHash &&
|
|
855
|
+
decodedBundleDepositHash.index === v3RelayHashes[relayDataHash].deposits!.length - 1,
|
|
856
|
+
"Not using correct bundle deposit hash key"
|
|
857
|
+
);
|
|
785
858
|
if (deposit.blockNumber >= originChainBlockRange[0]) {
|
|
786
|
-
bundleDepositHashes.push(
|
|
859
|
+
bundleDepositHashes.push(newBundleDepositHash);
|
|
787
860
|
updateBundleDepositsV3(bundleDepositsV3, deposit);
|
|
788
861
|
} else if (deposit.blockNumber < originChainBlockRange[0]) {
|
|
789
|
-
olderDepositHashes.push(
|
|
862
|
+
olderDepositHashes.push(newBundleDepositHash);
|
|
790
863
|
}
|
|
791
864
|
});
|
|
792
865
|
}
|
|
@@ -825,44 +898,67 @@ export class BundleDataClient {
|
|
|
825
898
|
(fill) => fill.blockNumber <= destinationChainBlockRange[1] && !isZeroValueFillOrSlowFillRequest(fill)
|
|
826
899
|
),
|
|
827
900
|
async (fill) => {
|
|
828
|
-
const relayDataHash = this.getRelayHashFromEvent(fill);
|
|
829
901
|
fillCounter++;
|
|
830
|
-
|
|
902
|
+
const relayDataHash = this.getRelayHashFromEvent(fill);
|
|
831
903
|
if (v3RelayHashes[relayDataHash]) {
|
|
832
904
|
if (!v3RelayHashes[relayDataHash].fill) {
|
|
833
905
|
assert(
|
|
834
|
-
isDefined(v3RelayHashes[relayDataHash].
|
|
906
|
+
isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0,
|
|
835
907
|
"Deposit should exist in relay hash dictionary."
|
|
836
908
|
);
|
|
837
909
|
// At this point, the v3RelayHashes entry already existed meaning that there is a matching deposit,
|
|
838
|
-
// so this fill
|
|
910
|
+
// so this fill can no longer be filled on-chain.
|
|
839
911
|
v3RelayHashes[relayDataHash].fill = fill;
|
|
840
912
|
if (fill.blockNumber >= destinationChainBlockRange[0]) {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
913
|
+
const fillToRefund = await verifyFillRepayment(
|
|
914
|
+
fill,
|
|
915
|
+
destinationClient.spokePool.provider,
|
|
916
|
+
v3RelayHashes[relayDataHash].deposits![0],
|
|
917
|
+
allChainIds
|
|
918
|
+
);
|
|
919
|
+
if (!isDefined(fillToRefund)) {
|
|
920
|
+
bundleUnrepayableFillsV3.push(fill);
|
|
921
|
+
// We don't return here yet because we still need to mark unexecutable slow fill leaves
|
|
922
|
+
// or duplicate deposits. However, we won't issue a fast fill refund.
|
|
923
|
+
} else {
|
|
924
|
+
v3RelayHashes[relayDataHash].fill = fillToRefund;
|
|
925
|
+
validatedBundleV3Fills.push({
|
|
926
|
+
...fillToRefund,
|
|
927
|
+
quoteTimestamp: v3RelayHashes[relayDataHash].deposits![0].quoteTimestamp, // ! due to assert above
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
845
931
|
// If fill replaced a slow fill request, then mark it as one that might have created an
|
|
846
932
|
// unexecutable slow fill. We can't know for sure until we check the slow fill request
|
|
847
933
|
// events.
|
|
848
934
|
// slow fill requests for deposits from or to lite chains are considered invalid
|
|
849
935
|
if (
|
|
850
936
|
fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill &&
|
|
851
|
-
|
|
852
|
-
!v3RelayHashes[relayDataHash].deposit!.toLiteChain
|
|
937
|
+
_canCreateSlowFillLeaf(v3RelayHashes[relayDataHash].deposits![0])
|
|
853
938
|
) {
|
|
854
939
|
fastFillsReplacingSlowFills.push(relayDataHash);
|
|
855
940
|
}
|
|
941
|
+
// Now that know this deposit has been filled on-chain, identify any duplicate deposits sent for this fill and refund
|
|
942
|
+
// them, because they would not be refunded otherwise. These deposits can no longer expire and get
|
|
943
|
+
// refunded as an expired deposit, and they won't trigger a pre-fill refund because the fill is
|
|
944
|
+
// in this bundle. Pre-fill refunds only happen when deposits are sent in this bundle and the
|
|
945
|
+
// fill is from a prior bundle.
|
|
946
|
+
const duplicateDeposits = v3RelayHashes[relayDataHash].deposits!.slice(1);
|
|
947
|
+
duplicateDeposits.forEach((duplicateDeposit) => {
|
|
948
|
+
updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit);
|
|
949
|
+
});
|
|
856
950
|
}
|
|
951
|
+
} else {
|
|
952
|
+
throw new Error("Duplicate fill detected");
|
|
857
953
|
}
|
|
858
954
|
return;
|
|
859
955
|
}
|
|
860
956
|
|
|
861
957
|
// At this point, there is no relay hash dictionary entry for this fill, so we need to
|
|
862
|
-
// instantiate the entry.
|
|
958
|
+
// instantiate the entry. We won't modify the fill.relayer until we match it with a deposit.
|
|
863
959
|
v3RelayHashes[relayDataHash] = {
|
|
864
|
-
|
|
865
|
-
fill
|
|
960
|
+
deposits: undefined,
|
|
961
|
+
fill,
|
|
866
962
|
slowFillRequest: undefined,
|
|
867
963
|
};
|
|
868
964
|
|
|
@@ -890,24 +986,42 @@ export class BundleDataClient {
|
|
|
890
986
|
bundleInvalidFillsV3.push(fill);
|
|
891
987
|
} else {
|
|
892
988
|
const matchedDeposit = historicalDeposit.deposit;
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
989
|
+
v3RelayHashes[relayDataHash].deposits = [matchedDeposit];
|
|
990
|
+
|
|
991
|
+
const fillToRefund = await verifyFillRepayment(
|
|
992
|
+
fill,
|
|
993
|
+
destinationClient.spokePool.provider,
|
|
994
|
+
matchedDeposit,
|
|
995
|
+
allChainIds
|
|
996
|
+
);
|
|
997
|
+
if (!isDefined(fillToRefund)) {
|
|
998
|
+
bundleUnrepayableFillsV3.push(fill);
|
|
999
|
+
// Don't return yet as we still need to mark down any unexecutable slow fill leaves
|
|
1000
|
+
// in case this fast fill replaced a slow fill request.
|
|
1001
|
+
} else {
|
|
1002
|
+
// @dev Since queryHistoricalDepositForFill validates the fill by checking individual
|
|
1003
|
+
// object property values against the deposit's, we
|
|
1004
|
+
// sanity check it here by comparing the full relay hashes. If there's an error here then the
|
|
1005
|
+
// historical deposit query is not working as expected.
|
|
1006
|
+
assert(this.getRelayHashFromEvent(matchedDeposit) === relayDataHash, "Relay hashes should match.");
|
|
1007
|
+
validatedBundleV3Fills.push({
|
|
1008
|
+
...fillToRefund,
|
|
1009
|
+
quoteTimestamp: matchedDeposit.quoteTimestamp,
|
|
1010
|
+
});
|
|
1011
|
+
v3RelayHashes[relayDataHash].fill = fillToRefund;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
903
1014
|
// slow fill requests for deposits from or to lite chains are considered invalid
|
|
904
1015
|
if (
|
|
905
1016
|
fill.relayExecutionInfo.fillType === FillType.ReplacedSlowFill &&
|
|
906
|
-
|
|
907
|
-
!matchedDeposit.toLiteChain
|
|
1017
|
+
_canCreateSlowFillLeaf(matchedDeposit)
|
|
908
1018
|
) {
|
|
909
1019
|
fastFillsReplacingSlowFills.push(relayDataHash);
|
|
910
1020
|
}
|
|
1021
|
+
|
|
1022
|
+
// No need to check for duplicate deposits here since we would have seen them in memory if they
|
|
1023
|
+
// had a non-infinite fill deadline, and duplicate deposits with infinite deadlines are impossible
|
|
1024
|
+
// to send.
|
|
911
1025
|
}
|
|
912
1026
|
}
|
|
913
1027
|
}
|
|
@@ -932,38 +1046,40 @@ export class BundleDataClient {
|
|
|
932
1046
|
v3RelayHashes[relayDataHash].slowFillRequest = slowFillRequest;
|
|
933
1047
|
if (v3RelayHashes[relayDataHash].fill) {
|
|
934
1048
|
// If there is a fill matching the relay hash, then this slow fill request can't be used
|
|
935
|
-
// to create a slow fill for a filled deposit.
|
|
1049
|
+
// to create a slow fill for a filled deposit. This takes advantage of the fact that
|
|
1050
|
+
// slow fill requests must precede fills, so if there is a matching fill for this request's
|
|
1051
|
+
// relay data, then this slow fill will be unexecutable.
|
|
936
1052
|
return;
|
|
937
1053
|
}
|
|
938
1054
|
assert(
|
|
939
|
-
isDefined(v3RelayHashes[relayDataHash].
|
|
1055
|
+
isDefined(v3RelayHashes[relayDataHash].deposits) && v3RelayHashes[relayDataHash].deposits!.length > 0,
|
|
940
1056
|
"Deposit should exist in relay hash dictionary."
|
|
941
1057
|
);
|
|
942
1058
|
// The ! is safe here because we've already checked that the deposit exists in the relay hash dictionary.
|
|
943
|
-
const matchedDeposit = v3RelayHashes[relayDataHash].
|
|
944
|
-
if (!_canCreateSlowFillLeaf(matchedDeposit)) {
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
1059
|
+
const matchedDeposit = v3RelayHashes[relayDataHash].deposits![0];
|
|
947
1060
|
|
|
948
1061
|
// If there is no fill matching the relay hash, then this might be a valid slow fill request
|
|
949
1062
|
// that we should produce a slow fill leaf for. Check if the slow fill request is in the
|
|
950
1063
|
// destination chain block range.
|
|
951
1064
|
if (
|
|
952
1065
|
slowFillRequest.blockNumber >= destinationChainBlockRange[0] &&
|
|
1066
|
+
_canCreateSlowFillLeaf(matchedDeposit) &&
|
|
953
1067
|
// Deposit must not have expired in this bundle.
|
|
954
|
-
|
|
1068
|
+
!_depositIsExpired(matchedDeposit)
|
|
955
1069
|
) {
|
|
956
1070
|
// At this point, the v3RelayHashes entry already existed meaning that there is a matching deposit,
|
|
957
1071
|
// so this slow fill request relay data is correct.
|
|
958
1072
|
validatedBundleSlowFills.push(matchedDeposit);
|
|
959
1073
|
}
|
|
1074
|
+
} else {
|
|
1075
|
+
throw new Error("Duplicate slow fill request detected.");
|
|
960
1076
|
}
|
|
961
1077
|
return;
|
|
962
1078
|
}
|
|
963
1079
|
|
|
964
1080
|
// Instantiate dictionary if there is neither a deposit nor fill matching it.
|
|
965
1081
|
v3RelayHashes[relayDataHash] = {
|
|
966
|
-
|
|
1082
|
+
deposits: undefined,
|
|
967
1083
|
fill: undefined,
|
|
968
1084
|
slowFillRequest: slowFillRequest,
|
|
969
1085
|
};
|
|
@@ -998,12 +1114,12 @@ export class BundleDataClient {
|
|
|
998
1114
|
this.getRelayHashFromEvent(matchedDeposit) === relayDataHash,
|
|
999
1115
|
"Deposit relay hashes should match."
|
|
1000
1116
|
);
|
|
1001
|
-
v3RelayHashes[relayDataHash].
|
|
1117
|
+
v3RelayHashes[relayDataHash].deposits = [matchedDeposit];
|
|
1002
1118
|
|
|
1003
1119
|
if (
|
|
1004
1120
|
!_canCreateSlowFillLeaf(matchedDeposit) ||
|
|
1005
1121
|
// Deposit must not have expired in this bundle.
|
|
1006
|
-
|
|
1122
|
+
_depositIsExpired(matchedDeposit)
|
|
1007
1123
|
) {
|
|
1008
1124
|
return;
|
|
1009
1125
|
}
|
|
@@ -1019,141 +1135,163 @@ export class BundleDataClient {
|
|
|
1019
1135
|
// - Or, has the deposit expired in this bundle? If so, then we need to issue an expiry refund.
|
|
1020
1136
|
// - And finally, has the deposit been slow filled? If so, then we need to issue a slow fill leaf
|
|
1021
1137
|
// 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
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1138
|
+
|
|
1139
|
+
// @todo Only start refunding pre-fills and slow fill requests after a config store version is activated. We
|
|
1140
|
+
// should remove this check once we've advanced far beyond the version bump block.
|
|
1141
|
+
await mapAsync(bundleDepositHashes, async (depositHash, currentBundleDepositHashIndex) => {
|
|
1142
|
+
// We don't need to call verifyFillRepayment() here to replace the fill.relayer because this value should already
|
|
1143
|
+
// be overwritten because the deposit and fill both exist.
|
|
1144
|
+
const { relayDataHash, index } = decodeBundleDepositHash(depositHash);
|
|
1145
|
+
const { deposits, fill, slowFillRequest } = v3RelayHashes[relayDataHash];
|
|
1146
|
+
const deposit = deposits![index];
|
|
1147
|
+
if (!deposit) throw new Error("Deposit should exist in relay hash dictionary.");
|
|
1148
|
+
if (deposit.originChainId !== originChainId || deposit.destinationChainId !== destinationChainId) {
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
const isDuplicateDepositInBundle = bundleDepositHashes
|
|
1152
|
+
.slice(0, currentBundleDepositHashIndex)
|
|
1153
|
+
.some((_depositHash) => {
|
|
1154
|
+
const { relayDataHash: _relayDataHash } = decodeBundleDepositHash(_depositHash);
|
|
1155
|
+
return _relayDataHash === relayDataHash;
|
|
1156
|
+
});
|
|
1157
|
+
// Don't refund duplicate deposits from a prior bundle, as they should have been refunded already
|
|
1158
|
+
// if they coincided with another deposit in the same bundle. If they didn't, then its input
|
|
1159
|
+
// amount was used to refund a pre-fill.
|
|
1160
|
+
// We will refund any duplicate deposits the first time that we see a deposit hash in this bundle.
|
|
1161
|
+
// If this is the first time we are seeing this deposit hash, then refund any duplicate deposits since
|
|
1162
|
+
// a fill exists for it and these duplicate deposits can no longer be refunded for expiry.
|
|
1163
|
+
// This means unfortunately that every duplicate deposit that is sent that
|
|
1164
|
+
// does not accompany another deposit in the same bundle will not be refunded. This should be unlikely.
|
|
1165
|
+
// This rule also allows us to protect honest depositors who accidentally send duplicate deposits
|
|
1166
|
+
// in rapid succession in most cases, unless they are unlucky enough to send duplicate deposits
|
|
1167
|
+
// in different bundle block ranges.
|
|
1168
|
+
const duplicateDepositsInBundle = deposits!.slice(index + 1);
|
|
1169
|
+
|
|
1170
|
+
// We are willing to refund a pre-fill multiple times for each duplicate deposit.
|
|
1171
|
+
// This is because a duplicate deposit for a pre-fill cannot get
|
|
1172
|
+
// refunded to the depositor anymore because its fill status on-chain has changed to Filled. Therefore
|
|
1173
|
+
// any duplicate deposits result in a net loss of funds for the depositor and effectively pay out
|
|
1174
|
+
// the pre-filler.
|
|
1175
|
+
|
|
1176
|
+
// If fill exists in memory, then the only case in which we need to create a refund is if the
|
|
1177
|
+
// the fill occurred in a previous bundle. There are no expiry refunds for filled deposits.
|
|
1178
|
+
if (fill) {
|
|
1179
|
+
if (!isDuplicateDepositInBundle && fill.blockNumber < destinationChainBlockRange[0]) {
|
|
1180
|
+
duplicateDepositsInBundle.forEach((duplicateDeposit) => {
|
|
1181
|
+
updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit);
|
|
1182
|
+
});
|
|
1183
|
+
if (canRefundPrefills) {
|
|
1050
1184
|
// If fill is in the current bundle then we can assume there is already a refund for it, so only
|
|
1051
1185
|
// include this pre fill if the fill is in an older bundle. If fill is after this current bundle, then
|
|
1052
1186
|
// we won't consider it, following the previous treatment of fills after the bundle block range.
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1187
|
+
if (!isSlowFill(fill)) {
|
|
1188
|
+
validatedBundleV3Fills.push({
|
|
1189
|
+
...fill,
|
|
1190
|
+
quoteTimestamp: deposit.quoteTimestamp,
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1057
1193
|
}
|
|
1058
|
-
return;
|
|
1059
1194
|
}
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1060
1197
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1198
|
+
// If a slow fill request exists in memory, then we know the deposit has not been filled because fills
|
|
1199
|
+
// must follow slow fill requests and we would have seen the fill already if it existed. Therefore,
|
|
1200
|
+
// we can conclude that either the deposit has expired and we need to create a deposit expiry refund, or
|
|
1201
|
+
// we need to create a slow fill leaf for the deposit. The latter should only happen if the slow fill request
|
|
1202
|
+
// took place in a prior bundle otherwise we would have already created a slow fill leaf for it.
|
|
1203
|
+
if (slowFillRequest) {
|
|
1204
|
+
if (_depositIsExpired(deposit)) {
|
|
1205
|
+
updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
|
|
1206
|
+
} else if (
|
|
1207
|
+
!isDuplicateDepositInBundle &&
|
|
1208
|
+
canRefundPrefills &&
|
|
1209
|
+
slowFillRequest.blockNumber < destinationChainBlockRange[0] &&
|
|
1210
|
+
_canCreateSlowFillLeaf(deposit)
|
|
1211
|
+
) {
|
|
1212
|
+
validatedBundleSlowFills.push(deposit);
|
|
1076
1213
|
}
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1077
1216
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1217
|
+
// So at this point in the code, there is no fill or slow fill request in memory for this deposit.
|
|
1218
|
+
// We need to check its fill status on-chain to figure out whether to issue a refund or a slow fill leaf.
|
|
1219
|
+
// We can assume at this point that all fills or slow fill requests, if found, were in previous bundles
|
|
1220
|
+
// because the spoke pool client lookback would have returned this entire bundle of events and stored
|
|
1221
|
+
// them into the relay hash dictionary.
|
|
1222
|
+
const fillStatus = await _getFillStatusForDeposit(deposit, destinationChainBlockRange[1]);
|
|
1223
|
+
|
|
1224
|
+
// If deposit was filled, then we need to issue a refund for the fill and also any duplicate deposits
|
|
1225
|
+
// in the same bundle.
|
|
1226
|
+
if (fillStatus === FillStatus.Filled) {
|
|
1227
|
+
// We need to find the fill event to issue a refund to the right relayer and repayment chain,
|
|
1228
|
+
// or msg.sender if relayer address is invalid for the repayment chain.
|
|
1229
|
+
const prefill = await this.findMatchingFillEvent(deposit, destinationClient);
|
|
1230
|
+
assert(isDefined(prefill), `findFillEvent# Cannot find prefill: ${relayDataHash}`);
|
|
1231
|
+
assert(this.getRelayHashFromEvent(prefill!) === relayDataHash, "Relay hashes should match.");
|
|
1232
|
+
if (!isDuplicateDepositInBundle) {
|
|
1233
|
+
duplicateDepositsInBundle.forEach((duplicateDeposit) => {
|
|
1234
|
+
updateExpiredDepositsV3(expiredDepositsToRefundV3, duplicateDeposit);
|
|
1235
|
+
});
|
|
1236
|
+
const verifiedFill = await verifyFillRepayment(
|
|
1237
|
+
prefill!,
|
|
1238
|
+
destinationClient.spokePool.provider,
|
|
1091
1239
|
deposit,
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
)
|
|
1095
|
-
if (!isSlowFill(prefill)) {
|
|
1240
|
+
allChainIds
|
|
1241
|
+
);
|
|
1242
|
+
if (canRefundPrefills && isDefined(verifiedFill) && !isSlowFill(verifiedFill)) {
|
|
1096
1243
|
validatedBundleV3Fills.push({
|
|
1097
|
-
...
|
|
1244
|
+
...verifiedFill!,
|
|
1098
1245
|
quoteTimestamp: deposit.quoteTimestamp,
|
|
1099
1246
|
});
|
|
1100
1247
|
}
|
|
1101
1248
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1249
|
+
}
|
|
1250
|
+
// If deposit is not filled and its newly expired, we can create a deposit refund for it.
|
|
1251
|
+
// We don't check that fillDeadline >= bundleBlockTimestamps[destinationChainId][0] because
|
|
1252
|
+
// that would eliminate any deposits in this bundle with a very low fillDeadline like equal to 0
|
|
1253
|
+
// for example. Those should be included in this bundle of refunded deposits.
|
|
1254
|
+
else if (_depositIsExpired(deposit)) {
|
|
1255
|
+
updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
|
|
1256
|
+
}
|
|
1257
|
+
// If slow fill requested, then issue a slow fill leaf for the deposit.
|
|
1258
|
+
else if (fillStatus === FillStatus.RequestedSlowFill) {
|
|
1259
|
+
// Input and Output tokens must be equivalent on the deposit for this to be slow filled.
|
|
1260
|
+
// Slow fill requests for deposits from or to lite chains are considered invalid
|
|
1261
|
+
if (!isDuplicateDepositInBundle && canRefundPrefills && _canCreateSlowFillLeaf(deposit)) {
|
|
1262
|
+
// If deposit newly expired, then we can't create a slow fill leaf for it but we can
|
|
1263
|
+
// create a deposit refund for it.
|
|
1264
|
+
validatedBundleSlowFills.push(deposit);
|
|
1118
1265
|
}
|
|
1119
1266
|
}
|
|
1120
|
-
);
|
|
1267
|
+
});
|
|
1121
1268
|
|
|
1122
1269
|
// For all fills that came after a slow fill request, we can now check if the slow fill request
|
|
1123
1270
|
// was a valid one and whether it was created in a previous bundle. If so, then it created a slow fill
|
|
1124
1271
|
// leaf that is now unexecutable.
|
|
1125
1272
|
fastFillsReplacingSlowFills.forEach((relayDataHash) => {
|
|
1126
|
-
const {
|
|
1273
|
+
const { deposits, slowFillRequest, fill } = v3RelayHashes[relayDataHash];
|
|
1127
1274
|
assert(
|
|
1128
1275
|
fill?.relayExecutionInfo.fillType === FillType.ReplacedSlowFill,
|
|
1129
1276
|
"Fill type should be ReplacedSlowFill."
|
|
1130
1277
|
);
|
|
1131
1278
|
// Needed for TSC - are implicitely checking that deposit exists by making it to this point.
|
|
1132
|
-
if (!
|
|
1279
|
+
if (!deposits || deposits.length < 1) {
|
|
1133
1280
|
throw new Error("Deposit should exist in relay hash dictionary.");
|
|
1134
1281
|
}
|
|
1135
1282
|
// We should never push fast fills involving lite chains here because slow fill requests for them are invalid:
|
|
1136
1283
|
assert(
|
|
1137
|
-
|
|
1138
|
-
"fastFillsReplacingSlowFills should
|
|
1284
|
+
_canCreateSlowFillLeaf(deposits[0]),
|
|
1285
|
+
"fastFillsReplacingSlowFills should contain only deposits that can be slow filled"
|
|
1139
1286
|
);
|
|
1140
1287
|
const destinationBlockRange = getBlockRangeForChain(blockRangesForChains, destinationChainId, chainIds);
|
|
1141
1288
|
if (
|
|
1142
|
-
// If the slow fill request that was replaced by this fill was in an older bundle, then we don't
|
|
1143
|
-
// need to check if the slow fill request was valid since we can assume all bundles in the past
|
|
1144
|
-
// were validated. However, we might as well double check.
|
|
1145
|
-
this.clients.hubPoolClient.areTokensEquivalent(
|
|
1146
|
-
deposit.inputToken,
|
|
1147
|
-
deposit.originChainId,
|
|
1148
|
-
deposit.outputToken,
|
|
1149
|
-
deposit.destinationChainId,
|
|
1150
|
-
deposit.quoteBlockNumber
|
|
1151
|
-
) &&
|
|
1152
1289
|
// If there is a slow fill request in this bundle that matches the relay hash, then there was no slow fill
|
|
1153
1290
|
// created that would be considered excess.
|
|
1154
|
-
|
|
1291
|
+
!slowFillRequest ||
|
|
1292
|
+
slowFillRequest.blockNumber < destinationBlockRange[0]
|
|
1155
1293
|
) {
|
|
1156
|
-
validatedBundleUnexecutableSlowFills.push(
|
|
1294
|
+
validatedBundleUnexecutableSlowFills.push(deposits[0]);
|
|
1157
1295
|
}
|
|
1158
1296
|
});
|
|
1159
1297
|
}
|
|
@@ -1167,10 +1305,14 @@ export class BundleDataClient {
|
|
|
1167
1305
|
// For all deposits older than this bundle, we need to check if they expired in this bundle and if they did,
|
|
1168
1306
|
// whether there was a slow fill created for it in a previous bundle that is now unexecutable and replaced
|
|
1169
1307
|
// by a new expired deposit refund.
|
|
1170
|
-
await forEachAsync(olderDepositHashes, async (
|
|
1171
|
-
const {
|
|
1172
|
-
|
|
1173
|
-
|
|
1308
|
+
await forEachAsync(olderDepositHashes, async (depositHash) => {
|
|
1309
|
+
const { relayDataHash, index } = decodeBundleDepositHash(depositHash);
|
|
1310
|
+
const { deposits, slowFillRequest, fill } = v3RelayHashes[relayDataHash];
|
|
1311
|
+
if (!deposits || deposits.length < 1) {
|
|
1312
|
+
throw new Error("Deposit should exist in relay hash dictionary.");
|
|
1313
|
+
}
|
|
1314
|
+
const deposit = deposits[index];
|
|
1315
|
+
const { destinationChainId } = deposit;
|
|
1174
1316
|
const destinationBlockRange = getBlockRangeForChain(blockRangesForChains, destinationChainId, chainIds);
|
|
1175
1317
|
|
|
1176
1318
|
// Only look for deposits that were mined before this bundle and that are newly expired.
|
|
@@ -1180,7 +1322,7 @@ export class BundleDataClient {
|
|
|
1180
1322
|
// If there is a valid fill that we saw matching this deposit, then it does not need a refund.
|
|
1181
1323
|
!fill &&
|
|
1182
1324
|
isDefined(deposit) && // Needed for TSC - we check this above.
|
|
1183
|
-
deposit
|
|
1325
|
+
_depositIsExpired(deposit) &&
|
|
1184
1326
|
deposit.fillDeadline >= bundleBlockTimestamps[destinationChainId][0] &&
|
|
1185
1327
|
spokePoolClients[destinationChainId] !== undefined
|
|
1186
1328
|
) {
|
|
@@ -1196,8 +1338,7 @@ export class BundleDataClient {
|
|
|
1196
1338
|
// If fill status is RequestedSlowFill, then we might need to mark down an unexecutable
|
|
1197
1339
|
// slow fill that we're going to replace with an expired deposit refund.
|
|
1198
1340
|
// If deposit cannot be slow filled, then exit early.
|
|
1199
|
-
|
|
1200
|
-
if (fillStatus !== FillStatus.RequestedSlowFill || deposit.fromLiteChain || deposit.toLiteChain) {
|
|
1341
|
+
if (fillStatus !== FillStatus.RequestedSlowFill || !_canCreateSlowFillLeaf(deposit)) {
|
|
1201
1342
|
return;
|
|
1202
1343
|
}
|
|
1203
1344
|
// Now, check if there was a slow fill created for this deposit in a previous bundle which would now be
|
|
@@ -1206,21 +1347,9 @@ export class BundleDataClient {
|
|
|
1206
1347
|
|
|
1207
1348
|
// If there is a slow fill request in this bundle, then the expired deposit refund will supercede
|
|
1208
1349
|
// the slow fill request. If there is no slow fill request seen or its older than this bundle, then we can
|
|
1209
|
-
// assume a slow fill leaf was created for it because
|
|
1210
|
-
// also sent before the fill deadline expired since we checked that above.
|
|
1211
|
-
if (
|
|
1212
|
-
// Since this deposit was requested for a slow fill in an older bundle at this point, we don't
|
|
1213
|
-
// technically need to check if the slow fill request was valid since we can assume all bundles in the past
|
|
1214
|
-
// were validated. However, we might as well double check.
|
|
1215
|
-
this.clients.hubPoolClient.areTokensEquivalent(
|
|
1216
|
-
deposit.inputToken,
|
|
1217
|
-
deposit.originChainId,
|
|
1218
|
-
deposit.outputToken,
|
|
1219
|
-
deposit.destinationChainId,
|
|
1220
|
-
deposit.quoteBlockNumber
|
|
1221
|
-
) &&
|
|
1222
|
-
(!slowFillRequest || slowFillRequest.blockNumber < destinationBlockRange[0])
|
|
1223
|
-
) {
|
|
1350
|
+
// assume a slow fill leaf was created for it because of the previous _canCreateSlowFillLeaf check.
|
|
1351
|
+
// The slow fill request was also sent before the fill deadline expired since we checked that above.
|
|
1352
|
+
if (!slowFillRequest || slowFillRequest.blockNumber < destinationBlockRange[0]) {
|
|
1224
1353
|
validatedBundleUnexecutableSlowFills.push(deposit);
|
|
1225
1354
|
}
|
|
1226
1355
|
}
|
|
@@ -1232,7 +1361,7 @@ export class BundleDataClient {
|
|
|
1232
1361
|
validatedBundleV3Fills.length > 0
|
|
1233
1362
|
? this.clients.hubPoolClient.batchComputeRealizedLpFeePct(
|
|
1234
1363
|
validatedBundleV3Fills.map((fill) => {
|
|
1235
|
-
const matchedDeposit = v3RelayHashes[this.getRelayHashFromEvent(fill)].
|
|
1364
|
+
const matchedDeposit = v3RelayHashes[this.getRelayHashFromEvent(fill)].deposits![0];
|
|
1236
1365
|
assert(isDefined(matchedDeposit), "Deposit should exist in relay hash dictionary.");
|
|
1237
1366
|
const { chainToSendRefundTo: paymentChainId } = getRefundInformationFromFill(
|
|
1238
1367
|
fill,
|
|
@@ -1276,7 +1405,7 @@ export class BundleDataClient {
|
|
|
1276
1405
|
});
|
|
1277
1406
|
v3FillLpFees.forEach(({ realizedLpFeePct }, idx) => {
|
|
1278
1407
|
const fill = validatedBundleV3Fills[idx];
|
|
1279
|
-
const associatedDeposit = v3RelayHashes[this.getRelayHashFromEvent(fill)].
|
|
1408
|
+
const associatedDeposit = v3RelayHashes[this.getRelayHashFromEvent(fill)].deposits![0];
|
|
1280
1409
|
assert(isDefined(associatedDeposit), "Deposit should exist in relay hash dictionary.");
|
|
1281
1410
|
const { chainToSendRefundTo, repaymentToken } = getRefundInformationFromFill(
|
|
1282
1411
|
fill,
|
|
@@ -1285,7 +1414,7 @@ export class BundleDataClient {
|
|
|
1285
1414
|
chainIds,
|
|
1286
1415
|
associatedDeposit!.fromLiteChain
|
|
1287
1416
|
);
|
|
1288
|
-
updateBundleFillsV3(bundleFillsV3, fill, realizedLpFeePct, chainToSendRefundTo, repaymentToken);
|
|
1417
|
+
updateBundleFillsV3(bundleFillsV3, fill, realizedLpFeePct, chainToSendRefundTo, repaymentToken, fill.relayer);
|
|
1289
1418
|
});
|
|
1290
1419
|
v3SlowFillLpFees.forEach(({ realizedLpFeePct: lpFeePct }, idx) => {
|
|
1291
1420
|
const deposit = validatedBundleSlowFills[idx];
|
|
@@ -1299,7 +1428,6 @@ export class BundleDataClient {
|
|
|
1299
1428
|
const v3SpokeEventsReadable = prettyPrintV3SpokePoolEvents(
|
|
1300
1429
|
bundleDepositsV3,
|
|
1301
1430
|
bundleFillsV3,
|
|
1302
|
-
bundleInvalidFillsV3,
|
|
1303
1431
|
bundleSlowFillsV3,
|
|
1304
1432
|
expiredDepositsToRefundV3,
|
|
1305
1433
|
unexecutableSlowFills
|
|
@@ -1314,6 +1442,15 @@ export class BundleDataClient {
|
|
|
1314
1442
|
});
|
|
1315
1443
|
}
|
|
1316
1444
|
|
|
1445
|
+
if (bundleUnrepayableFillsV3.length > 0) {
|
|
1446
|
+
this.logger.debug({
|
|
1447
|
+
at: "BundleDataClient#loadData",
|
|
1448
|
+
message: "Finished loading V3 spoke pool data and found some unrepayable V3 fills in range",
|
|
1449
|
+
blockRangesForChains,
|
|
1450
|
+
bundleUnrepayableFillsV3,
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1317
1454
|
this.logger.debug({
|
|
1318
1455
|
at: "BundleDataClient#loadDataFromScratch",
|
|
1319
1456
|
message: `Computed bundle data in ${Math.round(performance.now() - start) / 1000}s.`,
|
|
@@ -1333,8 +1470,24 @@ export class BundleDataClient {
|
|
|
1333
1470
|
// keccak256 hash of the relay data, which can be used as input into the on-chain `fillStatuses()` function in the
|
|
1334
1471
|
// spoke pool contract. However, this internal function is used to uniquely identify a bridging event
|
|
1335
1472
|
// for speed since its easier to build a string from the event data than to hash it.
|
|
1336
|
-
|
|
1337
|
-
return `${event.depositor}-${event.recipient}-${event.exclusiveRelayer}-${event.inputToken}-${event.outputToken}-${
|
|
1473
|
+
protected getRelayHashFromEvent(event: V3DepositWithBlock | V3FillWithBlock | SlowFillRequestWithBlock): string {
|
|
1474
|
+
return `${event.depositor}-${event.recipient}-${event.exclusiveRelayer}-${event.inputToken}-${event.outputToken}-${
|
|
1475
|
+
event.inputAmount
|
|
1476
|
+
}-${event.outputAmount}-${event.originChainId}-${event.depositId.toString()}-${event.fillDeadline}-${
|
|
1477
|
+
event.exclusivityDeadline
|
|
1478
|
+
}-${event.message}-${event.destinationChainId}`;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
protected async findMatchingFillEvent(
|
|
1482
|
+
deposit: DepositWithBlock,
|
|
1483
|
+
spokePoolClient: SpokePoolClient
|
|
1484
|
+
): Promise<FillWithBlock | undefined> {
|
|
1485
|
+
return await findFillEvent(
|
|
1486
|
+
spokePoolClient.spokePool,
|
|
1487
|
+
deposit,
|
|
1488
|
+
spokePoolClient.deploymentBlock,
|
|
1489
|
+
spokePoolClient.latestBlockSearched
|
|
1490
|
+
);
|
|
1338
1491
|
}
|
|
1339
1492
|
|
|
1340
1493
|
async getBundleBlockTimestamps(
|
|
@@ -1362,13 +1515,26 @@ export class BundleDataClient {
|
|
|
1362
1515
|
// will usually be called in production with block ranges that were validated by
|
|
1363
1516
|
// DataworkerUtils.blockRangesAreInvalidForSpokeClients.
|
|
1364
1517
|
const startBlockForChain = Math.min(_startBlockForChain, spokePoolClient.latestBlockSearched);
|
|
1365
|
-
|
|
1366
|
-
|
|
1518
|
+
// @dev Add 1 to the bundle end block. The thinking here is that there can be a gap between
|
|
1519
|
+
// block timestamps in subsequent blocks. The bundle data client assumes that fill deadlines expire
|
|
1520
|
+
// in exactly one bundle, therefore we must make sure that the bundle block timestamp for one bundle's
|
|
1521
|
+
// end block is exactly equal to the bundle block timestamp for the next bundle's start block. This way
|
|
1522
|
+
// there are no gaps in block timestamps between bundles.
|
|
1523
|
+
const endBlockForChain = Math.min(_endBlockForChain + 1, spokePoolClient.latestBlockSearched);
|
|
1524
|
+
const [startTime, _endTime] = [
|
|
1367
1525
|
await spokePoolClient.getTimestampForBlock(startBlockForChain),
|
|
1368
1526
|
await spokePoolClient.getTimestampForBlock(endBlockForChain),
|
|
1369
1527
|
];
|
|
1528
|
+
// @dev similar to reasoning above to ensure no gaps between bundle block range timestamps and also
|
|
1529
|
+
// no overlap, subtract 1 from the end time.
|
|
1530
|
+
const endBlockDelta = endBlockForChain > startBlockForChain ? 1 : 0;
|
|
1531
|
+
const endTime = Math.max(0, _endTime - endBlockDelta);
|
|
1532
|
+
|
|
1370
1533
|
// Sanity checks:
|
|
1371
|
-
assert(
|
|
1534
|
+
assert(
|
|
1535
|
+
endTime >= startTime,
|
|
1536
|
+
`End time for block ${endBlockForChain} should be greater than start time for block ${startBlockForChain}: ${endTime} >= ${startTime}.`
|
|
1537
|
+
);
|
|
1372
1538
|
assert(
|
|
1373
1539
|
startBlockForChain === 0 || startTime > 0,
|
|
1374
1540
|
"Start timestamp must be greater than 0 if the start block is greater than 0."
|