Skip to content

feat(SvmSpokeUtils): implement findFillEvent #998

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
69 changes: 46 additions & 23 deletions src/arch/svm/SpokeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fetchState, decodeFillStatusAccount } from "@across-protocol/contracts/
import { SvmCpiEventsClient } from "./eventsClient";
import { Deposit, FillStatus, FillWithBlock, RelayData } from "../../interfaces";
import { BigNumber, chainIsSvm, chunk, isUnsafeDepositId } from "../../utils";
import { getFillStatusPda } from "./utils";
import { getFillStatusPda, unwrapEventData } from "./utils";
import { SVMEventNames } from "./types";

type Provider = Rpc<SolanaRpcApi>;
Expand Down Expand Up @@ -224,30 +224,53 @@ export async function fillStatusArray(
}

/**
* Find the block at which a fill was completed.
* @todo After SpokePool upgrade, this function can be simplified to use the FillStatus enum.
* @param spokePool SpokePool contract instance.
* @param relayData Deposit information that is used to complete a fill.
* @param lowBlockNumber The lower bound of the search. Must be bounded by SpokePool deployment.
* @param highBlocknumber Optional upper bound for the search.
* @returns The block number at which the relay was completed, or undefined.
* Finds the `FilledRelay` event for a given deposit within the provided slot range.
*
* @param relayData - Deposit information that is used to complete a fill.
* @param destinationChainId - Destination chain ID (must be an SVM chain).
* @param svmEventsClient - SVM events client instance for querying events.
* @param fromSlot - Starting slot to search.
* @param toSlot (Optional) Ending slot to search. If not provided, the current confirmed slot will be used.
* @returns The fill event with block info, or `undefined` if not found.
*/
export function findFillBlock(
_spokePool: unknown,
_relayData: RelayData,
_lowBlockNumber: number,
_highBlockNumber?: number
): Promise<number | undefined> {
throw new Error("fillStatusArray: not implemented");
}

export function findFillEvent(
_spokePool: unknown,
_relayData: RelayData,
_lowBlockNumber: number,
_highBlockNumber?: number
export async function findFillEvent(
relayData: RelayData,
destinationChainId: number,
svmEventsClient: SvmCpiEventsClient,
fromSlot: number,
toSlot?: number
): Promise<FillWithBlock | undefined> {
throw new Error("fillStatusArray: not implemented");
assert(chainIsSvm(destinationChainId), "Destination chain must be an SVM chain");
toSlot ??= Number(await svmEventsClient.getRpc().getSlot({ commitment: "confirmed" }).send());

// Get fillStatus PDA using relayData
const programId = svmEventsClient.getProgramAddress();
const fillStatusPda = await getFillStatusPda(programId, relayData, destinationChainId);

// Get fill events from fillStatus PDA
const fillEvents = await svmEventsClient.queryDerivedAddressEvents(
SVMEventNames.FilledRelay,
fillStatusPda,
BigInt(fromSlot),
BigInt(toSlot),
{ limit: 10 }
);
assert(fillEvents.length <= 1, `Expected at most one fill event for ${fillStatusPda}, got ${fillEvents.length}`);

if (fillEvents.length > 0) {
const rawFillEvent = fillEvents[0];
const parsedFillEvent = {
transactionHash: rawFillEvent.signature,
blockNumber: Number(rawFillEvent.slot),
transactionIndex: 0,
logIndex: 0,
destinationChainId,
...(unwrapEventData(rawFillEvent.data) as Record<string, unknown>),
} as unknown as FillWithBlock;
return parsedFillEvent;
}

return undefined;
}

async function resolveFillStatusFromPdaEvents(
Expand Down
31 changes: 20 additions & 11 deletions src/clients/BundleDataClient/BundleDataClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
DepositWithBlock,
} from "../../interfaces";
import { SpokePoolClient } from "..";
import { findFillEvent } from "../../arch/evm";
import { findFillEvent as findEvmFillEvent } from "../../arch/evm";
import { findFillEvent as findSvmFillEvent } from "../../arch/svm";
import {
BigNumber,
bnZero,
Expand Down Expand Up @@ -58,7 +59,7 @@ import {
verifyFillRepayment,
} from "./utils";
import { UNDEFINED_MESSAGE_HASH } from "../../constants";
import { isEVMSpokePoolClient } from "../SpokePoolClient";
import { isEVMSpokePoolClient, isSvmSpokePoolClient } from "../SpokePoolClient";

// max(uint256) - 1
export const INFINITE_FILL_DEADLINE = bnUint32Max;
Expand Down Expand Up @@ -1534,16 +1535,24 @@ export class BundleDataClient {
deposit: DepositWithBlock,
spokePoolClient: SpokePoolClient
): Promise<FillWithBlock | undefined> {
if (!isEVMSpokePoolClient(spokePoolClient)) {
// FIXME: Handle non-EVM chains.
throw new Error("Destination chain is not an EVM chain.");
if (isSvmSpokePoolClient(spokePoolClient)) {
return await findSvmFillEvent(
deposit,
spokePoolClient.chainId,
spokePoolClient.svmEventsClient,
spokePoolClient.deploymentBlock,
spokePoolClient.latestHeightSearched
);
} else if (isEVMSpokePoolClient(spokePoolClient)) {
return await findEvmFillEvent(
spokePoolClient.spokePool,
deposit,
spokePoolClient.deploymentBlock,
spokePoolClient.latestHeightSearched
);
} else {
throw new Error("Unsupported spoke pool client type");
}
return await findFillEvent(
spokePoolClient.spokePool,
deposit,
spokePoolClient.deploymentBlock,
spokePoolClient.latestHeightSearched
);
}

async getBundleBlockTimestamps(
Expand Down
2 changes: 1 addition & 1 deletion src/clients/SpokePoolClient/SVMSpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class SvmSpokePoolClient extends SpokePoolClient {
chainId: number,
deploymentSlot: bigint, // Using slot instead of block number for SVM
eventSearchConfig: MakeOptional<EventSearchConfig, "to">,
protected svmEventsClient: SvmCpiEventsClient,
public svmEventsClient: SvmCpiEventsClient,
protected programId: Address,
protected statePda: Address
) {
Expand Down
10 changes: 10 additions & 0 deletions src/clients/SpokePoolClient/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EVMSpokePoolClient } from "./EVMSpokePoolClient";
import { SvmSpokePoolClient } from "./SVMSpokePoolClient";
import { SpokePoolClient } from "./SpokePoolClient";

export { EVMSpokePoolClient } from "./EVMSpokePoolClient";
Expand All @@ -13,3 +14,12 @@ export { SvmSpokePoolClient } from "./SVMSpokePoolClient";
export function isEVMSpokePoolClient(spokePoolClient: SpokePoolClient): spokePoolClient is EVMSpokePoolClient {
return spokePoolClient instanceof EVMSpokePoolClient;
}

/**
* Checks if a SpokePoolClient is an SVMSpokePoolClient.
* @param spokePoolClient The SpokePoolClient to check.
* @returns True if the SpokePoolClient is an SVMSpokePoolClient, false otherwise.
*/
export function isSvmSpokePoolClient(spokePoolClient: SpokePoolClient): spokePoolClient is SvmSpokePoolClient {
return spokePoolClient instanceof SvmSpokePoolClient;
}