import { Blockhash, Keypair, PublicKey, SystemProgram, Transaction, TransactionInstruction, } from "@solana/web3.js"; import { BN } from "bn.js"; import * as borsh from "borsh"; import { assert, expect } from "chai"; import { describe, test } from "mocha"; import { BanksClient, ProgramTestContext, start } from "solana-bankrun"; // This is a helper class to assign properties to the class class Assignable { constructor(properties) { for (const [key, value] of Object.entries(properties)) { this[key] = value; } } } const MyInstruction = { CreateFav: 0, GetFav: 1, } as const; class CreateFav extends Assignable { number: number; instruction: MyInstruction; color: string; hobbies: string[]; toBuffer() { return Buffer.from(borsh.serialize(CreateNewAccountSchema, this)); } static fromBuffer(buffer: Buffer): CreateFav { return borsh.deserialize( { struct: { number: "u64", color: "string", hobbies: { array: { type: "string", }, }, }, }, buffer, ) as CreateFav; } } const CreateNewAccountSchema = { struct: { instruction: "u8", number: "u64", color: "string", hobbies: { array: { type: "string", }, }, }, }; class GetFav extends Assignable { toBuffer() { return Buffer.from(borsh.serialize(GetFavSchema, this)); } } const GetFavSchema = { struct: { instruction: "u8", }, }; describe("Favorites Solana Native", () => { // Randomly generate the program keypair and load the program to solana-bankrun const programId = PublicKey.unique(); let context: ProgramTestContext; let client: BanksClient; let payer: Keypair; let blockhash: Blockhash; beforeEach(async () => { context = await start([{ name: "favorites_native", programId }], []); client = context.banksClient; // Get the payer keypair from the context, this will be used to sign transactions with enough lamports payer = context.payer; blockhash = context.lastBlockhash; }); test("Set the favorite pda and cross-check the updated data", async () => { const favoritesPda = PublicKey.findProgramAddressSync( [Buffer.from("favorite"), payer.publicKey.toBuffer()], programId, )[0]; const favData = { instruction: MyInstruction.CreateFav, number: 42, color: "blue", hobbies: ["coding", "reading", "traveling"], }; const favorites = new CreateFav(favData); const ix = new TransactionInstruction({ keys: [ { pubkey: payer.publicKey, isSigner: true, isWritable: true }, { pubkey: favoritesPda, isSigner: false, isWritable: true }, { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, ], programId, data: favorites.toBuffer(), }); const tx = new Transaction().add(ix); tx.feePayer = payer.publicKey; tx.recentBlockhash = blockhash; tx.sign(payer); tx.recentBlockhash = blockhash; await client.processTransaction(tx); const account = await client.getAccount(favoritesPda); const data = Buffer.from(account.data); const favoritesData = CreateFav.fromBuffer(data); console.log("Deserialized data:", favoritesData); expect(new BN(favoritesData.number as any, "le").toNumber()).to.equal( favData.number, ); expect(favoritesData.color).to.equal(favData.color); expect(favoritesData.hobbies).to.deep.equal(favData.hobbies); }); test("Check if the test fails if the pda seeds aren't same", async () => { // We put the wrong seeds knowingly to see if the test fails because of checks const favoritesPda = PublicKey.findProgramAddressSync( [Buffer.from("favorite"), payer.publicKey.toBuffer()], programId, )[0]; const favData = { instruction: MyInstruction.CreateFav, number: 42, color: "blue", hobbies: ["coding", "reading", "traveling"], }; const favorites = new CreateFav(favData); const ix = new TransactionInstruction({ keys: [ { pubkey: payer.publicKey, isSigner: true, isWritable: true }, { pubkey: favoritesPda, isSigner: false, isWritable: true }, { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, ], programId, data: favorites.toBuffer(), }); const tx = new Transaction().add(ix); tx.feePayer = payer.publicKey; tx.recentBlockhash = blockhash; tx.sign(payer); tx.recentBlockhash = blockhash; try { await client.processTransaction(tx); console.error("Expected the test to fail"); } catch (_err) { assert(true); } }); test("Get the favorite pda and cross-check the data", async () => { // Creating a new account with payer's pubkey const favoritesPda = PublicKey.findProgramAddressSync( [Buffer.from("favorite"), payer.publicKey.toBuffer()], programId, )[0]; const favData = { instruction: MyInstruction.CreateFav, number: 42, color: "hazel", hobbies: ["singing", "dancing", "skydiving"], }; const favorites = new CreateFav(favData); const ix = new TransactionInstruction({ keys: [ { pubkey: payer.publicKey, isSigner: true, isWritable: true }, { pubkey: favoritesPda, isSigner: false, isWritable: true }, { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, ], programId, data: favorites.toBuffer(), }); const tx1 = new Transaction().add(ix); tx1.feePayer = payer.publicKey; tx1.recentBlockhash = blockhash; tx1.sign(payer); tx1.recentBlockhash = blockhash; await client.processTransaction(tx1); // Getting the user's data through the get_pda instruction const getfavData = { instruction: MyInstruction.GetFav }; const getfavorites = new GetFav(getfavData); const ix2 = new TransactionInstruction({ keys: [ { pubkey: payer.publicKey, isSigner: true, isWritable: true }, { pubkey: favoritesPda, isSigner: false, isWritable: false }, ], programId, data: getfavorites.toBuffer(), }); const tx = new Transaction().add(ix2); tx.feePayer = payer.publicKey; tx.recentBlockhash = blockhash; tx.sign(payer); tx.recentBlockhash = blockhash; await client.processTransaction(tx); }); });