From 978e1b6f14daf2ece64991f825a004fdbc80e8e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 22:49:35 +0000 Subject: [PATCH] Use solana-kite in the compression tests These three were the only Anchor projects not using solana-kite, and each had hand-rolled a `send` helper that is kite's `send_transaction_from_instructions` statement for statement: build a Message, take the blockhash, sign, send, map the result. Three copies of a function already available from a dependency the other 53 projects use. Wallet setup goes the same way: `Keypair::new()` followed by `svm.airdrop(...)` is what `create_wallet` does. `assert_custom_error` in cnft-vault now takes kite's `SolanaKiteError`. It was already matching on the text of the debug-formatted failure for `Custom(N)`, and kite wraps the same `FailedTransactionMetadata` debug output, so the assertion checks exactly what it did before. The Bubblegum fixtures, the keccak hash recomputation, the Merkle proof building and the borsh mirror structs are untouched. Those are the bulk of these files and kite has nothing to say about them. Tests: cnft-burn 3, cnft-vault 6, cutils 3. (cherry picked from commit 0726129517794c8e4358cbbd2f0ea04da7e9d8e4) --- .../anchor/programs/cnft-burn/Cargo.toml | 1 + .../programs/cnft-burn/tests/test_burn.rs | 49 +++++----- .../anchor/programs/cnft-vault/Cargo.toml | 1 + .../programs/cnft-vault/tests/test_vault.rs | 89 ++++++++----------- .../cutils/anchor/programs/cutils/Cargo.toml | 1 + .../programs/cutils/tests/test_cutils.rs | 46 ++++------ 6 files changed, 73 insertions(+), 114 deletions(-) diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml b/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml index ff89b4f26..0a050b2e5 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml +++ b/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml @@ -38,6 +38,7 @@ borsh = { version = "1", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" +solana-kite = "0.4.0" solana-instruction = "3.0.0" solana-keypair = "3.0.1" solana-pubkey = "3.0.0" diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/tests/test_burn.rs b/compression/cnft-burn/anchor/programs/cnft-burn/tests/test_burn.rs index a2ece44da..819b28fa9 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/tests/test_burn.rs +++ b/compression/cnft-burn/anchor/programs/cnft-burn/tests/test_burn.rs @@ -18,10 +18,9 @@ use { solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keccak_hasher::hashv, solana_keypair::Keypair, - solana_message::Message, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_pubkey::{pubkey, Pubkey}, solana_signer::Signer, - solana_transaction::Transaction, }; // ---- Program IDs ---------------------------------------------------------- @@ -239,19 +238,6 @@ fn read_current_root(data: &[u8]) -> [u8; 32] { // ---- Helpers --------------------------------------------------------------- -fn send( - svm: &mut LiteSVM, - ixs: Vec, - payer: &Keypair, - signers: &[&Keypair], -) -> Result<(), Box> { - let msg = Message::new(&ixs, Some(&payer.pubkey())); - let blockhash = svm.latest_blockhash(); - let mut tx = Transaction::new_unsigned(msg); - tx.sign(signers, blockhash); - svm.send_transaction(tx).map(|_| ()).map_err(Box::new) -} - #[test] fn test_burn_cnft() { let mut svm = LiteSVM::new(); @@ -279,15 +265,8 @@ fn test_burn_cnft() { .unwrap(); // Fund payer and leaf_owner. - let payer = Keypair::new(); - let leaf_owner = Keypair::new(); - svm.airdrop(&payer.pubkey(), 100 * solana_native_token::LAMPORTS_PER_SOL) - .unwrap(); - svm.airdrop( - &leaf_owner.pubkey(), - 10 * solana_native_token::LAMPORTS_PER_SOL, - ) - .unwrap(); + let payer = create_wallet(&mut svm, 100 * solana_native_token::LAMPORTS_PER_SOL).unwrap(); + let leaf_owner = create_wallet(&mut svm, 10 * solana_native_token::LAMPORTS_PER_SOL).unwrap(); // Create the Merkle tree account, owned by the compression program. let merkle_tree = Keypair::new(); @@ -334,11 +313,11 @@ fn test_burn_cnft() { }, }; - send( + send_transaction_from_instructions( &mut svm, vec![create_acc, create_tree_ix], - &payer, &[&payer, &merkle_tree], + &payer.pubkey(), ) .expect("create_tree_config should succeed"); @@ -383,7 +362,8 @@ fn test_burn_cnft() { d }, }; - send(&mut svm, vec![mint_ix], &payer, &[&payer]).expect("mint_v1 should succeed"); + send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey()) + .expect("mint_v1 should succeed"); // Recompute data_hash and creator_hash exactly as Bubblegum does. let data_hash = hash_metadata(&metadata); @@ -432,7 +412,13 @@ fn test_burn_cnft() { data: burn_data.clone(), }; - send(&mut svm, vec![burn_ix], &leaf_owner, &[&leaf_owner]).expect("burn_cnft should succeed"); + send_transaction_from_instructions( + &mut svm, + vec![burn_ix], + &[&leaf_owner], + &leaf_owner.pubkey(), + ) + .expect("burn_cnft should succeed"); // After burning, leaf 0 is zeroed. The root the test cached is now stale, // so a second burn with the same (root, hashes) must fail. @@ -441,7 +427,12 @@ fn test_burn_cnft() { accounts: burn_accounts, data: burn_data, }; - let second = send(&mut svm, vec![burn_ix2], &leaf_owner, &[&leaf_owner]); + let second = send_transaction_from_instructions( + &mut svm, + vec![burn_ix2], + &[&leaf_owner], + &leaf_owner.pubkey(), + ); assert!( second.is_err(), "second burn must fail: the leaf was already burned" diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml b/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml index 56ccfe2a9..9899b0442 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml +++ b/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml @@ -41,6 +41,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana" [dev-dependencies] litesvm = "0.13.1" +solana-kite = "0.4.0" solana-instruction = "3.0.0" solana-keypair = "3.0.1" solana-pubkey = "3.0.0" diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs b/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs index 44183400a..ad0ef8801 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs @@ -32,10 +32,9 @@ use { solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keccak_hasher::hashv, solana_keypair::Keypair, - solana_message::Message, + solana_kite::{create_wallet, send_transaction_from_instructions, SolanaKiteError}, solana_pubkey::{pubkey, Pubkey}, solana_signer::Signer, - solana_transaction::Transaction, }; // ---- Program IDs ---------------------------------------------------------- @@ -249,30 +248,14 @@ fn read_current_root(data: &[u8]) -> [u8; 32] { // ---- Transaction helpers ---------------------------------------------------- -fn send( - svm: &mut LiteSVM, - ixs: Vec, - payer: &Keypair, - signers: &[&Keypair], -) -> Result<(), Box> { - let msg = Message::new(&ixs, Some(&payer.pubkey())); - let blockhash = svm.latest_blockhash(); - let mut tx = Transaction::new_unsigned(msg); - tx.sign(signers, blockhash); - svm.send_transaction(tx).map(|_| ()).map_err(Box::new) -} - /// Assert a failed transaction carries the given program error. -fn assert_custom_error( - result: Result<(), Box>, - expected: VaultError, -) { +fn assert_custom_error(result: Result<(), SolanaKiteError>, expected: VaultError) { let failed = result.expect_err("transaction should fail"); // v2's `#[error_code]` makes the enum `#[repr(u32)]` and only generates // `From for anchor_lang::Error`, so the on-wire custom code is // the discriminant plus the default 6000 offset. let expected_code = expected as u32 + 6000; - let error_text = format!("{:?}", failed.err); + let error_text = format!("{failed:?}"); assert!( error_text.contains(&format!("Custom({expected_code})")), "expected Custom({expected_code}), got: {error_text}" @@ -325,16 +308,9 @@ fn setup_vault() -> VaultTestContext { ) .unwrap(); - let payer = Keypair::new(); - svm.airdrop(&payer.pubkey(), 100 * solana_native_token::LAMPORTS_PER_SOL) - .unwrap(); + let payer = create_wallet(&mut svm, 100 * solana_native_token::LAMPORTS_PER_SOL).unwrap(); - let authority = Keypair::new(); - svm.airdrop( - &authority.pubkey(), - 10 * solana_native_token::LAMPORTS_PER_SOL, - ) - .unwrap(); + let authority = create_wallet(&mut svm, 10 * solana_native_token::LAMPORTS_PER_SOL).unwrap(); // The vault PDA that stores the authority, owns the cNFTs (as Bubblegum // leaf owner) and signs the transfer CPI. @@ -357,11 +333,11 @@ fn setup_vault() -> VaultTestContext { vault_pda, }; let authority_keypair = svm_context.authority.insecure_clone(); - send( + send_transaction_from_instructions( &mut svm_context.svm, vec![initialize_ix], - &authority_keypair, &[&authority_keypair], + &authority_keypair.pubkey(), ) .expect("initialize_vault should succeed"); @@ -419,11 +395,11 @@ fn create_tree_with_vault_cnft(context: &mut VaultTestContext) -> TreeWithVaultC }, }; - send( + send_transaction_from_instructions( &mut context.svm, vec![create_acc, create_tree_ix], - &payer, &[&payer, &merkle_tree], + &payer.pubkey(), ) .expect("create_tree_config should succeed"); @@ -469,7 +445,8 @@ fn create_tree_with_vault_cnft(context: &mut VaultTestContext) -> TreeWithVaultC d }, }; - send(&mut context.svm, vec![mint_ix], &payer, &[&payer]).expect("mint_v1 should succeed"); + send_transaction_from_instructions(&mut context.svm, vec![mint_ix], &[&payer], &payer.pubkey()) + .expect("mint_v1 should succeed"); // Recompute data_hash and creator_hash exactly as Bubblegum does. let data_hash = hash_metadata(&metadata); @@ -612,22 +589,22 @@ fn test_withdraw_cnft_by_authority() { // The stored authority signs, so the withdraw succeeds (the vault PDA // signs the Bubblegum CPI via invoke_signed inside the program). - send( + send_transaction_from_instructions( &mut context.svm, vec![withdraw_ix.clone()], - &authority, &[&authority], + &authority.pubkey(), ) .expect("withdraw_cnft signed by the vault authority should succeed"); // After transfer, leaf 0's owner changed (vault -> recipient), so the root // moved. A second withdraw replaying the same (root, hashes) must fail: the // cached root is stale and the leaf no longer hashes to it for the vault. - let second = send( + let second = send_transaction_from_instructions( &mut context.svm, vec![withdraw_ix], - &authority, &[&authority], + &authority.pubkey(), ); assert!( second.is_err(), @@ -643,19 +620,18 @@ fn test_withdraw_cnft_rejected_for_non_authority() { // An attacker funds and signs their own withdraw attempt; the vault's // stored authority did not sign. - let attacker = Keypair::new(); - context - .svm - .airdrop( - &attacker.pubkey(), - 10 * solana_native_token::LAMPORTS_PER_SOL, - ) - .unwrap(); + let attacker = + create_wallet(&mut context.svm, 10 * solana_native_token::LAMPORTS_PER_SOL).unwrap(); let withdraw_ix = build_withdraw_cnft_instruction(&context, attacker.pubkey(), &tree, recipient.pubkey()); - let result = send(&mut context.svm, vec![withdraw_ix], &attacker, &[&attacker]); + let result = send_transaction_from_instructions( + &mut context.svm, + vec![withdraw_ix], + &[&attacker], + &attacker.pubkey(), + ); assert_custom_error(result, VaultError::InvalidWithdrawAuthority); } @@ -677,11 +653,11 @@ fn test_withdraw_two_cnfts_by_authority() { MAX_DEPTH as u8, ); - send( + send_transaction_from_instructions( &mut context.svm, vec![withdraw_ix], - &authority, &[&authority], + &authority.pubkey(), ) .expect("withdraw_two_cnfts signed by the vault authority should succeed"); @@ -689,7 +665,12 @@ fn test_withdraw_two_cnfts_by_authority() { // single-tree withdraw against either tree with the cached roots fails. let replay1 = build_withdraw_cnft_instruction(&context, authority.pubkey(), &tree1, recipient.pubkey()); - let replay = send(&mut context.svm, vec![replay1], &authority, &[&authority]); + let replay = send_transaction_from_instructions( + &mut context.svm, + vec![replay1], + &[&authority], + &authority.pubkey(), + ); assert!( replay.is_err(), "cNFT#1 already left the vault, replay must fail" @@ -720,11 +701,11 @@ fn test_withdraw_two_cnfts_rejects_out_of_range_proof_length() { 0, ); - let result = send( + let result = send_transaction_from_instructions( &mut context.svm, vec![withdraw_ix], - &authority, &[&authority], + &authority.pubkey(), ); assert_custom_error(result, VaultError::ProofLengthMismatch); } @@ -749,11 +730,11 @@ fn test_withdraw_two_cnfts_rejects_inconsistent_proof_lengths() { MAX_DEPTH as u8, ); - let result = send( + let result = send_transaction_from_instructions( &mut context.svm, vec![withdraw_ix], - &authority, &[&authority], + &authority.pubkey(), ); assert_custom_error(result, VaultError::ProofLengthMismatch); } diff --git a/compression/cutils/anchor/programs/cutils/Cargo.toml b/compression/cutils/anchor/programs/cutils/Cargo.toml index ce870ee74..3329f5eac 100644 --- a/compression/cutils/anchor/programs/cutils/Cargo.toml +++ b/compression/cutils/anchor/programs/cutils/Cargo.toml @@ -43,6 +43,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana" [dev-dependencies] litesvm = "0.13.1" +solana-kite = "0.4.0" solana-instruction = "3.0.0" solana-keypair = "3.0.1" solana-pubkey = "3.0.0" diff --git a/compression/cutils/anchor/programs/cutils/tests/test_cutils.rs b/compression/cutils/anchor/programs/cutils/tests/test_cutils.rs index 36be2b733..ec00f50d5 100644 --- a/compression/cutils/anchor/programs/cutils/tests/test_cutils.rs +++ b/compression/cutils/anchor/programs/cutils/tests/test_cutils.rs @@ -31,10 +31,9 @@ use { solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keccak_hasher::hashv, solana_keypair::Keypair, - solana_message::Message, + solana_kite::{create_wallet, send_transaction_from_instructions}, solana_pubkey::{pubkey, Pubkey}, solana_signer::Signer, - solana_transaction::Transaction, }; // ---- Program IDs ---------------------------------------------------------- @@ -278,19 +277,6 @@ fn get_asset_id(tree: &Pubkey, nonce: u64) -> Pubkey { // ---- Helpers --------------------------------------------------------------- -fn send( - svm: &mut LiteSVM, - ixs: Vec, - payer: &Keypair, - signers: &[&Keypair], -) -> Result<(), Box> { - let msg = Message::new(&ixs, Some(&payer.pubkey())); - let blockhash = svm.latest_blockhash(); - let mut tx = Transaction::new_unsigned(msg); - tx.sign(signers, blockhash); - svm.send_transaction(tx).map(|_| ()).map_err(Box::new) -} - fn metadata_pda(mint: &Pubkey) -> Pubkey { Pubkey::find_program_address( &[b"metadata", TOKEN_METADATA_ID.as_ref(), mint.as_ref()], @@ -397,7 +383,7 @@ fn create_collection_nft( }, }; - send( + send_transaction_from_instructions( svm, vec![ create_mint, @@ -406,8 +392,8 @@ fn create_collection_nft( init_token_acct, mint_to, ], - payer, &[payer, &mint, &token_account, authority], + &payer.pubkey(), ) .expect("collection mint setup should succeed"); @@ -465,11 +451,11 @@ fn create_collection_nft( }, }; - send( + send_transaction_from_instructions( svm, vec![create_metadata, create_master_edition], - payer, &[payer, authority], + &payer.pubkey(), ) .expect("collection metadata + master edition should succeed"); @@ -508,11 +494,8 @@ fn test_cutils_mint_and_verify() { .unwrap(); // Fund payer (also the collection authority / tree delegate / leaf owner). - let payer = Keypair::new(); - let leaf_owner = Keypair::new(); - svm.airdrop(&payer.pubkey(), 1_000 * 1_000_000_000).unwrap(); - svm.airdrop(&leaf_owner.pubkey(), 10 * 1_000_000_000) - .unwrap(); + let payer = create_wallet(&mut svm, 1_000 * 1_000_000_000).unwrap(); + let leaf_owner = create_wallet(&mut svm, 10 * 1_000_000_000).unwrap(); // Build the verified collection NFT (payer is the collection authority). let (collection_mint, collection_metadata, collection_master_edition) = @@ -560,11 +543,11 @@ fn test_cutils_mint_and_verify() { }, }; - send( + send_transaction_from_instructions( &mut svm, vec![create_acc, create_tree_ix], - &payer, &[&payer, &merkle_tree], + &payer.pubkey(), ) .expect("create_tree_config should succeed"); @@ -611,7 +594,8 @@ fn test_cutils_mint_and_verify() { }, }; - send(&mut svm, vec![mint_ix], &payer, &[&payer]).expect("cutils mint should succeed"); + send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey()) + .expect("cutils mint should succeed"); // ---- Recompute the stored leaf's data_hash / creator_hash --------------- // @@ -709,22 +693,22 @@ fn test_cutils_mint_and_verify() { } }; - send( + send_transaction_from_instructions( &mut svm, vec![build_verify(data_hash)], - &leaf_owner, &[&leaf_owner], + &leaf_owner.pubkey(), ) .expect("cutils verify should succeed for the minted leaf"); // A tampered data_hash must fail verification. let mut bad = data_hash; bad[0] ^= 0xff; - let bad_result = send( + let bad_result = send_transaction_from_instructions( &mut svm, vec![build_verify(bad)], - &leaf_owner, &[&leaf_owner], + &leaf_owner.pubkey(), ); assert!( bad_result.is_err(),