|
| 1 | +import assert from "assert"; |
1 | 2 | import { Rpc, SolanaRpcApi, Address } from "@solana/kit";
|
| 3 | +import { SvmSpokeIdl } from "@across-protocol/contracts"; |
| 4 | +import { fetchState } from "@across-protocol/contracts/dist/src/svm/clients/SvmSpoke"; |
2 | 5 |
|
| 6 | +import { SvmCpiEventsClient } from "./eventsClient"; |
3 | 7 | import { Deposit, FillStatus, FillWithBlock, RelayData } from "../../interfaces";
|
4 |
| -import { BigNumber, isUnsafeDepositId } from "../../utils"; |
5 |
| -import { fetchState } from "@across-protocol/contracts/dist/src/svm/clients/SvmSpoke"; |
| 8 | +import { BigNumber, chainIsSvm, chunk, isUnsafeDepositId } from "../../utils"; |
| 9 | +import { getFillStatusPda } from "./utils"; |
| 10 | +import { SVMEventNames } from "./types"; |
6 | 11 |
|
7 | 12 | type Provider = Rpc<SolanaRpcApi>;
|
8 | 13 |
|
@@ -125,27 +130,86 @@ export function findDepositBlock(
|
125 | 130 | }
|
126 | 131 |
|
127 | 132 | /**
|
128 |
| - * Find the amount filled for a deposit at a particular block. |
| 133 | + * Find the fill status for a deposit at a particular block. |
129 | 134 | * @param spokePool SpokePool contract instance.
|
130 | 135 | * @param relayData Deposit information that is used to complete a fill.
|
131 |
| - * @param blockTag Block tag (numeric or "latest") to query at. |
132 |
| - * @returns The amount filled for the specified deposit at the requested block (or latest). |
| 136 | + * @param fromSlot Slot to start the search at. |
| 137 | + * @param blockTag Slot (numeric or "confirmed") to query at. |
| 138 | + * @returns The fill status for the specified deposit at the requested slot (or at the current confirmed slot). |
133 | 139 | */
|
134 |
| -export function relayFillStatus( |
135 |
| - _spokePool: unknown, |
136 |
| - _relayData: RelayData, |
137 |
| - _blockTag?: number | "latest", |
138 |
| - _destinationChainId?: number |
| 140 | +export async function relayFillStatus( |
| 141 | + programId: Address, |
| 142 | + relayData: RelayData, |
| 143 | + fromSlot: number, |
| 144 | + blockTag: number | "confirmed" = "confirmed", |
| 145 | + destinationChainId: number, |
| 146 | + provider: Provider |
139 | 147 | ): Promise<FillStatus> {
|
140 |
| - throw new Error("relayFillStatus: not implemented"); |
| 148 | + assert(chainIsSvm(destinationChainId), "Destination chain must be an SVM chain"); |
| 149 | + |
| 150 | + // Get fill status PDA using relayData |
| 151 | + const fillStatusPda = await getFillStatusPda(programId, relayData, destinationChainId); |
| 152 | + |
| 153 | + // Set search range |
| 154 | + let toSlot: bigint; |
| 155 | + if (blockTag === "confirmed") { |
| 156 | + toSlot = await provider.getSlot({ commitment: "confirmed" }).send(); |
| 157 | + } else { |
| 158 | + toSlot = BigInt(blockTag); |
| 159 | + } |
| 160 | + |
| 161 | + // Get fill and requested slow fill events from fillStatusPda |
| 162 | + const pdaEventsClient = await SvmCpiEventsClient.createFor(provider, programId, SvmSpokeIdl, fillStatusPda); |
| 163 | + const eventsToQuery = [SVMEventNames.FilledRelay, SVMEventNames.RequestedSlowFill]; |
| 164 | + const relevantEvents = ( |
| 165 | + await Promise.all( |
| 166 | + eventsToQuery.map((eventName) => |
| 167 | + pdaEventsClient.queryDerivedAddressEvents(eventName, BigInt(fromSlot), toSlot, { limit: 50 }) |
| 168 | + ) |
| 169 | + ) |
| 170 | + ).flat(); |
| 171 | + |
| 172 | + if (relevantEvents.length === 0) { |
| 173 | + // No fill or requested slow fill events found for this fill status PDA |
| 174 | + return FillStatus.Unfilled; |
| 175 | + } |
| 176 | + |
| 177 | + // Sort events in ascending order of slot number |
| 178 | + relevantEvents.sort((a, b) => Number(a.slot - b.slot)); |
| 179 | + |
| 180 | + // At this point we have an ordered array of fill and requested slow fill events and since it's not possible to |
| 181 | + // submit a slow fill request once a fill has been submitted, we can use the last event in the sorted list to |
| 182 | + // determine the fill status at the requested block. |
| 183 | + const fillStatusEvent = relevantEvents.pop(); |
| 184 | + switch (fillStatusEvent!.name) { |
| 185 | + case SVMEventNames.FilledRelay: |
| 186 | + return FillStatus.Filled; |
| 187 | + case SVMEventNames.RequestedSlowFill: |
| 188 | + return FillStatus.RequestedSlowFill; |
| 189 | + default: |
| 190 | + throw new Error(`Unexpected event name: ${fillStatusEvent!.name}`); |
| 191 | + } |
141 | 192 | }
|
142 | 193 |
|
143 |
| -export function fillStatusArray( |
144 |
| - _spokePool: unknown, |
145 |
| - _relayData: RelayData[], |
146 |
| - _blockTag = "processed" |
| 194 | +export async function fillStatusArray( |
| 195 | + programId: Address, |
| 196 | + relayData: RelayData[], |
| 197 | + fromSlot: number, |
| 198 | + blockTag: number | "confirmed" = "confirmed", |
| 199 | + destinationChainId: number, |
| 200 | + provider: Provider |
147 | 201 | ): Promise<(FillStatus | undefined)[]> {
|
148 |
| - throw new Error("fillStatusArray: not implemented"); |
| 202 | + assert(chainIsSvm(destinationChainId), "Destination chain must be an SVM chain"); |
| 203 | + const chunkSize = 2; |
| 204 | + const chunkedRelayData = chunk(relayData, chunkSize); |
| 205 | + const results = []; |
| 206 | + for (const chunk of chunkedRelayData) { |
| 207 | + const chunkResults = await Promise.all( |
| 208 | + chunk.map((relayData) => relayFillStatus(programId, relayData, fromSlot, blockTag, destinationChainId, provider)) |
| 209 | + ); |
| 210 | + results.push(...chunkResults); |
| 211 | + } |
| 212 | + return results.flat(); |
149 | 213 | }
|
150 | 214 |
|
151 | 215 | /**
|
|
0 commit comments