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