1use litesvm::types::TransactionMetadata;
7use litesvm::LiteSVM;
8use solana_program::instruction::Instruction;
9use solana_sdk::signature::{Keypair, Signer};
10use solana_sdk::transaction::Transaction;
11use std::fmt;
12use thiserror::Error;
13
14#[derive(Error, Debug)]
15pub enum TransactionError {
16 #[error("Transaction execution failed: {0}")]
17 ExecutionFailed(String),
18
19 #[error("Transaction build error: {0}")]
20 BuildError(String),
21
22 #[error("Assertion failed: {0}")]
23 AssertionFailed(String),
24}
25
26pub struct TransactionResult {
40 inner: TransactionMetadata,
41 instruction_name: Option<String>,
42 error: Option<String>,
43}
44
45impl TransactionResult {
46 pub fn new(result: TransactionMetadata, instruction_name: Option<String>) -> Self {
53 Self {
54 inner: result,
55 instruction_name,
56 error: None,
57 }
58 }
59
60 pub fn new_failed(error: String, result: TransactionMetadata, instruction_name: Option<String>) -> Self {
68 Self {
69 inner: result,
70 instruction_name,
71 error: Some(error),
72 }
73 }
74
75 pub fn assert_success(&self) -> &Self {
87 assert!(
88 self.error.is_none(),
89 "Transaction failed: {}\nLogs:\n{}",
90 self.error.as_ref().unwrap_or(&"Unknown error".to_string()),
91 self.logs().join("\n")
92 );
93 self
94 }
95
96 pub fn is_success(&self) -> bool {
102 self.error.is_none()
103 }
104
105 pub fn error(&self) -> Option<&String> {
111 self.error.as_ref()
112 }
113
114 pub fn logs(&self) -> &[String] {
120 &self.inner.logs
121 }
122
123 pub fn has_log(&self, message: &str) -> bool {
133 self.inner.logs.iter().any(|log| log.contains(message))
134 }
135
136 pub fn find_log(&self, pattern: &str) -> Option<&String> {
146 self.inner.logs.iter().find(|log| log.contains(pattern))
147 }
148
149 pub fn compute_units(&self) -> u64 {
155 self.inner.compute_units_consumed
156 }
157
158 pub fn print_logs(&self) {
160 println!("=== Transaction Logs ===");
161 if let Some(name) = &self.instruction_name {
162 println!("Instruction: {}", name);
163 }
164 for log in &self.inner.logs {
165 println!("{}", log);
166 }
167 if let Some(err) = &self.error {
168 println!("Error: {}", err);
169 }
170 println!("Compute Units: {}", self.compute_units());
171 println!("========================");
172 }
173
174 pub fn inner(&self) -> &TransactionMetadata {
176 &self.inner
177 }
178
179 pub fn assert_failure(&self) -> &Self {
195 assert!(
196 self.error.is_some(),
197 "Expected transaction to fail, but it succeeded.\nLogs:\n{}",
198 self.logs().join("\n")
199 );
200 self
201 }
202
203 pub fn assert_error(&self, expected_error: &str) -> &Self {
223 match &self.error {
224 Some(error) => {
225 assert!(
226 error.contains(expected_error),
227 "Transaction failed with unexpected error.\nExpected substring: {}\nActual error: {}\nLogs:\n{}",
228 expected_error,
229 error,
230 self.logs().join("\n")
231 );
232 }
233 None => {
234 panic!(
235 "Expected transaction to fail with error containing '{}', but it succeeded.\nLogs:\n{}",
236 expected_error,
237 self.logs().join("\n")
238 );
239 }
240 }
241 self
242 }
243
244 pub fn assert_error_code(&self, error_code: u32) -> &Self {
267 let error_code_str = format!("custom program error: 0x{:x}", error_code);
268 self.assert_error(&error_code_str)
269 }
270
271 pub fn assert_anchor_error(&self, error_name: &str) -> &Self {
294 self.assert_failure();
295
296 let found_in_logs = self.logs().iter().any(|log| log.contains(error_name));
298
299 let found_in_error = self.error
301 .as_ref()
302 .map(|e| e.contains(error_name))
303 .unwrap_or(false);
304
305 assert!(
306 found_in_logs || found_in_error,
307 "Expected Anchor error '{}' not found in transaction logs or error message.\nError: {:?}\nLogs:\n{}",
308 error_name,
309 self.error,
310 self.logs().join("\n")
311 );
312 self
313 }
314
315 pub fn assert_log_error(&self, error_message: &str) -> &Self {
337 assert!(
338 self.has_log(error_message),
339 "Expected error message '{}' not found in logs.\nLogs:\n{}",
340 error_message,
341 self.logs().join("\n")
342 );
343 self
344 }
345}
346
347impl fmt::Debug for TransactionResult {
348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349 f.debug_struct("TransactionResult")
350 .field("instruction", &self.instruction_name)
351 .field("success", &self.is_success())
352 .field("error", &self.error())
353 .field("compute_units", &self.compute_units())
354 .field("log_count", &self.logs().len())
355 .finish()
356 }
357}
358
359pub trait TransactionHelpers {
361 fn send_instruction(
376 &mut self,
377 instruction: Instruction,
378 signers: &[&Keypair],
379 ) -> Result<TransactionResult, TransactionError>;
380
381 fn send_instructions(
397 &mut self,
398 instructions: &[Instruction],
399 signers: &[&Keypair],
400 ) -> Result<TransactionResult, TransactionError>;
401
402 fn send_transaction_result(
424 &mut self,
425 transaction: Transaction,
426 ) -> Result<TransactionResult, TransactionError>;
427}
428
429impl TransactionHelpers for LiteSVM {
430 fn send_instruction(
431 &mut self,
432 instruction: Instruction,
433 signers: &[&Keypair],
434 ) -> Result<TransactionResult, TransactionError> {
435 if signers.is_empty() {
436 return Err(TransactionError::BuildError("No signers provided".to_string()));
437 }
438
439 let tx = Transaction::new_signed_with_payer(
440 &[instruction],
441 Some(&signers[0].pubkey()),
442 signers,
443 self.latest_blockhash(),
444 );
445
446 self.send_transaction_result(tx)
447 }
448
449 fn send_instructions(
450 &mut self,
451 instructions: &[Instruction],
452 signers: &[&Keypair],
453 ) -> Result<TransactionResult, TransactionError> {
454 if signers.is_empty() {
455 return Err(TransactionError::BuildError("No signers provided".to_string()));
456 }
457
458 let tx = Transaction::new_signed_with_payer(
459 instructions,
460 Some(&signers[0].pubkey()),
461 signers,
462 self.latest_blockhash(),
463 );
464
465 self.send_transaction_result(tx)
466 }
467
468 fn send_transaction_result(
469 &mut self,
470 transaction: Transaction,
471 ) -> Result<TransactionResult, TransactionError> {
472 match self.send_transaction(transaction) {
473 Ok(result) => Ok(TransactionResult::new(result, None)),
474 Err(failed) => {
475 Ok(TransactionResult::new_failed(
477 format!("{:?}", failed.err),
478 failed.meta,
479 None,
480 ))
481 }
482 }
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use crate::test_helpers::TestHelpers;
490 use solana_system_interface::instruction as system_instruction;
491
492 #[test]
493 fn test_transaction_result_success() {
494 let mut svm = LiteSVM::new();
495 let payer = svm.create_funded_account(10_000_000_000).unwrap();
496 let recipient = Keypair::new();
497
498 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
500
501 let result = svm.send_instruction(ix, &[&payer]).unwrap();
502
503 assert!(result.is_success());
504 assert_eq!(result.error(), None);
505 result.assert_success();
506 }
507
508 #[test]
509 fn test_transaction_result_has_log() {
510 let mut svm = LiteSVM::new();
511 let payer = svm.create_funded_account(10_000_000_000).unwrap();
512 let recipient = Keypair::new();
513
514 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
515 let result = svm.send_instruction(ix, &[&payer]).unwrap();
516
517 assert!(result.has_log("invoke"));
519 }
520
521 #[test]
522 fn test_transaction_result_find_log() {
523 let mut svm = LiteSVM::new();
524 let payer = svm.create_funded_account(10_000_000_000).unwrap();
525 let recipient = Keypair::new();
526
527 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
528 let result = svm.send_instruction(ix, &[&payer]).unwrap();
529
530 let log = result.find_log("invoke");
532 assert!(log.is_some());
533 }
534
535 #[test]
536 fn test_transaction_result_compute_units() {
537 let mut svm = LiteSVM::new();
538 let payer = svm.create_funded_account(10_000_000_000).unwrap();
539 let recipient = Keypair::new();
540
541 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
542 let result = svm.send_instruction(ix, &[&payer]).unwrap();
543
544 let cu = result.compute_units();
546 assert!(cu > 0);
547 assert!(cu < 1_000_000); }
549
550 #[test]
551 fn test_transaction_result_logs() {
552 let mut svm = LiteSVM::new();
553 let payer = svm.create_funded_account(10_000_000_000).unwrap();
554 let recipient = Keypair::new();
555
556 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
557 let result = svm.send_instruction(ix, &[&payer]).unwrap();
558
559 let logs = result.logs();
560 assert!(!logs.is_empty());
561 }
562
563 #[test]
564 fn test_transaction_result_inner() {
565 let mut svm = LiteSVM::new();
566 let payer = svm.create_funded_account(10_000_000_000).unwrap();
567 let recipient = Keypair::new();
568
569 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
570 let result = svm.send_instruction(ix, &[&payer]).unwrap();
571
572 let _inner = result.inner();
574 assert!(_inner.compute_units_consumed > 0);
575 }
576
577 #[test]
578 fn test_transaction_result_failure() {
579 let mut svm = LiteSVM::new();
580 let payer = Keypair::new(); let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
584 let result = svm.send_instruction(ix, &[&payer]).unwrap();
585
586 assert!(!result.is_success());
587 assert!(result.error().is_some());
588 }
589
590 #[test]
591 fn test_transaction_result_assert_failure() {
592 let mut svm = LiteSVM::new();
593 let payer = Keypair::new(); let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
596 let result = svm.send_instruction(ix, &[&payer]).unwrap();
597
598 result.assert_failure();
600 }
601
602 #[test]
603 #[should_panic(expected = "Expected transaction to fail")]
604 fn test_transaction_result_assert_failure_on_success() {
605 let mut svm = LiteSVM::new();
606 let payer = svm.create_funded_account(10_000_000_000).unwrap();
607
608 let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
609 let result = svm.send_instruction(ix, &[&payer]).unwrap();
610
611 result.assert_failure();
613 }
614
615 #[test]
616 fn test_transaction_result_assert_error() {
617 let mut svm = LiteSVM::new();
618 let payer = Keypair::new(); let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
621 let result = svm.send_instruction(ix, &[&payer]).unwrap();
622
623 result.assert_error("AccountNotFound");
625 }
626
627 #[test]
628 #[should_panic(expected = "Transaction failed with unexpected error")]
629 fn test_transaction_result_assert_error_wrong_message() {
630 let mut svm = LiteSVM::new();
631 let payer = Keypair::new(); let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
634 let result = svm.send_instruction(ix, &[&payer]).unwrap();
635
636 result.assert_error("this error does not exist");
638 }
639
640 #[test]
641 fn test_send_multiple_instructions() {
642 let mut svm = LiteSVM::new();
643 let payer = svm.create_funded_account(10_000_000_000).unwrap();
644 let recipient1 = Keypair::new();
645 let recipient2 = Keypair::new();
646
647 let ix1 = system_instruction::transfer(&payer.pubkey(), &recipient1.pubkey(), 1_000_000);
649 let ix2 = system_instruction::transfer(&payer.pubkey(), &recipient2.pubkey(), 2_000_000);
650
651 let result = svm.send_instructions(&[ix1, ix2], &[&payer]).unwrap();
652 result.assert_success();
653
654 let balance1 = svm.get_balance(&recipient1.pubkey()).unwrap();
656 let balance2 = svm.get_balance(&recipient2.pubkey()).unwrap();
657 assert_eq!(balance1, 1_000_000);
658 assert_eq!(balance2, 2_000_000);
659 }
660
661 #[test]
662 fn test_send_instruction_no_signers() {
663 let mut svm = LiteSVM::new();
664 let payer = Keypair::new();
665 let recipient = Keypair::new();
666
667 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
668
669 let result = svm.send_instruction(ix, &[]);
671 assert!(result.is_err());
672 match result {
673 Err(TransactionError::BuildError(msg)) => {
674 assert!(msg.contains("No signers"));
675 }
676 _ => panic!("Expected BuildError"),
677 }
678 }
679
680 #[test]
681 fn test_send_instructions_no_signers() {
682 let mut svm = LiteSVM::new();
683 let payer = Keypair::new();
684 let recipient = Keypair::new();
685
686 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
687
688 let result = svm.send_instructions(&[ix], &[]);
690 assert!(result.is_err());
691 }
692
693 #[test]
694 fn test_transaction_result_debug() {
695 let mut svm = LiteSVM::new();
696 let payer = svm.create_funded_account(10_000_000_000).unwrap();
697 let recipient = Keypair::new();
698
699 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
700 let result = svm.send_instruction(ix, &[&payer]).unwrap();
701
702 let debug_str = format!("{:?}", result);
704 assert!(debug_str.contains("TransactionResult"));
705 }
706
707 #[test]
708 fn test_transaction_result_print_logs() {
709 let mut svm = LiteSVM::new();
710 let payer = svm.create_funded_account(10_000_000_000).unwrap();
711 let recipient = Keypair::new();
712
713 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
714 let result = svm.send_instruction(ix, &[&payer]).unwrap();
715
716 result.print_logs();
718 }
719
720 #[test]
721 fn test_send_transaction_result() {
722 let mut svm = LiteSVM::new();
723 let payer = svm.create_funded_account(10_000_000_000).unwrap();
724 let recipient = Keypair::new();
725
726 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
727 let tx = Transaction::new_signed_with_payer(
728 &[ix],
729 Some(&payer.pubkey()),
730 &[&payer],
731 svm.latest_blockhash(),
732 );
733
734 let result = svm.send_transaction_result(tx).unwrap();
735 result.assert_success();
736 }
737}