Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(BundleDataClient): Support refunds for pre-fills/slow-fill-requests and duplicate deposits #835

Merged
merged 118 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
2b647e5
feat(BundleDataClient): Support refunds for pre-fills and fills for p…
nicholaspai Jan 23, 2025
5d81719
re-use v3Relayhashes to get all deposits
nicholaspai Jan 23, 2025
b6053c7
Update src/clients/BundleDataClient/BundleDataClient.ts
nicholaspai Jan 23, 2025
f91d4dd
Update BundleDataClient.ts
nicholaspai Jan 23, 2025
20325f1
Update BundleDataClient.ts
nicholaspai Jan 23, 2025
8ed54b8
Update BundleDataClient.ts
nicholaspai Jan 23, 2025
d3be312
feat: Add findFillEvent utility function (#836)
nicholaspai Jan 23, 2025
23d9a6e
Update BundleDataClient.ts
nicholaspai Jan 23, 2025
80e5b1c
feat(BundleDataClient): Support duplicate expired deposit refunds
nicholaspai Jan 23, 2025
c608499
Handle duplicate deposits
nicholaspai Jan 23, 2025
81f3e51
Merge branch 'pre-fills' into duplicate-deposit-refunds
nicholaspai Jan 23, 2025
9cbded7
Update SpokePoolClient.ts
nicholaspai Jan 23, 2025
828c55e
Fix duplicate deposit logic
nicholaspai Jan 24, 2025
45e75eb
Add caveats about duplicate deposits
nicholaspai Jan 24, 2025
c9184e4
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
c3bb4bb
Merge branch 'pre-fills' into duplicate-deposit-refunds
nicholaspai Jan 24, 2025
886a5e0
Handle expired deposits better
nicholaspai Jan 24, 2025
a7b5f36
Merge branch 'pre-fills' into duplicate-deposit-refunds
nicholaspai Jan 24, 2025
1fc293a
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
6569e60
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
774b88a
Merge branch 'pre-fills' into duplicate-deposit-refunds
nicholaspai Jan 24, 2025
09d55f8
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
29be548
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
75ba384
Fix expired deposit loigc
nicholaspai Jan 24, 2025
f411d4c
Merge branch 'pre-fills' into duplicate-deposit-refunds
nicholaspai Jan 24, 2025
130992e
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
15ebfea
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
a798405
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
768b5d6
fix pre slow fill request handling
nicholaspai Jan 24, 2025
945defd
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
4d34e0d
Update BundleDataClient.ts
nicholaspai Jan 24, 2025
d8a1881
Update package.json
nicholaspai Jan 24, 2025
719c29c
adjust for empty message hash
nicholaspai Jan 24, 2025
b4d85d7
add isZeroValueFillOrSlowFillRequest
nicholaspai Jan 24, 2025
6247de9
Merge branch 'master' into pre-fills
nicholaspai Jan 24, 2025
70bf908
fix
nicholaspai Jan 24, 2025
2366769
Update package.json
nicholaspai Jan 24, 2025
e6f4996
use isMessageEmpty in isFillOrSlowFillRequestMessageEmpty
nicholaspai Jan 24, 2025
2d83142
Update SpokePoolClient.ValidateFill.ts
nicholaspai Jan 24, 2025
dde1130
Refactor and change up conditionals for marginal speedups
nicholaspai Jan 26, 2025
d18e6a6
Update BundleDataClient.ts
nicholaspai Jan 26, 2025
f32cdb4
Merge branch 'master' into pre-fills
nicholaspai Jan 27, 2025
45441a1
Update SpokePoolClient.fills.ts
nicholaspai Jan 27, 2025
461db1c
refactor
nicholaspai Jan 27, 2025
ebcaf16
Update package.json
nicholaspai Jan 27, 2025
38a6fc7
merge
nicholaspai Jan 27, 2025
1924d76
Update package.json
nicholaspai Jan 27, 2025
bd9bd0b
Update MockSpokePoolClient.ts
nicholaspai Jan 27, 2025
2e3f0be
Update package.json
nicholaspai Jan 27, 2025
7b3170d
Update BundleDataClient.ts
nicholaspai Jan 28, 2025
36f0836
Gatekeep behind version bump
nicholaspai Jan 28, 2025
de4f3f8
Update BundleDataClient.ts
nicholaspai Jan 28, 2025
9488ecf
fix
nicholaspai Jan 29, 2025
a6b236d
fix toggle
nicholaspai Jan 29, 2025
4f0f42e
meerge
nicholaspai Jan 29, 2025
5a32e81
Update BundleDataClient.ts
nicholaspai Jan 29, 2025
1ec42b5
Add verifyFill check to pre-fillr efund
nicholaspai Jan 29, 2025
7bdc4a1
Update src/clients/BundleDataClient/BundleDataClient.ts
nicholaspai Jan 29, 2025
5bf0ad2
Update BundleDataClient.ts
nicholaspai Jan 29, 2025
68f50a4
Update package.json
nicholaspai Jan 29, 2025
57a2a9f
fix(BundleDataClient): Make sure bundle block timestamps have no gaps
nicholaspai Jan 29, 2025
01343f4
Update package.json
nicholaspai Jan 29, 2025
b98d74a
Merge branch 'bundle-block-timestamps' into pre-fills
nicholaspai Jan 29, 2025
6ad7121
Revert "Merge branch 'bundle-block-timestamps' into pre-fills"
nicholaspai Jan 29, 2025
6001a8c
Update package.json
nicholaspai Jan 29, 2025
6e90289
Update BundleDataClient.ts
nicholaspai Jan 29, 2025
3058a4c
Update BundleDataClient.ts
nicholaspai Jan 29, 2025
bc5e5a6
Update BundleDataClient.ts
nicholaspai Jan 29, 2025
89595e9
Update package.json
nicholaspai Jan 29, 2025
5aae5fa
Update BundleDataClient.ts
nicholaspai Jan 29, 2025
7ab59e3
Update package.json
nicholaspai Jan 29, 2025
d251e09
fix
nicholaspai Jan 29, 2025
33e6ae2
fix
nicholaspai Jan 29, 2025
929eac8
Update BundleDataClient.ts
nicholaspai Jan 30, 2025
a1eb56b
Merge branch 'bundle-block-timestamps' into pre-fills
nicholaspai Jan 30, 2025
b19482a
Update package.json
nicholaspai Jan 30, 2025
3153720
Merge branch 'master' into pre-fills
nicholaspai Jan 30, 2025
2349b73
Update package.json
nicholaspai Jan 30, 2025
79f13f2
Add case work
nicholaspai Jan 30, 2025
d8b439e
Update package.json
nicholaspai Jan 30, 2025
52090fb
Update BundleDataClient.ts
nicholaspai Jan 30, 2025
709952b
Refund duplicate deposits
nicholaspai Jan 30, 2025
4a520a6
Update package.json
nicholaspai Jan 30, 2025
ed8f6d2
Merge branch 'master' into pre-fills
nicholaspai Jan 31, 2025
0b5a43d
Update package.json
nicholaspai Jan 31, 2025
0260489
fix
nicholaspai Jan 31, 2025
8aa3835
use ZERO_BYTES
nicholaspai Jan 31, 2025
dc6d482
Remove isSlowFill check
nicholaspai Jan 31, 2025
9ebd0f2
Update package.json
nicholaspai Jan 31, 2025
d1eafe5
Revert "Remove isSlowFill check"
nicholaspai Jan 31, 2025
3479dae
Re-add slow fill check and refund to depositor
nicholaspai Jan 31, 2025
6ffd1a0
Update BundleDataClient.ts
nicholaspai Jan 31, 2025
5ada1fb
Remove duplicate deposit refunds and revert back to refunding pre-fills
nicholaspai Jan 31, 2025
e1d60a6
wip
nicholaspai Jan 31, 2025
f0544d4
fix
nicholaspai Jan 31, 2025
3fc8262
Update package.json
nicholaspai Jan 31, 2025
56ea588
Make sure any time we queryHistoricalDepositForFill we also check the…
nicholaspai Jan 31, 2025
b63cf9a
Update package.json
nicholaspai Jan 31, 2025
e5cd418
Update BundleDataClient.ts
nicholaspai Jan 31, 2025
5b6eeeb
Update package.json
nicholaspai Jan 31, 2025
6def4c0
Pay duplicate deposits to filler or depositor
nicholaspai Jan 31, 2025
de176c7
Update BundleDataClient.ts
nicholaspai Jan 31, 2025
283a8e9
Update package.json
nicholaspai Jan 31, 2025
49560fb
Update BundleDataClient.ts
nicholaspai Jan 31, 2025
84f61ad
Update BundleDataClient.ts
nicholaspai Jan 31, 2025
573d00f
add comments about matching first slow fill leaf
nicholaspai Jan 31, 2025
54f28e4
Update BundleDataClient.ts
nicholaspai Jan 31, 2025
f853323
Merge branch 'master' into pre-fills
nicholaspai Jan 31, 2025
87cb051
Add verifyFillRepayment check to prefill loop
nicholaspai Feb 1, 2025
6e6488c
Add extra verifyFillRepayment checks
nicholaspai Feb 1, 2025
7bcc855
lint
nicholaspai Feb 1, 2025
1bcd9c5
Add asserts
nicholaspai Feb 2, 2025
5092f14
Simplify code by removing out of date comments and other things
nicholaspai Feb 3, 2025
a2251a0
Add assert
nicholaspai Feb 3, 2025
cc9ebfc
Update BundleDataClient.ts
nicholaspai Feb 3, 2025
5c32bd8
Update BundleDataClient.ts
nicholaspai Feb 3, 2025
7eb7ede
Update package.json
nicholaspai Feb 3, 2025
d614976
Update package.json
nicholaspai Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 137 additions & 73 deletions src/clients/BundleDataClient/BundleDataClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
ExpiredDepositsToRefundV3,
Clients,
CombinedRefunds,
FillWithBlock,
Deposit,
DepositWithBlock,
} from "../../interfaces";
import { AcrossConfigStoreClient, SpokePoolClient } from "..";
import {
Expand All @@ -32,6 +35,7 @@ import {
mapAsync,
bnUint32Max,
isZeroValueDeposit,
findFillEvent,
} from "../../utils";
import winston from "winston";
import {
Expand Down Expand Up @@ -665,6 +669,37 @@ export class BundleDataClient {
return isChainDisabled(blockRangeForChain);
};

const _canCreateSlowFillLeaf = (deposit: DepositWithBlock): boolean => {
return (
// Cannot slow fill when input and output tokens are not equivalent.
this.clients.hubPoolClient.areTokensEquivalent(
deposit.inputToken,
deposit.originChainId,
deposit.outputToken,
deposit.destinationChainId,
deposit.quoteBlockNumber
) &&
// Cannot slow fill from or to a lite chain.
!deposit.fromLiteChain &&
!deposit.toLiteChain &&
// Deposit must not expire during this bundle.
deposit.fillDeadline >= bundleBlockTimestamps[deposit.destinationChainId][1]
);
};

const _getFillStatusForDeposit = (deposit: Deposit, queryBlock: number): Promise<FillStatus> => {
return spokePoolClients[deposit.destinationChainId].relayFillStatus(
deposit,
// We can assume that in production
// the block to query is not one that the spoke pool client
// hasn't queried. This is because this function will usually be called
// in production with block ranges that were validated by
// DataworkerUtils.blockRangesAreInvalidForSpokeClients.
Math.min(queryBlock, spokePoolClients[deposit.destinationChainId].latestBlockSearched),
deposit.destinationChainId
);
};

// Infer chain ID's to load from number of block ranges passed in.
const allChainIds = blockRangesForChains
.map((_blockRange, index) => chainIds[index])
Expand Down Expand Up @@ -779,7 +814,10 @@ export class BundleDataClient {
continue;
}
originClient.getDepositsForDestinationChain(destinationChainId).forEach((deposit) => {
if (isZeroValueDeposit(deposit)) {
// Only evaluate deposits that are in this bundle or in previous bundles. This means we cannot issue fill
// refunds or slow fills here for deposits that are in future bundles (i.e. "pre-fills"). Instead, we'll
// evaluate these pre-fills once the deposit is inside the "current" bundle block range.
if (isZeroValueDeposit(deposit) || deposit.blockNumber > originChainBlockRange[1]) {
return;
}
depositCounter++;
Expand Down Expand Up @@ -809,7 +847,7 @@ export class BundleDataClient {
// If deposit is in bundle and it has expired, additionally save it as an expired deposit.
// If deposit is not in the bundle block range, then save it as an older deposit that
// may have expired.
if (deposit.blockNumber >= originChainBlockRange[0] && deposit.blockNumber <= originChainBlockRange[1]) {
if (deposit.blockNumber >= originChainBlockRange[0]) {
// Deposit is a V3 deposit in this origin chain's bundle block range and is not a duplicate.
updateBundleDepositsV3(bundleDepositsV3, deposit);
// We don't check that fillDeadline >= bundleBlockTimestamps[destinationChainId][0] because
Expand Down Expand Up @@ -968,36 +1006,14 @@ export class BundleDataClient {
);
// The ! is safe here because we've already checked that the deposit exists in the relay hash dictionary.
const matchedDeposit = v3RelayHashes[relayDataHash].deposit!;

// Input and Output tokens must be equivalent on the deposit for this to be slow filled.
if (
!this.clients.hubPoolClient.areTokensEquivalent(
matchedDeposit.inputToken,
matchedDeposit.originChainId,
matchedDeposit.outputToken,
matchedDeposit.destinationChainId,
matchedDeposit.quoteBlockNumber
)
) {
return;
}

// slow fill requests for deposits from or to lite chains are considered invalid
if (
v3RelayHashes[relayDataHash].deposit?.fromLiteChain ||
v3RelayHashes[relayDataHash].deposit?.toLiteChain
) {
if (!_canCreateSlowFillLeaf(matchedDeposit)) {
return;
}

// If there is no fill matching the relay hash, then this might be a valid slow fill request
// that we should produce a slow fill leaf for. Check if the slow fill request is in the
// destination chain block range and that the underlying deposit has not expired yet.
if (
slowFillRequest.blockNumber >= destinationChainBlockRange[0] &&
// Deposit must not have expired in this bundle.
slowFillRequest.fillDeadline >= bundleBlockTimestamps[destinationChainId][1]
) {
// destination chain block range.
if (slowFillRequest.blockNumber >= destinationChainBlockRange[0]) {
// At this point, the v3RelayHashes entry already existed meaning that there is a matching deposit,
// so this slow fill request relay data is correct.
validatedBundleSlowFills.push(matchedDeposit);
Expand Down Expand Up @@ -1043,37 +1059,106 @@ export class BundleDataClient {
this.getRelayHashFromEvent(matchedDeposit) === relayDataHash,
"Deposit relay hashes should match."
);
v3RelayHashes[relayDataHash].deposit = matchedDeposit;

// slow fill requests for deposits from or to lite chains are considered invalid
if (matchedDeposit.fromLiteChain || matchedDeposit.toLiteChain) {
if (!_canCreateSlowFillLeaf(matchedDeposit)) {
return;
}

v3RelayHashes[relayDataHash].deposit = matchedDeposit;

// Note: we don't need to query for a historical fill at this point because a fill
// cannot precede a slow fill request and if the fill came after the slow fill request,
// we would have seen it already because we would have processed it in the loop above.
if (
// Input and Output tokens must be equivalent on the deposit for this to be slow filled.
!this.clients.hubPoolClient.areTokensEquivalent(
matchedDeposit.inputToken,
matchedDeposit.originChainId,
matchedDeposit.outputToken,
matchedDeposit.destinationChainId,
matchedDeposit.quoteBlockNumber
) ||
// Deposit must not have expired in this bundle.
slowFillRequest.fillDeadline < bundleBlockTimestamps[destinationChainId][1]
) {
// TODO: Invalid slow fill request. Maybe worth logging.
return;
}
validatedBundleSlowFills.push(matchedDeposit);
}
}
);

// The above loops for adding deposits, fills, and then slow fill requests to the relay hash dictionary
// ignore any events that are after the bundle block range. Because of that, we should consider that there
// are deposits in this bundle that correspond to fills that were sent in a prior bundle that have not
// yet been refunded. These fills are also known as "pre-fills" from here on.
const originBlockRange = getBlockRangeForChain(blockRangesForChains, originChainId, chainIds);

await mapAsync(
Object.values(v3RelayHashes).filter(
({ deposit }) =>
deposit &&
deposit.originChainId === originChainId &&
deposit.destinationChainId === destinationChainId &&
deposit.blockNumber >= originBlockRange[0] &&
deposit.blockNumber <= originBlockRange[1] &&
!isZeroValueDeposit(deposit)
),
async ({ deposit, fill, slowFillRequest }) => {
if (!deposit) throw new Error("Deposit should exist in relay hash dictionary.");
// We don't check the deposit's fillDeadline here because we are ok if the deposit expires in this bundle
// and we issue an expiry refund for it. This expired deposit could also have been pre-filled and we just
// want to make sure in this code block that all valid pre-fills get refunded once the deposit appears.
// If a pre-fill gets refunded and its deposit expired and gets refunded as well, then there is no net loss
// to the protocol.

// If fill exists in memory, then the only case in which we need to create a refund is if the
// the fill occurred in a previous bundle.
if (fill) {
if (!isSlowFill(fill) && fill.blockNumber < destinationChainBlockRange[0]) {
// If fill is in the current bundle then we can assume there is already a refund for it, so only
// include this pre fill if the fill is in an older bundle. If fill is after this current bundle, then
// we won't consider it, following the previous treatment of fills after the bundle block range.
validatedBundleV3Fills.push({
...fill,
quoteTimestamp: deposit.quoteTimestamp,
});
}
return;
}

// If fill does not exist in memory but there is a slow fill request in memory, then we need to issue a
// slow fill leaf for the deposit. We can assume there was no fill preceding the slow fill request because
// slow fill requests cannot follow fills. If there were a fill following this request, we would have
// entered the above case. Again as with pre-fills, we should only consider slow fill requests that were
// in previous bundles.
if (slowFillRequest) {
if (_canCreateSlowFillLeaf(deposit) && slowFillRequest.blockNumber < destinationChainBlockRange[0]) {
validatedBundleSlowFills.push(deposit);
}
return;
}

// So at this point in the code, there is no fill or slow fill request in memory for this deposit.
// We need to check its fill status on-chain to figure out whether to issue a refund or a slow fill leaf.
// We can assume at this point that all fills or slow fill requests, if found, were in previous bundles
// because the spoke pool client lookback would have returned this entire bundle of events and stored
// them into the relay hash dictionary.
const fillStatus = await _getFillStatusForDeposit(deposit, destinationChainBlockRange[1]);

// If deposit was filled, then we need to issue a refund for it.
if (fillStatus === FillStatus.Filled) {
// We need to find the fill event to issue a refund to the right relayer and repayment chain,
// or msg.sender if relayer address is invalid for the repayment chain.
const prefill = (await findFillEvent(
destinationClient.spokePool,
deposit,
destinationClient.deploymentBlock,
destinationClient.latestBlockSearched
)) as unknown as FillWithBlock;
if (!isSlowFill(prefill)) {
validatedBundleV3Fills.push({
...prefill,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to push verifiedFill here in case the relayer repayment address changed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep realized this while adding a unit test

quoteTimestamp: deposit.quoteTimestamp,
});
}
}
// If slow fill requested, then issue a slow fill leaf for the deposit.
else if (fillStatus === FillStatus.RequestedSlowFill) {
// Input and Output tokens must be equivalent on the deposit for this to be slow filled.
// Slow fill requests for deposits from or to lite chains are considered invalid
if (_canCreateSlowFillLeaf(deposit)) {
validatedBundleSlowFills.push(deposit);
}
}
}
);

// For all fills that came after a slow fill request, we can now check if the slow fill request
// was a valid one and whether it was created in a previous bundle. If so, then it created a slow fill
// leaf that is now unexecutable.
Expand Down Expand Up @@ -1119,23 +1204,11 @@ export class BundleDataClient {
});
start = performance.now();

// Go through expired deposits in this bundle and now prune those that we have seen a fill for to construct
// the list of expired deposits we need to refund in this bundle.
expiredBundleDepositHashes.forEach((relayDataHash) => {
const { deposit, fill } = v3RelayHashes[relayDataHash];
assert(isDefined(deposit), "Deposit should exist in relay hash dictionary.");
if (
!fill &&
isDefined(deposit) // Needed for TSC - we check this above.
) {
updateExpiredDepositsV3(expiredDepositsToRefundV3, deposit);
}
});

// For all deposits older than this bundle, we need to check if they expired in this bundle and if they did,
// whether there was a slow fill created for it in a previous bundle that is now unexecutable and replaced
// by a new expired deposit refund.
await forEachAsync(Array.from(olderDepositHashes), async (relayDataHash) => {
// Add any newly expired deposits to the list of expired deposits to refund.
// For these refunds, we need to check whether there was a slow fill created for it in a previous bundle
// that is now unexecutable and replaced by a new expired deposit refund.
const possibleExpiredDeposits = Array.from(olderDepositHashes).concat(Array.from(expiredBundleDepositHashes));
await forEachAsync(possibleExpiredDeposits, async (relayDataHash) => {
const { deposit, slowFillRequest, fill } = v3RelayHashes[relayDataHash];
assert(isDefined(deposit), "Deposit should exist in relay hash dictionary.");
const { destinationChainId } = deposit!;
Expand All @@ -1154,16 +1227,7 @@ export class BundleDataClient {
) {
// If we haven't seen a fill matching this deposit, then we need to rule out that it was filled a long time ago
// by checkings its on-chain fill status.
const fillStatus = await spokePoolClients[destinationChainId].relayFillStatus(
deposit,
// We can assume that in production
// the block ranges passed into this function would never contain blocks where the spoke pool client
// hasn't queried. This is because this function will usually be called
// in production with block ranges that were validated by
// DataworkerUtils.blockRangesAreInvalidForSpokeClients
Math.min(destinationBlockRange[1], spokePoolClients[destinationChainId].latestBlockSearched),
destinationChainId
);
const fillStatus = await _getFillStatusForDeposit(deposit, destinationBlockRange[1]);

// If there is no matching fill and the deposit expired in this bundle and the fill status on-chain is not
// Filled, then we can to refund it as an expired deposit.
Expand Down
37 changes: 36 additions & 1 deletion src/utils/SpokeUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import assert from "assert";
import { BytesLike, Contract, PopulatedTransaction, providers, utils as ethersUtils } from "ethers";
import { CHAIN_IDs, MAX_SAFE_DEPOSIT_ID, ZERO_ADDRESS } from "../constants";
import { Deposit, Fill, FillStatus, RelayData, SlowFillRequest } from "../interfaces";
import { Deposit, Fill, FillStatus, FillWithBlock, RelayData, SlowFillRequest } from "../interfaces";
import { SpokePoolClient } from "../clients";
import { chunk } from "./ArrayUtils";
import { BigNumber, toBN } from "./BigNumberUtils";
import { isDefined } from "./TypeGuards";
import { getNetworkName } from "./NetworkUtils";
import { paginatedEventQuery, spreadEventWithBlockNumber } from "./EventUtils";

type BlockTag = providers.BlockTag;

Expand Down Expand Up @@ -380,3 +381,37 @@ export async function findFillBlock(

return lowBlockNumber;
}

export async function findFillEvent(
spokePool: Contract,
relayData: RelayData,
lowBlockNumber: number,
highBlockNumber?: number
): Promise<FillWithBlock | undefined> {
const blockNumber = await findFillBlock(spokePool, relayData, lowBlockNumber, highBlockNumber);
if (!blockNumber) return undefined;
const query = await paginatedEventQuery(
spokePool,
spokePool.filters.FilledV3Relay(null, null, null, null, null, relayData.originChainId, relayData.depositId),
{
fromBlock: blockNumber,
toBlock: blockNumber,
maxBlockLookBack: 0, // We can hardcode this to 0 to instruct paginatedEventQuery to make a single request
// for the same block number.
}
);
if (query.length === 0) throw new Error(`Failed to find fill event at block ${blockNumber}`);
const event = query[0];
// In production the chainId returned from the provider matches 1:1 with the actual chainId. Querying the provider
// object saves an RPC query becasue the chainId is cached by StaticJsonRpcProvider instances. In hre, the SpokePool
// may be configured with a different chainId than what is returned by the provider.
// @todo Sub out actual chain IDs w/ CHAIN_IDs constants
const destinationChainId = Object.values(CHAIN_IDs).includes(relayData.originChainId)
? (await spokePool.provider.getNetwork()).chainId
: Number(await spokePool.chainId());
const fill = {
...spreadEventWithBlockNumber(event),
destinationChainId,
} as FillWithBlock;
return fill;
}
20 changes: 19 additions & 1 deletion test/SpokePoolClient.fills.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import hre from "hardhat";
import { SpokePoolClient } from "../src/clients";
import { Deposit } from "../src/interfaces";
import { bnOne, findFillBlock, getNetworkName } from "../src/utils";
import { bnOne, findFillBlock, findFillEvent, getNetworkName } from "../src/utils";
import { EMPTY_MESSAGE, ZERO_ADDRESS } from "../src/constants";
import { originChainId, destinationChainId } from "./constants";
import {
Expand Down Expand Up @@ -114,6 +114,24 @@ describe("SpokePoolClient: Fills", function () {
expect(fillBlock).to.equal(targetFillBlock);
});

it("Correctly returns the FilledV3Relay event using the relay data", async function () {
const targetDeposit = { ...deposit, depositId: deposit.depositId + 1 };
// Submit multiple fills at the same block:
const startBlock = await spokePool.provider.getBlockNumber();
await fillV3Relay(spokePool, deposit, relayer1);
await fillV3Relay(spokePool, targetDeposit, relayer1);
await fillV3Relay(spokePool, { ...deposit, depositId: deposit.depositId + 2 }, relayer1);
await hre.network.provider.send("evm_mine");

const fill = await findFillEvent(spokePool, targetDeposit, startBlock);
expect(fill).to.not.be.undefined;
expect(fill!.depositId).to.equal(targetDeposit.depositId);

// Looking for a fill can return undefined:
const missingFill = await findFillEvent(spokePool, { ...deposit, depositId: deposit.depositId + 3 }, startBlock);
expect(missingFill).to.be.undefined;
});

it("FilledV3Relay block search: bounds checking", async function () {
const nBlocks = 100;
const startBlock = await spokePool.provider.getBlockNumber();
Expand Down
Loading