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(),