From e30103c2c70ca734deaa00e54a206b979ad5610c Mon Sep 17 00:00:00 2001 From: Ian Macalinao Date: Fri, 10 Oct 2025 13:15:37 +0800 Subject: [PATCH 1/3] Adds with_program_error assertion --- crates/testsvm-assertions/src/lib.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/testsvm-assertions/src/lib.rs b/crates/testsvm-assertions/src/lib.rs index 8683ccb..c40548b 100644 --- a/crates/testsvm-assertions/src/lib.rs +++ b/crates/testsvm-assertions/src/lib.rs @@ -15,7 +15,7 @@ use anyhow::*; use litesvm::types::TransactionMetadata; -use solana_sdk::instruction::InstructionError; +use solana_sdk::{instruction::InstructionError, program_error::ProgramError}; use testsvm_core::prelude::*; /// Provides assertion methods for failed transactions. @@ -128,6 +128,30 @@ impl TXErrorAssertions { } } + /// Asserts that the transaction failed with a specific program error. + pub fn with_program_error>(&self, err: T) -> Result<()> { + let program_error: ProgramError = err.into(); + match self.error.metadata.err.clone() { + solana_sdk::transaction::TransactionError::InstructionError(_, err) => { + let result_program_error: ProgramError = err.try_into()?; + if result_program_error == program_error { + Ok(()) + } else { + Err(anyhow!( + "Expected custom program error {}, but got '{}'", + program_error, + result_program_error + )) + } + } + _ => Err(anyhow!( + "Expected custom program error {}, but got instruction error '{}'", + program_error, + self.error.metadata.err.to_string() + )), + } + } + /// Returns the underlying transaction error for custom assertions. pub fn error(&self) -> &TXError { &self.error From 10577b0542b52e53097a4f5842e5ea03e1ea47f4 Mon Sep 17 00:00:00 2001 From: Ian Macalinao Date: Fri, 10 Oct 2025 13:31:38 +0800 Subject: [PATCH 2/3] don't log address book on failures if they are expected --- crates/solana-address-book/src/address_book.rs | 2 +- crates/testsvm-assertions/src/lib.rs | 10 ++++++++-- crates/testsvm-core/src/lib.rs | 14 +++++--------- crates/testsvm-core/src/tx_result.rs | 11 +++++++---- .../src/tests/test_rewarder_management.rs | 3 ++- devenv.lock | 12 ++++++------ 6 files changed, 29 insertions(+), 23 deletions(-) diff --git a/crates/solana-address-book/src/address_book.rs b/crates/solana-address-book/src/address_book.rs index 735fb4f..91e7c52 100644 --- a/crates/solana-address-book/src/address_book.rs +++ b/crates/solana-address-book/src/address_book.rs @@ -14,7 +14,7 @@ use std::collections::{HashMap, HashSet}; /// Solana addresses by their public keys, labels, and roles. It's designed /// to help with debugging and transaction analysis by providing meaningful /// context for addresses. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct AddressBook { addresses: HashMap>, registered_addresses: HashSet, diff --git a/crates/testsvm-assertions/src/lib.rs b/crates/testsvm-assertions/src/lib.rs index c40548b..3d6a2ad 100644 --- a/crates/testsvm-assertions/src/lib.rs +++ b/crates/testsvm-assertions/src/lib.rs @@ -294,7 +294,13 @@ impl TXResultAssertions for TXResult { } fn succeeds(self) -> Result { - let metadata = self?; - Ok(TXSuccessAssertions { metadata }) + match self { + Result::Ok(metadata) => Ok(TXSuccessAssertions { metadata }), + Result::Err(e) => { + e.print_error(); + e.address_book.print_all(); + Err(anyhow::anyhow!("Unexpected failed transaction: {}", e)) + } + } } } diff --git a/crates/testsvm-core/src/lib.rs b/crates/testsvm-core/src/lib.rs index fa1cc74..aa5942a 100644 --- a/crates/testsvm-core/src/lib.rs +++ b/crates/testsvm-core/src/lib.rs @@ -65,15 +65,11 @@ impl TestSVM { pub fn execute_transaction(&mut self, transaction: Transaction) -> TXResult { match self.svm.send_transaction(transaction.clone()) { Result::Ok(tx_result) => Result::Ok(tx_result), - Err(e) => { - let tx_error = TXError { - transaction, - metadata: e.clone(), - }; - tx_error.print_error(&self.address_book); - self.address_book.print_all(); - Err(Box::new(tx_error)) - } + Err(e) => Err(Box::new(TXError { + transaction, + metadata: e.clone(), + address_book: self.address_book.clone(), + })), } } diff --git a/crates/testsvm-core/src/tx_result.rs b/crates/testsvm-core/src/tx_result.rs index 9d87d25..af60434 100644 --- a/crates/testsvm-core/src/tx_result.rs +++ b/crates/testsvm-core/src/tx_result.rs @@ -34,6 +34,8 @@ pub struct TXError { pub transaction: Transaction, /// Underlying failed transaction metadata pub metadata: FailedTransactionMetadata, + /// Address book at the time of transaction failure + pub address_book: AddressBook, } impl Error for TXError {} @@ -46,7 +48,7 @@ impl Display for TXError { impl TXError { /// Print the error details, formatted using an [AddressBook]. - pub fn print_error(&self, address_book: &AddressBook) { + pub fn print_error(&self) { println!( "\n{} {}", "❌".red(), @@ -61,7 +63,8 @@ impl TXError { ); println!( "{}", - address_book.replace_addresses_in_text(&self.metadata.meta.pretty_logs()) + self.address_book + .replace_addresses_in_text(&self.metadata.meta.pretty_logs()) ); // Log each instruction for debugging @@ -76,7 +79,7 @@ impl TXError { " {} {}: {}", "Instruction".dimmed(), i.to_string().bold(), - address_book.format_address(&program_id) + self.address_book.format_address(&program_id) ); println!( " {} {}", @@ -111,7 +114,7 @@ impl TXError { " {} {}: {}{}", "Account".dimmed(), j.to_string().bold(), - address_book.format_address(&account_key), + self.address_book.format_address(&account_key), flags_str ); } diff --git a/crates/testsvm-quarry/src/tests/test_rewarder_management.rs b/crates/testsvm-quarry/src/tests/test_rewarder_management.rs index d835b5e..b3daf80 100644 --- a/crates/testsvm-quarry/src/tests/test_rewarder_management.rs +++ b/crates/testsvm-quarry/src/tests/test_rewarder_management.rs @@ -57,7 +57,8 @@ fn test_set_rewards_share_authority_check() -> Result<()> { ); env.execute_ixs_with_signers(&[unauthorized_ix], &[&unauthorized]) - .fails()?; + .fails()? + .with_anchor_error("Unauthorized")?; // Verify share remains unchanged let quarry_data = quarry.fetch_quarry(&env)?; diff --git a/devenv.lock b/devenv.lock index 7dce35e..55257c5 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,10 +3,10 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1756415044, + "lastModified": 1759939975, "owner": "cachix", "repo": "devenv", - "rev": "c570189b38b549141179647da3ddde249ac50fec", + "rev": "6eda3b7af3010d289e6e8e047435956fc80c1395", "type": "github" }, "original": { @@ -40,10 +40,10 @@ ] }, "locked": { - "lastModified": 1755960406, + "lastModified": 1759523803, "owner": "cachix", "repo": "git-hooks.nix", - "rev": "e891a93b193fcaf2fc8012d890dc7f0befe86ec2", + "rev": "cfc9f7bb163ad8542029d303e599c0f7eee09835", "type": "github" }, "original": { @@ -74,10 +74,10 @@ }, "nixpkgs": { "locked": { - "lastModified": 1755783167, + "lastModified": 1758532697, "owner": "cachix", "repo": "devenv-nixpkgs", - "rev": "4a880fb247d24fbca57269af672e8f78935b0328", + "rev": "207a4cb0e1253c7658c6736becc6eb9cace1f25f", "type": "github" }, "original": { From 3d324e325f888e15a1c028d67ebfb9c2e54bb958 Mon Sep 17 00:00:00 2001 From: Ian Macalinao Date: Fri, 10 Oct 2025 13:33:10 +0800 Subject: [PATCH 3/3] get_pda_with_bump helper --- crates/testsvm-core/src/lib.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/testsvm-core/src/lib.rs b/crates/testsvm-core/src/lib.rs index aa5942a..f35af2c 100644 --- a/crates/testsvm-core/src/lib.rs +++ b/crates/testsvm-core/src/lib.rs @@ -156,10 +156,21 @@ impl TestSVM { seeds: &[&[u8]], program_id: Pubkey, ) -> Result> { - let (pubkey, _bump) = self + let (pda, _) = self.get_pda_with_bump(label, seeds, program_id)?; + Ok(pda) + } + + /// Finds a program derived address and return an [AccountRef] with proper type information and bump seed. + pub fn get_pda_with_bump( + &mut self, + label: &str, + seeds: &[&[u8]], + program_id: Pubkey, + ) -> Result<(AccountRef, u8)> { + let (pubkey, bump) = self .address_book .find_pda_with_bump(label, seeds, program_id)?; - Ok(AccountRef::new(pubkey)) + Ok((AccountRef::new(pubkey), bump)) } /// Advance the time by the specified number of seconds