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 all 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@across-protocol/sdk",
"author": "UMA Team",
"version": "3.4.20",
"version": "4.0.0",
"license": "AGPL-3.0",
"homepage": "https://docs.across.to/reference/sdk",
"files": [
Expand Down
637 changes: 377 additions & 260 deletions src/clients/BundleDataClient/BundleDataClient.ts
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

65 changes: 42 additions & 23 deletions src/clients/BundleDataClient/utils/FillUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _ from "lodash";
import { providers } from "ethers";
import { DepositWithBlock, Fill, FillWithBlock } from "../../../interfaces";
import { Deposit, DepositWithBlock, Fill, FillWithBlock } from "../../../interfaces";
import { getBlockRangeForChain, isSlowFill, chainIsEvm, isValidEvmAddress, isDefined } from "../../../utils";
import { HubPoolClient } from "../../HubPoolClient";

Expand Down Expand Up @@ -47,34 +47,51 @@ export function getRefundInformationFromFill(
};
}

export function getRepaymentChainId(fill: Fill, matchedDeposit: Deposit): number {
// Lite chain deposits force repayment on origin chain.
return matchedDeposit.fromLiteChain ? fill.originChainId : fill.repaymentChainId;
}

export function isEvmRepaymentValid(
fill: Fill,
repaymentChainId: number,
possibleRepaymentChainIds: number[] = []
): boolean {
// Slow fills don't result in repayments so they're always valid.
if (isSlowFill(fill)) {
return true;
}
// Return undefined if the requested repayment chain ID is not in a passed in set of eligible chains. This can
// be used by the caller to narrow the chains to those that are not disabled in the config store.
if (possibleRepaymentChainIds.length > 0 && !possibleRepaymentChainIds.includes(repaymentChainId)) {
return false;
}
return chainIsEvm(repaymentChainId) && isValidEvmAddress(fill.relayer);
}

// Verify that a fill sent to an EVM chain has a 20 byte address. If the fill does not, then attempt
// to repay the `msg.sender` of the relay transaction. Otherwise, return undefined.
export async function verifyFillRepayment(
fill: FillWithBlock,
_fill: FillWithBlock,
destinationChainProvider: providers.Provider,
matchedDeposit: DepositWithBlock,
possibleRepaymentChainIds: number[]
possibleRepaymentChainIds: number[] = []
): Promise<FillWithBlock | undefined> {
// Slow fills don't result in repayments so they're always valid.
if (isSlowFill(fill)) {
return fill;
}
// Lite chain deposits force repayment on origin chain.
const repaymentChainId = matchedDeposit.fromLiteChain ? fill.originChainId : fill.repaymentChainId;
// Return undefined if the requested repayment chain ID is not recognized by the hub pool.
if (!possibleRepaymentChainIds.includes(repaymentChainId)) {
return undefined;
}
const updatedFill = _.cloneDeep(fill);
const fill = _.cloneDeep(_fill);

// If the fill requests repayment on a chain where the repayment address is not valid, attempt to find a valid
// repayment address, otherwise return undefined.
const repaymentChainId = getRepaymentChainId(fill, matchedDeposit);
const validEvmRepayment = isEvmRepaymentValid(fill, repaymentChainId, possibleRepaymentChainIds);

// Case 1: repayment chain is an EVM chain but repayment address is not a valid EVM address.
if (chainIsEvm(repaymentChainId) && !isValidEvmAddress(updatedFill.relayer)) {
// Case 1: Repayment chain is EVM and repayment address is valid EVM address.
if (validEvmRepayment) {
return fill;
}
// Case 2: Repayment chain is EVM but repayment address is not a valid EVM address. Attempt to switch repayment
// address to msg.sender of relay transaction.
else if (chainIsEvm(repaymentChainId) && !isValidEvmAddress(fill.relayer)) {
// TODO: Handle case where fill was sent on non-EVM chain, in which case the following call would fail
// or return something unexpected. We'd want to return undefined here.
const fillTransaction = await destinationChainProvider.getTransaction(updatedFill.transactionHash);
const fillTransaction = await destinationChainProvider.getTransaction(fill.transactionHash);
const destinationRelayer = fillTransaction?.from;
// Repayment chain is still an EVM chain, but the msg.sender is a bytes32 address, so the fill is invalid.
if (!isDefined(destinationRelayer) || !isValidEvmAddress(destinationRelayer)) {
Expand All @@ -83,9 +100,11 @@ export async function verifyFillRepayment(
// Otherwise, assume the relayer to be repaid is the msg.sender. We don't need to modify the repayment chain since
// the getTransaction() call would only succeed if the fill was sent on an EVM chain and therefore the msg.sender
// is a valid EVM address and the repayment chain is an EVM chain.
updatedFill.relayer = destinationRelayer;
fill.relayer = destinationRelayer;
return fill;
}
// Case 3: Repayment chain is not an EVM chain, must be invalid.
else {
return undefined;
}

// Case 2: TODO repayment chain is an SVM chain and repayment address is not a valid SVM address.
return updatedFill;
}
32 changes: 31 additions & 1 deletion src/clients/SpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class SpokePoolClient extends BaseAbstractClient {
protected currentTime = 0;
protected oldestTime = 0;
protected depositHashes: { [depositHash: string]: DepositWithBlock } = {};
protected duplicateDepositHashes: { [depositHash: string]: DepositWithBlock[] } = {};
protected depositHashesToFills: { [depositHash: string]: FillWithBlock[] } = {};
protected speedUps: { [depositorAddress: string]: { [depositId: string]: SpeedUpWithBlock[] } } = {};
protected slowFillRequests: { [relayDataHash: string]: SlowFillRequestWithBlock } = {};
Expand Down Expand Up @@ -126,14 +127,42 @@ export class SpokePoolClient extends BaseAbstractClient {
}

/**
* Retrieves a list of deposits from the SpokePool contract destined for the given destination chain ID.
* Retrieves a list of unique deposits from the SpokePool contract destined for the given destination chain ID.
* @param destinationChainId The destination chain ID.
* @returns A list of deposits.
*/
public getDepositsForDestinationChain(destinationChainId: number): DepositWithBlock[] {
return Object.values(this.depositHashes).filter((deposit) => deposit.destinationChainId === destinationChainId);
}

/**
* Retrieves a list of duplicate deposits matching the given deposit's deposit hash.
* @notice A duplicate is considered any deposit sent after the original deposit with the same deposit hash.
* @param deposit The deposit to find duplicates for.
* @returns A list of duplicate deposits. Does NOT include the original deposit
* unless the original deposit is a duplicate.
*/
private _getDuplicateDeposits(deposit: DepositWithBlock): DepositWithBlock[] {
const depositHash = this.getDepositHash(deposit);
return this.duplicateDepositHashes[depositHash] ?? [];
}

/**
* Returns a list of all deposits including any duplicate ones. Designed only to be used in use cases where
* all deposits are required, regardless of duplicates. For example, the Dataworker can use this to refund
* expired deposits including for duplicates.
* @param destinationChainId
* @returns A list of deposits
*/
public getDepositsForDestinationChainWithDuplicates(destinationChainId: number): DepositWithBlock[] {
const deposits = this.getDepositsForDestinationChain(destinationChainId);
const duplicateDeposits = deposits.reduce((acc, deposit) => {
const duplicates = this._getDuplicateDeposits(deposit);
return acc.concat(duplicates);
}, [] as DepositWithBlock[]);
return sortEventsAscendingInPlace(deposits.concat(duplicateDeposits.flat()));
}

/**
* Retrieves a list of deposits from the SpokePool contract that are associated with this spoke pool.
* @returns A list of deposits.
Expand Down Expand Up @@ -579,6 +608,7 @@ export class SpokePoolClient extends BaseAbstractClient {
}

if (this.depositHashes[this.getDepositHash(deposit)] !== undefined) {
assign(this.duplicateDepositHashes, [this.getDepositHash(deposit)], [deposit]);
continue;
}
assign(this.depositHashes, [this.getDepositHash(deposit)], deposit);
Expand Down
1 change: 0 additions & 1 deletion src/clients/mocks/MockSpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ export class MockSpokePoolClient extends SpokePoolClient {
const { blockNumber, transactionIndex } = deposit;
let { depositId, depositor, destinationChainId, inputToken, inputAmount, outputToken, outputAmount } = deposit;
depositId ??= this.numberOfDeposits;
assert(depositId.gte(this.numberOfDeposits), `${depositId.toString()} < ${this.numberOfDeposits}`);
this.numberOfDeposits = depositId.add(bnOne);

destinationChainId ??= random(1, 42161, false);
Expand Down
4 changes: 3 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ export const SECONDS_PER_YEAR = 31557600; // 365.25 days per year.
*/
export const HUBPOOL_CHAIN_ID = 1;

// List of versions where certain UMIP features were deprecated
// List of versions where certain UMIP features were deprecated or activated
export const TRANSFER_THRESHOLD_MAX_CONFIG_STORE_VERSION = 1;

export const PRE_FILL_MIN_CONFIG_STORE_VERSION = 5;

// A hardcoded identifier used, by default, to tag all Arweave records.
export const ARWEAVE_TAG_APP_NAME = "across-protocol";

Expand Down
10 changes: 9 additions & 1 deletion src/utils/DepositUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from "assert";
import { SpokePoolClient } from "../clients";
import { DEFAULT_CACHING_TTL, EMPTY_MESSAGE } from "../constants";
import { DEFAULT_CACHING_TTL, EMPTY_MESSAGE, ZERO_BYTES } from "../constants";
import { CachingMechanismInterface, Deposit, DepositWithBlock, Fill, SlowFillRequest } from "../interfaces";
import { getNetworkName } from "./NetworkUtils";
import { getDepositInCache, getDepositKey, setDepositInCache } from "./CachingUtils";
Expand Down Expand Up @@ -146,6 +146,10 @@ export function isZeroValueDeposit(deposit: Pick<Deposit, "inputAmount" | "messa
return deposit.inputAmount.eq(0) && isMessageEmpty(deposit.message);
}

export function isZeroValueFillOrSlowFillRequest(e: Pick<Fill | SlowFillRequest, "inputAmount" | "message">): boolean {
return e.inputAmount.eq(0) && isFillOrSlowFillRequestMessageEmpty(e.message);
}

/**
* Determines if a message is empty or not.
* @param message The message to check.
Expand All @@ -155,6 +159,10 @@ export function isMessageEmpty(message = EMPTY_MESSAGE): boolean {
return message === "" || message === "0x";
}

export function isFillOrSlowFillRequestMessageEmpty(message: string): boolean {
return isMessageEmpty(message) || message === ZERO_BYTES;
}

/**
* Determines if a deposit was updated via a speed-up transaction.
* @param deposit Deposit to evaluate.
Expand Down
39 changes: 36 additions & 3 deletions src/utils/SpokeUtils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import assert from "assert";
import { BytesLike, Contract, PopulatedTransaction, providers, utils as ethersUtils } from "ethers";
import { CHAIN_IDs, MAX_SAFE_DEPOSIT_ID, ZERO_ADDRESS, ZERO_BYTES } 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, bnOne, bnZero } from "./BigNumberUtils";
import { keccak256 } from "./common";
import { isMessageEmpty } from "./DepositUtils";
import { isDefined } from "./TypeGuards";
import { getNetworkName } from "./NetworkUtils";
import { paginatedEventQuery, spreadEventWithBlockNumber } from "./EventUtils";

type BlockTag = providers.BlockTag;

Expand Down Expand Up @@ -351,12 +352,11 @@ export async function findFillBlock(
): Promise<number | undefined> {
const { provider } = spokePool;
highBlockNumber ??= await provider.getBlockNumber();
assert(highBlockNumber > lowBlockNumber, `Block numbers out of range (${lowBlockNumber} > ${highBlockNumber})`);
assert(highBlockNumber > lowBlockNumber, `Block numbers out of range (${lowBlockNumber} >= ${highBlockNumber})`);

// 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 provider.getNetwork()).chainId
: Number(await spokePool.chainId());
Expand Down Expand Up @@ -399,6 +399,39 @@ 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.
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;
}

// Determines if the input address (either a bytes32 or bytes20) is the zero address.
export function isZeroAddress(address: string): boolean {
return address === ZERO_ADDRESS || address === ZERO_BYTES;
Expand Down
4 changes: 2 additions & 2 deletions test/SpokePoolClient.SpeedUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,13 @@ describe("SpokePoolClient: SpeedUp", function () {
// attributed to the existing deposit.
for (const field of ["originChainId", "depositId", "depositor"]) {
const testOriginChainId = field !== "originChainId" ? originChainId : originChainId + 1;
const testDepositId = field !== "depositId" ? depositId : depositId + 1;
const testDepositId = field !== "depositId" ? depositId : depositId.add(1);
const testDepositor = field !== "depositor" ? depositor : (await ethers.getSigners())[0];
assert.isTrue(field !== "depositor" || testDepositor.address !== depositor.address); // Sanity check

const signature = await getUpdatedV3DepositSignature(
testDepositor,
testDepositId,
testDepositId.toNumber(),
testOriginChainId,
updatedOutputAmount,
updatedRecipient,
Expand Down
4 changes: 2 additions & 2 deletions test/SpokePoolClient.ValidateFill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ describe("SpokePoolClient: Fill Validation", function () {

// Override the first spoke pool deposit ID that the client thinks is available in the contract.
await spokePoolClient1.update();
spokePoolClient1.firstDepositIdForSpokePool = deposit.depositId + 1;
spokePoolClient1.firstDepositIdForSpokePool = deposit.depositId.add(1);
expect(fill.depositId < spokePoolClient1.firstDepositIdForSpokePool).is.true;
const search = await queryHistoricalDepositForFill(spokePoolClient1, fill);

Expand All @@ -636,7 +636,7 @@ describe("SpokePoolClient: Fill Validation", function () {
);

// Override the deposit ID that we are "filling" to be > 1, the latest deposit ID in spoke pool 1.
await fillV3Relay(spokePool_2, { ...deposit, depositId: deposit.depositId + 1 }, relayer);
await fillV3Relay(spokePool_2, { ...deposit, depositId: deposit.depositId.add(1) }, relayer);
await spokePoolClient2.update();
const [fill] = spokePoolClient2.getFills();

Expand Down
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, bnZero, findFillBlock, getNetworkName } from "../src/utils";
import { bnOne, bnZero, 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.add(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.add(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.add(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