use anyhow::*;
use litesvm::types::TransactionMetadata;
use solana_sdk::{instruction::InstructionError, program_error::ProgramError};
use testsvm_core::prelude::*;
pub struct TXErrorAssertions {
pub(crate) error: TXError,
}
impl TXErrorAssertions {
pub fn with_anchor_error(&self, error_name: &str) -> Result<()> {
match self.error.metadata.err.clone() {
solana_sdk::transaction::TransactionError::InstructionError(
_,
InstructionError::Custom(_error_code),
) => {
let maybe_error_message = self
.error
.metadata
.meta
.logs
.iter()
.rev()
.find(|line| line.contains("AnchorError"));
if let Some(error_message) = maybe_error_message {
if error_message.contains(&format!("{error_name}. Error Number:")) {
Ok(())
} else {
Err(anyhow!(
"Expected Anchor error '{}', got '{}'",
error_name,
error_message
))
}
} else {
Err(anyhow!(
"Expected Anchor error '{}', but nothing was found in the logs",
error_name
))
}
}
_ => Err(anyhow!(
"Expected error containing '{}', but got '{}'",
error_name,
self.error.metadata.err.to_string()
)),
}
}
pub fn with_error(&self, error_name: &str) -> Result<()> {
match self.error.metadata.err.clone() {
solana_sdk::transaction::TransactionError::InstructionError(
_,
InstructionError::Custom(error_code),
) => {
if self
.error
.metadata
.meta
.pretty_logs()
.contains(format!("{error_name}. Error Number: {error_code}").as_str())
{
Ok(())
} else {
Err(anyhow!("Expected error '{}'", error_name))
}
}
_ => Err(anyhow!(
"Expected error containing '{}', but got '{}'",
error_name,
self.error.metadata.err.to_string()
)),
}
}
pub fn with_custom_error(&self, error_code: u32) -> Result<()> {
match self.error.metadata.err.clone() {
solana_sdk::transaction::TransactionError::InstructionError(
_,
InstructionError::Custom(code),
) => {
if code == error_code {
Ok(())
} else {
Err(anyhow!(
"Expected custom error code {}, got {}",
error_code,
code
))
}
}
_ => Err(anyhow!(
"Expected custom error code {}, but got '{}'",
error_code,
self.error.metadata.err.to_string()
)),
}
}
pub fn with_program_error<T: Into<ProgramError>>(&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()
)),
}
}
pub fn error(&self) -> &TXError {
&self.error
}
}
pub struct TXSuccessAssertions {
pub metadata: TransactionMetadata,
}
impl TXSuccessAssertions {
pub fn compute_units(&self) -> u64 {
self.metadata.compute_units_consumed
}
pub fn logs(&self) -> &Vec<String> {
&self.metadata.logs
}
}
pub trait TXResultAssertions {
fn fails(self) -> Result<TXErrorAssertions>;
fn succeeds(self) -> Result<TXSuccessAssertions>;
}
impl TXResultAssertions for TXResult {
fn fails(self) -> Result<TXErrorAssertions> {
let err = self
.err()
.ok_or(anyhow::anyhow!("Unexpected successful transaction"))?;
Ok(TXErrorAssertions { error: *err })
}
fn succeeds(self) -> Result<TXSuccessAssertions> {
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))
}
}
}
}