Skip to content

Commit 0fc9625

Browse files
authored
test(svm): fill test with kit and lut (#977)
* test(svm): fill test with kit and lut Signed-off-by: Pablo Maldonado <[email protected]> * fix: query events v2 Signed-off-by: Pablo Maldonado <[email protected]> * fix: script Signed-off-by: Pablo Maldonado <[email protected]> * fix: extra describe section Signed-off-by: Pablo Maldonado <[email protected]> * tmp Signed-off-by: Pablo Maldonado <[email protected]> * refactor: rename sendTransactionWithLookupTable Signed-off-by: Pablo Maldonado <[email protected]> * refactor: docstrings Signed-off-by: Pablo Maldonado <[email protected]> * feat: add remaining ac to lut Signed-off-by: Pablo Maldonado <[email protected]> * fix: remove .only from tests Signed-off-by: Pablo Maldonado <[email protected]> --------- Signed-off-by: Pablo Maldonado <[email protected]>
1 parent 01fdb91 commit 0fc9625

13 files changed

+436
-122
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"@openzeppelin/contracts": "4.9.6",
5353
"@openzeppelin/contracts-upgradeable": "4.9.6",
5454
"@scroll-tech/contracts": "^0.1.0",
55+
"@solana-developers/helpers": "^2.4.0",
56+
"@solana-program/address-lookup-table": "^0.7.0",
5557
"@solana-program/token": "^0.5.1",
5658
"@solana/kit": "^2.1.0",
5759
"@solana/spl-token": "^0.4.6",

scripts/svm/findFillStatusFromFillStatusPda.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// This script finds the fillStatus (fillStatus + event) from a provided fillStatusPda.
22
import * as anchor from "@coral-xyz/anchor";
33
import { AnchorProvider, BN, Program } from "@coral-xyz/anchor";
4-
import { address, createSolanaRpc } from "@solana/kit";
4+
import { address, createSolanaRpc, createSolanaRpcSubscriptions } from "@solana/kit";
55
import yargs from "yargs";
66
import { hideBin } from "yargs/helpers";
77
import { SvmSpokeIdl } from "../../src/svm";
@@ -23,10 +23,14 @@ async function findFillStatusFromFillStatusPda(): Promise<void> {
2323
const fillStatusPda = address(resolvedArgv.fillStatusPda);
2424

2525
console.log(`Looking for Fill Event for Fill Status PDA: ${fillStatusPda.toString()}`);
26+
const { rpcEndpoint } = provider.connection;
27+
const rpc = createSolanaRpc(rpcEndpoint);
28+
const rpcSubscriptions = createSolanaRpcSubscriptions(
29+
rpcEndpoint.replace(/^http(s?):\/\//i, (_m, s) => `ws${s ?? ""}://`)
30+
);
2631

27-
const rpc = createSolanaRpc(provider.connection.rpcEndpoint);
2832
const { event, slot } = await readFillEventFromFillStatusPda(
29-
rpc,
33+
{ rpc, rpcSubscriptions },
3034
fillStatusPda,
3135
address(resolvedArgv.programId),
3236
SvmSpokeIdl

scripts/svm/queryEventsV2.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// This script queries the events of the spoke pool and prints them in a human readable format.
22
import { AnchorProvider } from "@coral-xyz/anchor";
3-
import { address, createSolanaRpc } from "@solana/kit";
3+
import { address, createSolanaRpc, createSolanaRpcSubscriptions } from "@solana/kit";
44
import { stringifyCpiEvent } from "../../src/svm/web3-v1";
55
import { SvmSpokeIdl } from "../../src/svm";
66
import yargs from "yargs";
@@ -41,11 +41,18 @@ const argvPromise = yargs(hideBin(process.argv))
4141
async function queryEvents(): Promise<void> {
4242
const argv = await argvPromise;
4343
const eventName = argv.eventName || "any";
44-
const programId = argv.programId;
45-
const rpc = createSolanaRpc(provider.connection.rpcEndpoint);
46-
const events = await readProgramEvents(rpc, address(programId), SvmSpokeIdl, "confirmed");
47-
const filteredEvents = events.filter((event: any) => (eventName == "any" ? true : event.name == eventName));
48-
const formattedEvents = filteredEvents.map((event: any) => stringifyCpiEvent(event));
44+
const { programId } = argv;
45+
const { rpcEndpoint } = provider.connection;
46+
47+
const rpc = createSolanaRpc(rpcEndpoint);
48+
const rpcSubscriptions = createSolanaRpcSubscriptions(
49+
rpcEndpoint.replace(/^http(s?):\/\//i, (_m, s) => `ws${s ?? ""}://`)
50+
);
51+
52+
const events = await readProgramEvents({ rpc, rpcSubscriptions }, address(programId), SvmSpokeIdl, "confirmed");
53+
54+
const filteredEvents = events.filter((e) => (eventName === "any" ? true : e.name === eventName));
55+
const formattedEvents = filteredEvents.map(stringifyCpiEvent);
4956

5057
console.log(JSON.stringify(formattedEvents, null, 2));
5158
}

src/svm/web3-v2/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from "./solanaProgramUtils";
2+
export * from "./transactionUtils";
3+
export * from "./types";

src/svm/web3-v2/solanaProgramUtils.ts

+12-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import { BorshEventCoder, Idl, utils } from "@coral-xyz/anchor";
2-
import web3, {
3-
Address,
4-
Commitment,
5-
GetSignaturesForAddressApi,
6-
GetTransactionApi,
7-
RpcTransport,
8-
Signature,
9-
} from "@solana/kit";
2+
import web3, { Address, Commitment, GetSignaturesForAddressApi, GetTransactionApi, Signature } from "@solana/kit";
3+
import { RpcClient } from "./types";
104

115
type GetTransactionReturnType = ReturnType<GetTransactionApi["getTransaction"]>;
126

@@ -18,7 +12,7 @@ type GetSignaturesForAddressTransaction = ReturnType<GetSignaturesForAddressApi[
1812
* Reads all events for a specific program.
1913
*/
2014
export async function readProgramEvents(
21-
rpc: web3.Rpc<web3.SolanaRpcApiFromTransport<RpcTransport>>,
15+
rpc: RpcClient,
2216
program: Address,
2317
anchorIdl: Idl,
2418
finality: Commitment = "confirmed",
@@ -45,14 +39,14 @@ export async function readProgramEvents(
4539
}
4640

4741
async function searchSignaturesUntilLimit(
48-
rpc: web3.Rpc<web3.SolanaRpcApiFromTransport<RpcTransport>>,
42+
client: RpcClient,
4943
program: Address,
5044
options: GetSignaturesForAddressConfig = { limit: 1000 }
5145
): Promise<GetSignaturesForAddressTransaction[]> {
5246
const allSignatures: GetSignaturesForAddressTransaction[] = [];
5347
// Fetch all signatures in sequential batches
5448
while (true) {
55-
const signatures = await rpc.getSignaturesForAddress(program, options).send();
49+
const signatures = await client.rpc.getSignaturesForAddress(program, options).send();
5650
allSignatures.push(...signatures);
5751

5852
// Update options for the next batch. Set before to the last fetched signature.
@@ -69,13 +63,15 @@ async function searchSignaturesUntilLimit(
6963
* Reads events from a transaction.
7064
*/
7165
export async function readEvents(
72-
rpc: web3.Rpc<web3.SolanaRpcApiFromTransport<RpcTransport>>,
66+
client: RpcClient,
7367
txSignature: Signature,
7468
programId: Address,
7569
programIdl: Idl,
7670
commitment: Commitment = "confirmed"
7771
) {
78-
const txResult = await rpc.getTransaction(txSignature, { commitment, maxSupportedTransactionVersion: 0 }).send();
72+
const txResult = await client.rpc
73+
.getTransaction(txSignature, { commitment, maxSupportedTransactionVersion: 0 })
74+
.send();
7975

8076
if (txResult === null) return [];
8177

@@ -132,16 +128,16 @@ async function processEventFromTx(
132128
* For a given fillStatusPDa & associated spokePool ProgramID, return the fill event.
133129
*/
134130
export async function readFillEventFromFillStatusPda(
135-
rpc: web3.Rpc<web3.SolanaRpcApiFromTransport<RpcTransport>>,
131+
client: RpcClient,
136132
fillStatusPda: Address,
137133
programId: Address,
138134
programIdl: Idl
139135
): Promise<{ event: any; slot: number }> {
140-
const signatures = await searchSignaturesUntilLimit(rpc, fillStatusPda);
136+
const signatures = await searchSignaturesUntilLimit(client, fillStatusPda);
141137
if (signatures.length === 0) return { event: null, slot: 0 };
142138

143139
// The first signature will always be PDA creation, and therefore CPI event carrying signature. Any older signatures
144140
// will therefore be either spam or PDA closure signatures and can be ignored when looking for the fill event.
145-
const events = await readEvents(rpc, signatures[signatures.length - 1].signature, programId, programIdl);
141+
const events = await readEvents(client, signatures[signatures.length - 1].signature, programId, programIdl);
146142
return { event: events[0], slot: Number(signatures[signatures.length - 1].slot) };
147143
}

src/svm/web3-v2/transactionUtils.ts

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {
2+
Address,
3+
AddressesByLookupTableAddress,
4+
appendTransactionMessageInstruction,
5+
appendTransactionMessageInstructions,
6+
Commitment,
7+
CompilableTransactionMessage,
8+
compressTransactionMessageUsingAddressLookupTables as compressTxWithAlt,
9+
createTransactionMessage,
10+
getSignatureFromTransaction,
11+
IInstruction,
12+
KeyPairSigner,
13+
pipe,
14+
sendAndConfirmTransactionFactory,
15+
setTransactionMessageFeePayerSigner,
16+
setTransactionMessageLifetimeUsingBlockhash,
17+
signTransactionMessageWithSigners,
18+
TransactionMessageWithBlockhashLifetime,
19+
TransactionSigner,
20+
} from "@solana/kit";
21+
22+
import {
23+
fetchAddressLookupTable,
24+
findAddressLookupTablePda,
25+
getCreateLookupTableInstructionAsync,
26+
getExtendLookupTableInstruction,
27+
} from "@solana-program/address-lookup-table";
28+
import { RpcClient } from "./types";
29+
30+
/**
31+
* Signs and sends a transaction.
32+
*/
33+
export const signAndSendTransaction = async (
34+
rpcClient: RpcClient,
35+
transactionMessage: CompilableTransactionMessage & TransactionMessageWithBlockhashLifetime,
36+
commitment: Commitment = "confirmed"
37+
) => {
38+
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
39+
const signature = getSignatureFromTransaction(signedTransaction);
40+
await sendAndConfirmTransactionFactory(rpcClient)(signedTransaction, {
41+
commitment,
42+
});
43+
return signature;
44+
};
45+
46+
export const createDefaultTransaction = async (rpcClient: RpcClient, signer: TransactionSigner) => {
47+
const { value: latestBlockhash } = await rpcClient.rpc.getLatestBlockhash().send();
48+
return pipe(
49+
createTransactionMessage({ version: 0 }),
50+
(tx) => setTransactionMessageFeePayerSigner(signer, tx),
51+
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)
52+
);
53+
};
54+
55+
/**
56+
* Sends a transaction with an Address Lookup Table.
57+
*/
58+
export async function sendTransactionWithLookupTable(
59+
client: RpcClient,
60+
payer: KeyPairSigner,
61+
instructions: IInstruction[],
62+
addressesByLookupTableAddress: AddressesByLookupTableAddress
63+
) {
64+
return pipe(
65+
await createDefaultTransaction(client, payer),
66+
(tx) => appendTransactionMessageInstructions(instructions, tx),
67+
(tx) => compressTxWithAlt(tx, addressesByLookupTableAddress),
68+
(tx) => signTransactionMessageWithSigners(tx),
69+
async (tx) => {
70+
const signedTx = await tx;
71+
await sendAndConfirmTransactionFactory(client)(signedTx, {
72+
commitment: "confirmed",
73+
skipPreflight: false,
74+
});
75+
return getSignatureFromTransaction(signedTx);
76+
}
77+
);
78+
}
79+
80+
/**
81+
* Creates an Address Lookup Table.
82+
*/
83+
export async function createLookupTable(client: RpcClient, authority: KeyPairSigner): Promise<Address> {
84+
const recentSlot = await client.rpc.getSlot({ commitment: "finalized" }).send();
85+
86+
const [alt] = await findAddressLookupTablePda({
87+
authority: authority.address,
88+
recentSlot,
89+
});
90+
91+
const createAltIx = await getCreateLookupTableInstructionAsync({
92+
authority,
93+
recentSlot,
94+
});
95+
96+
await pipe(
97+
await createDefaultTransaction(client, authority),
98+
(tx) => appendTransactionMessageInstruction(createAltIx, tx),
99+
(tx) => signAndSendTransaction(client, tx)
100+
);
101+
102+
return alt;
103+
}
104+
105+
/**
106+
* Extends an Address Lookup Table.
107+
*/
108+
export async function extendLookupTable(
109+
client: RpcClient,
110+
authority: KeyPairSigner,
111+
alt: Address,
112+
addresses: Address[]
113+
) {
114+
const extendAltIx = getExtendLookupTableInstruction({
115+
address: alt,
116+
authority,
117+
payer: authority,
118+
addresses,
119+
});
120+
121+
await pipe(
122+
await createDefaultTransaction(client, authority),
123+
(tx) => appendTransactionMessageInstruction(extendAltIx, tx),
124+
(tx) => signAndSendTransaction(client, tx)
125+
);
126+
127+
const altAccount = await fetchAddressLookupTable(client.rpc, alt);
128+
129+
const addressesByLookupTableAddress: AddressesByLookupTableAddress = {};
130+
addressesByLookupTableAddress[alt] = altAccount.data.addresses;
131+
132+
// Delay a second here to let lookup table warm up
133+
await new Promise((resolve) => setTimeout(resolve, 1000));
134+
135+
return addressesByLookupTableAddress;
136+
}

src/svm/web3-v2/types.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {
2+
Rpc,
3+
RpcSubscriptions,
4+
RpcTransport,
5+
SignatureNotificationsApi,
6+
SlotNotificationsApi,
7+
SolanaRpcApiFromTransport,
8+
} from "@solana/kit";
9+
10+
/**
11+
* A client for the Solana RPC.
12+
*/
13+
export type RpcClient = {
14+
rpc: Rpc<SolanaRpcApiFromTransport<RpcTransport>>;
15+
rpcSubscriptions: RpcSubscriptions<SignatureNotificationsApi & SlotNotificationsApi>;
16+
};

test/svm/SvmSpoke.Deposit.ts

+18-5
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,29 @@ import {
2626
} from "@solana/spl-token";
2727
import { Keypair, PublicKey, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
2828
import { BigNumber, ethers } from "ethers";
29-
import { SvmSpokeClient } from "../../src/svm";
29+
import { SvmSpokeClient, createDefaultTransaction, signAndSendTransaction } from "../../src/svm";
3030
import { DepositInput } from "../../src/svm/clients/SvmSpoke";
3131
import { intToU8Array32, readEventsUntilFound, u8Array32ToBigNumber, u8Array32ToInt } from "../../src/svm/web3-v1";
3232
import { DepositDataValues } from "../../src/types/svm";
3333
import { MAX_EXCLUSIVITY_OFFSET_SECONDS } from "../../test-utils";
3434
import { common } from "./SvmSpoke.common";
35-
import { createDefaultSolanaClient, createDefaultTransaction, signAndSendTransaction } from "./utils";
36-
const { provider, connection, program, owner, seedBalance, initializeState, depositData } = common;
37-
const { createRoutePda, getVaultAta, assertSE, assert, getCurrentTime, depositQuoteTimeBuffer, fillDeadlineBuffer } =
38-
common;
35+
import { createDefaultSolanaClient } from "./utils";
36+
const {
37+
provider,
38+
connection,
39+
program,
40+
owner,
41+
seedBalance,
42+
initializeState,
43+
depositData,
44+
createRoutePda,
45+
getVaultAta,
46+
assertSE,
47+
assert,
48+
getCurrentTime,
49+
depositQuoteTimeBuffer,
50+
fillDeadlineBuffer,
51+
} = common;
3952

4053
const maxExclusivityOffsetSeconds = new BN(MAX_EXCLUSIVITY_OFFSET_SECONDS); // 1 year in seconds
4154

0 commit comments

Comments
 (0)