Skip to content

Commit 5ac115a

Browse files
feat(SvmSpokeUtils): implement findFillEvent (#998)
1 parent 549fee8 commit 5ac115a

File tree

4 files changed

+77
-35
lines changed

4 files changed

+77
-35
lines changed

src/arch/svm/SpokeUtils.ts

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { fetchState, decodeFillStatusAccount } from "@across-protocol/contracts/
66
import { SvmCpiEventsClient } from "./eventsClient";
77
import { Deposit, FillStatus, FillWithBlock, RelayData } from "../../interfaces";
88
import { BigNumber, chainIsSvm, chunk, isUnsafeDepositId } from "../../utils";
9-
import { getFillStatusPda } from "./utils";
9+
import { getFillStatusPda, unwrapEventData } from "./utils";
1010
import { SVMEventNames } from "./types";
1111

1212
type Provider = Rpc<SolanaRpcApi>;
@@ -224,30 +224,53 @@ export async function fillStatusArray(
224224
}
225225

226226
/**
227-
* Find the block at which a fill was completed.
228-
* @todo After SpokePool upgrade, this function can be simplified to use the FillStatus enum.
229-
* @param spokePool SpokePool contract instance.
230-
* @param relayData Deposit information that is used to complete a fill.
231-
* @param lowBlockNumber The lower bound of the search. Must be bounded by SpokePool deployment.
232-
* @param highBlocknumber Optional upper bound for the search.
233-
* @returns The block number at which the relay was completed, or undefined.
227+
* Finds the `FilledRelay` event for a given deposit within the provided slot range.
228+
*
229+
* @param relayData - Deposit information that is used to complete a fill.
230+
* @param destinationChainId - Destination chain ID (must be an SVM chain).
231+
* @param svmEventsClient - SVM events client instance for querying events.
232+
* @param fromSlot - Starting slot to search.
233+
* @param toSlot (Optional) Ending slot to search. If not provided, the current confirmed slot will be used.
234+
* @returns The fill event with block info, or `undefined` if not found.
234235
*/
235-
export function findFillBlock(
236-
_spokePool: unknown,
237-
_relayData: RelayData,
238-
_lowBlockNumber: number,
239-
_highBlockNumber?: number
240-
): Promise<number | undefined> {
241-
throw new Error("fillStatusArray: not implemented");
242-
}
243-
244-
export function findFillEvent(
245-
_spokePool: unknown,
246-
_relayData: RelayData,
247-
_lowBlockNumber: number,
248-
_highBlockNumber?: number
236+
export async function findFillEvent(
237+
relayData: RelayData,
238+
destinationChainId: number,
239+
svmEventsClient: SvmCpiEventsClient,
240+
fromSlot: number,
241+
toSlot?: number
249242
): Promise<FillWithBlock | undefined> {
250-
throw new Error("fillStatusArray: not implemented");
243+
assert(chainIsSvm(destinationChainId), "Destination chain must be an SVM chain");
244+
toSlot ??= Number(await svmEventsClient.getRpc().getSlot({ commitment: "confirmed" }).send());
245+
246+
// Get fillStatus PDA using relayData
247+
const programId = svmEventsClient.getProgramAddress();
248+
const fillStatusPda = await getFillStatusPda(programId, relayData, destinationChainId);
249+
250+
// Get fill events from fillStatus PDA
251+
const fillEvents = await svmEventsClient.queryDerivedAddressEvents(
252+
SVMEventNames.FilledRelay,
253+
fillStatusPda,
254+
BigInt(fromSlot),
255+
BigInt(toSlot),
256+
{ limit: 10 }
257+
);
258+
assert(fillEvents.length <= 1, `Expected at most one fill event for ${fillStatusPda}, got ${fillEvents.length}`);
259+
260+
if (fillEvents.length > 0) {
261+
const rawFillEvent = fillEvents[0];
262+
const parsedFillEvent = {
263+
transactionHash: rawFillEvent.signature,
264+
blockNumber: Number(rawFillEvent.slot),
265+
transactionIndex: 0,
266+
logIndex: 0,
267+
destinationChainId,
268+
...(unwrapEventData(rawFillEvent.data) as Record<string, unknown>),
269+
} as unknown as FillWithBlock;
270+
return parsedFillEvent;
271+
}
272+
273+
return undefined;
251274
}
252275

253276
async function resolveFillStatusFromPdaEvents(

src/clients/BundleDataClient/BundleDataClient.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {
2020
DepositWithBlock,
2121
} from "../../interfaces";
2222
import { SpokePoolClient } from "..";
23-
import { findFillEvent } from "../../arch/evm";
23+
import { findFillEvent as findEvmFillEvent } from "../../arch/evm";
24+
import { findFillEvent as findSvmFillEvent } from "../../arch/svm";
2425
import {
2526
BigNumber,
2627
bnZero,
@@ -58,7 +59,7 @@ import {
5859
verifyFillRepayment,
5960
} from "./utils";
6061
import { UNDEFINED_MESSAGE_HASH } from "../../constants";
61-
import { isEVMSpokePoolClient } from "../SpokePoolClient";
62+
import { isEVMSpokePoolClient, isSvmSpokePoolClient } from "../SpokePoolClient";
6263

6364
// max(uint256) - 1
6465
export const INFINITE_FILL_DEADLINE = bnUint32Max;
@@ -1534,16 +1535,24 @@ export class BundleDataClient {
15341535
deposit: DepositWithBlock,
15351536
spokePoolClient: SpokePoolClient
15361537
): Promise<FillWithBlock | undefined> {
1537-
if (!isEVMSpokePoolClient(spokePoolClient)) {
1538-
// FIXME: Handle non-EVM chains.
1539-
throw new Error("Destination chain is not an EVM chain.");
1538+
if (isSvmSpokePoolClient(spokePoolClient)) {
1539+
return await findSvmFillEvent(
1540+
deposit,
1541+
spokePoolClient.chainId,
1542+
spokePoolClient.svmEventsClient,
1543+
spokePoolClient.deploymentBlock,
1544+
spokePoolClient.latestHeightSearched
1545+
);
1546+
} else if (isEVMSpokePoolClient(spokePoolClient)) {
1547+
return await findEvmFillEvent(
1548+
spokePoolClient.spokePool,
1549+
deposit,
1550+
spokePoolClient.deploymentBlock,
1551+
spokePoolClient.latestHeightSearched
1552+
);
1553+
} else {
1554+
throw new Error("Unsupported spoke pool client type");
15401555
}
1541-
return await findFillEvent(
1542-
spokePoolClient.spokePool,
1543-
deposit,
1544-
spokePoolClient.deploymentBlock,
1545-
spokePoolClient.latestHeightSearched
1546-
);
15471556
}
15481557

15491558
async getBundleBlockTimestamps(

src/clients/SpokePoolClient/SVMSpokePoolClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class SvmSpokePoolClient extends SpokePoolClient {
3636
chainId: number,
3737
deploymentSlot: bigint, // Using slot instead of block number for SVM
3838
eventSearchConfig: MakeOptional<EventSearchConfig, "to">,
39-
protected svmEventsClient: SvmCpiEventsClient,
39+
public svmEventsClient: SvmCpiEventsClient,
4040
protected programId: Address,
4141
protected statePda: Address
4242
) {

src/clients/SpokePoolClient/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { EVMSpokePoolClient } from "./EVMSpokePoolClient";
2+
import { SvmSpokePoolClient } from "./SVMSpokePoolClient";
23
import { SpokePoolClient } from "./SpokePoolClient";
34

45
export { EVMSpokePoolClient } from "./EVMSpokePoolClient";
@@ -13,3 +14,12 @@ export { SvmSpokePoolClient } from "./SVMSpokePoolClient";
1314
export function isEVMSpokePoolClient(spokePoolClient: SpokePoolClient): spokePoolClient is EVMSpokePoolClient {
1415
return spokePoolClient instanceof EVMSpokePoolClient;
1516
}
17+
18+
/**
19+
* Checks if a SpokePoolClient is an SVMSpokePoolClient.
20+
* @param spokePoolClient The SpokePoolClient to check.
21+
* @returns True if the SpokePoolClient is an SVMSpokePoolClient, false otherwise.
22+
*/
23+
export function isSvmSpokePoolClient(spokePoolClient: SpokePoolClient): spokePoolClient is SvmSpokePoolClient {
24+
return spokePoolClient instanceof SvmSpokePoolClient;
25+
}

0 commit comments

Comments
 (0)