use litesvm::types::TransactionMetadata;
use solana_program::instruction::Instruction;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::transaction::Transaction;
use std::fmt;
pub struct TransactionResult {
inner: TransactionMetadata,
instruction_name: Option<String>,
}
impl TransactionResult {
pub fn new(result: TransactionMetadata, instruction_name: Option<String>) -> Self {
Self {
inner: result,
instruction_name,
}
}
pub fn assert_success(&self) -> &Self {
self
}
pub fn logs(&self) -> &[String] {
&self.inner.logs
}
pub fn find_logs(&self, pattern: &str) -> Vec<&String> {
self.inner
.logs
.iter()
.filter(|log| log.contains(pattern))
.collect()
}
pub fn has_log(&self, pattern: &str) -> bool {
self.inner.logs.iter().any(|log| log.contains(pattern))
}
pub fn compute_units(&self) -> u64 {
for log in &self.inner.logs {
if log.contains("consumed") && log.contains("compute units") {
if let Some(consumed_part) = log.split("consumed").nth(1) {
if let Some(number_part) = consumed_part.split("of").next() {
if let Ok(units) = number_part.trim().parse::<u64>() {
return units;
}
}
}
}
}
0
}
pub fn print_logs(&self) {
if let Some(ref name) = self.instruction_name {
println!("Transaction logs for '{}':", name);
} else {
println!("Transaction logs:");
}
for log in &self.inner.logs {
println!(" {}", log);
}
}
pub fn inner(&self) -> &TransactionMetadata {
&self.inner
}
}
impl fmt::Debug for TransactionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TransactionResult")
.field("instruction", &self.instruction_name)
.field("logs_count", &self.inner.logs.len())
.field("compute_units", &self.compute_units())
.finish()
}
}
#[derive(Debug)]
pub enum TransactionError {
ExecutionFailed(String),
BuildError(String),
}
impl fmt::Display for TransactionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransactionError::ExecutionFailed(msg) => {
write!(f, "Transaction execution failed: {}", msg)
}
TransactionError::BuildError(msg) => {
write!(f, "Transaction build error: {}", msg)
}
}
}
}
impl std::error::Error for TransactionError {}
pub trait TransactionHelpers {
fn send_instruction(
&mut self,
instruction: Instruction,
signers: &[&Keypair],
) -> Result<TransactionResult, TransactionError>;
fn send_instructions(
&mut self,
instructions: &[Instruction],
signers: &[&Keypair],
) -> Result<TransactionResult, TransactionError>;
fn execute<T>(
&mut self,
instruction_name: &str,
accounts: Vec<solana_program::instruction::AccountMeta>,
args: T,
signers: &[&Keypair],
) -> Result<TransactionResult, TransactionError>
where
T: anchor_lang::AnchorSerialize;
}
impl TransactionHelpers for crate::AnchorContext {
fn send_instruction(
&mut self,
instruction: Instruction,
signers: &[&Keypair],
) -> Result<TransactionResult, TransactionError> {
self.send_instructions(&[instruction], signers)
}
fn send_instructions(
&mut self,
instructions: &[Instruction],
signers: &[&Keypair],
) -> Result<TransactionResult, TransactionError> {
if signers.is_empty() {
return Err(TransactionError::BuildError(
"No signers provided".to_string(),
));
}
let payer = &signers[0].pubkey();
let tx = Transaction::new_signed_with_payer(
instructions,
Some(payer),
signers,
self.svm.latest_blockhash(),
);
match self.svm.send_transaction(tx) {
Ok(result) => Ok(TransactionResult::new(result, None)),
Err(e) => Err(TransactionError::ExecutionFailed(format!("{:?}", e))),
}
}
fn execute<T>(
&mut self,
instruction_name: &str,
accounts: Vec<solana_program::instruction::AccountMeta>,
args: T,
signers: &[&Keypair],
) -> Result<TransactionResult, TransactionError>
where
T: anchor_lang::AnchorSerialize,
{
let instruction = self
.build_instruction(instruction_name, accounts, args)
.map_err(|e| TransactionError::BuildError(e.to_string()))?;
match self.send_instruction(instruction, signers) {
Ok(mut result) => {
result.instruction_name = Some(instruction_name.to_string());
Ok(result)
}
Err(e) => Err(e),
}
}
}