1use litesvm::types::TransactionMetadata;
7use litesvm::LiteSVM;
8use solana_keypair::Keypair;
9use solana_program::instruction::Instruction;
10use solana_signer::Signer;
11use solana_transaction::Transaction;
12use std::fmt;
13use thiserror::Error;
14
15#[derive(Error, Debug)]
16pub enum TransactionError {
17 #[error("Transaction execution failed: {0}")]
18 ExecutionFailed(String),
19
20 #[error("Transaction build error: {0}")]
21 BuildError(String),
22
23 #[error("Assertion failed: {0}")]
24 AssertionFailed(String),
25}
26
27pub struct TransactionResult {
41 inner: TransactionMetadata,
42 instruction_name: Option<String>,
43 error: Option<String>,
44}
45
46impl TransactionResult {
47 pub fn new(result: TransactionMetadata, instruction_name: Option<String>) -> Self {
54 Self {
55 inner: result,
56 instruction_name,
57 error: None,
58 }
59 }
60
61 pub fn new_failed(
69 error: String,
70 result: TransactionMetadata,
71 instruction_name: Option<String>,
72 ) -> Self {
73 Self {
74 inner: result,
75 instruction_name,
76 error: Some(error),
77 }
78 }
79
80 pub fn assert_success(&self) -> &Self {
92 assert!(
93 self.error.is_none(),
94 "Transaction failed: {}\nLogs:\n{}",
95 self.error.as_ref().unwrap_or(&"Unknown error".to_string()),
96 self.logs().join("\n")
97 );
98 self
99 }
100
101 pub fn is_success(&self) -> bool {
107 self.error.is_none()
108 }
109
110 pub fn error(&self) -> Option<&String> {
116 self.error.as_ref()
117 }
118
119 pub fn logs(&self) -> &[String] {
125 &self.inner.logs
126 }
127
128 pub fn has_log(&self, message: &str) -> bool {
138 self.inner.logs.iter().any(|log| log.contains(message))
139 }
140
141 pub fn find_log(&self, pattern: &str) -> Option<&String> {
151 self.inner.logs.iter().find(|log| log.contains(pattern))
152 }
153
154 pub fn compute_units(&self) -> u64 {
160 self.inner.compute_units_consumed
161 }
162
163 pub fn print_logs(&self) {
165 println!("=== Transaction Logs ===");
166 if let Some(name) = &self.instruction_name {
167 println!("Instruction: {}", name);
168 }
169 for log in &self.inner.logs {
170 println!("{}", log);
171 }
172 if let Some(err) = &self.error {
173 println!("Error: {}", err);
174 }
175 println!("Compute Units: {}", self.compute_units());
176 println!("========================");
177 }
178
179 pub fn inner(&self) -> &TransactionMetadata {
181 &self.inner
182 }
183
184 pub fn assert_failure(&self) -> &Self {
200 assert!(
201 self.error.is_some(),
202 "Expected transaction to fail, but it succeeded.\nLogs:\n{}",
203 self.logs().join("\n")
204 );
205 self
206 }
207
208 pub fn assert_error(&self, expected_error: &str) -> &Self {
228 match &self.error {
229 Some(error) => {
230 assert!(
231 error.contains(expected_error),
232 "Transaction failed with unexpected error.\nExpected substring: {}\nActual error: {}\nLogs:\n{}",
233 expected_error,
234 error,
235 self.logs().join("\n")
236 );
237 }
238 None => {
239 panic!(
240 "Expected transaction to fail with error containing '{}', but it succeeded.\nLogs:\n{}",
241 expected_error,
242 self.logs().join("\n")
243 );
244 }
245 }
246 self
247 }
248
249 pub fn assert_error_code(&self, error_code: u32) -> &Self {
272 let error_code_str = format!("custom program error: 0x{:x}", error_code);
273 self.assert_error(&error_code_str)
274 }
275
276 pub fn assert_anchor_error(&self, error_name: &str) -> &Self {
299 self.assert_failure();
300
301 let found_in_logs = self.logs().iter().any(|log| log.contains(error_name));
303
304 let found_in_error = self
306 .error
307 .as_ref()
308 .map(|e| e.contains(error_name))
309 .unwrap_or(false);
310
311 assert!(
312 found_in_logs || found_in_error,
313 "Expected Anchor error '{}' not found in transaction logs or error message.\nError: {:?}\nLogs:\n{}",
314 error_name,
315 self.error,
316 self.logs().join("\n")
317 );
318 self
319 }
320
321 pub fn assert_log_error(&self, error_message: &str) -> &Self {
343 assert!(
344 self.has_log(error_message),
345 "Expected error message '{}' not found in logs.\nLogs:\n{}",
346 error_message,
347 self.logs().join("\n")
348 );
349 self
350 }
351}
352
353impl fmt::Debug for TransactionResult {
354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 f.debug_struct("TransactionResult")
356 .field("instruction", &self.instruction_name)
357 .field("success", &self.is_success())
358 .field("error", &self.error())
359 .field("compute_units", &self.compute_units())
360 .field("log_count", &self.logs().len())
361 .finish()
362 }
363}
364
365pub trait TransactionHelpers {
367 fn send_instruction(
382 &mut self,
383 instruction: Instruction,
384 signers: &[&Keypair],
385 ) -> Result<TransactionResult, TransactionError>;
386
387 fn send_instructions(
403 &mut self,
404 instructions: &[Instruction],
405 signers: &[&Keypair],
406 ) -> Result<TransactionResult, TransactionError>;
407
408 fn send_transaction_result(
431 &mut self,
432 transaction: Transaction,
433 ) -> Result<TransactionResult, TransactionError>;
434}
435
436impl TransactionHelpers for LiteSVM {
437 fn send_instruction(
438 &mut self,
439 instruction: Instruction,
440 signers: &[&Keypair],
441 ) -> Result<TransactionResult, TransactionError> {
442 if signers.is_empty() {
443 return Err(TransactionError::BuildError(
444 "No signers provided".to_string(),
445 ));
446 }
447
448 let tx = Transaction::new_signed_with_payer(
449 &[instruction],
450 Some(&signers[0].pubkey()),
451 signers,
452 self.latest_blockhash(),
453 );
454
455 self.send_transaction_result(tx)
456 }
457
458 fn send_instructions(
459 &mut self,
460 instructions: &[Instruction],
461 signers: &[&Keypair],
462 ) -> Result<TransactionResult, TransactionError> {
463 if signers.is_empty() {
464 return Err(TransactionError::BuildError(
465 "No signers provided".to_string(),
466 ));
467 }
468
469 let tx = Transaction::new_signed_with_payer(
470 instructions,
471 Some(&signers[0].pubkey()),
472 signers,
473 self.latest_blockhash(),
474 );
475
476 self.send_transaction_result(tx)
477 }
478
479 fn send_transaction_result(
480 &mut self,
481 transaction: Transaction,
482 ) -> Result<TransactionResult, TransactionError> {
483 match self.send_transaction(transaction) {
484 Ok(result) => Ok(TransactionResult::new(result, None)),
485 Err(failed) => {
486 Ok(TransactionResult::new_failed(
488 format!("{:?}", failed.err),
489 failed.meta,
490 None,
491 ))
492 }
493 }
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500 use crate::test_helpers::TestHelpers;
501 use solana_system_interface::instruction as system_instruction;
502
503 #[test]
504 fn test_transaction_result_success() {
505 let mut svm = LiteSVM::new();
506 let payer = svm.create_funded_account(10_000_000_000).unwrap();
507 let recipient = Keypair::new();
508
509 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
511
512 let result = svm.send_instruction(ix, &[&payer]).unwrap();
513
514 assert!(result.is_success());
515 assert_eq!(result.error(), None);
516 result.assert_success();
517 }
518
519 #[test]
520 fn test_transaction_result_has_log() {
521 let mut svm = LiteSVM::new();
522 let payer = svm.create_funded_account(10_000_000_000).unwrap();
523 let recipient = Keypair::new();
524
525 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
526 let result = svm.send_instruction(ix, &[&payer]).unwrap();
527
528 assert!(result.has_log("invoke"));
530 }
531
532 #[test]
533 fn test_transaction_result_find_log() {
534 let mut svm = LiteSVM::new();
535 let payer = svm.create_funded_account(10_000_000_000).unwrap();
536 let recipient = Keypair::new();
537
538 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
539 let result = svm.send_instruction(ix, &[&payer]).unwrap();
540
541 let log = result.find_log("invoke");
543 assert!(log.is_some());
544 }
545
546 #[test]
547 fn test_transaction_result_compute_units() {
548 let mut svm = LiteSVM::new();
549 let payer = svm.create_funded_account(10_000_000_000).unwrap();
550 let recipient = Keypair::new();
551
552 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
553 let result = svm.send_instruction(ix, &[&payer]).unwrap();
554
555 let cu = result.compute_units();
557 assert!(cu > 0);
558 assert!(cu < 1_000_000); }
560
561 #[test]
562 fn test_transaction_result_logs() {
563 let mut svm = LiteSVM::new();
564 let payer = svm.create_funded_account(10_000_000_000).unwrap();
565 let recipient = Keypair::new();
566
567 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
568 let result = svm.send_instruction(ix, &[&payer]).unwrap();
569
570 let logs = result.logs();
571 assert!(!logs.is_empty());
572 }
573
574 #[test]
575 fn test_transaction_result_inner() {
576 let mut svm = LiteSVM::new();
577 let payer = svm.create_funded_account(10_000_000_000).unwrap();
578 let recipient = Keypair::new();
579
580 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
581 let result = svm.send_instruction(ix, &[&payer]).unwrap();
582
583 let _inner = result.inner();
585 assert!(_inner.compute_units_consumed > 0);
586 }
587
588 #[test]
589 fn test_transaction_result_failure() {
590 let mut svm = LiteSVM::new();
591 let payer = Keypair::new(); let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
595 let result = svm.send_instruction(ix, &[&payer]).unwrap();
596
597 assert!(!result.is_success());
598 assert!(result.error().is_some());
599 }
600
601 #[test]
602 fn test_transaction_result_assert_failure() {
603 let mut svm = LiteSVM::new();
604 let payer = Keypair::new(); let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
607 let result = svm.send_instruction(ix, &[&payer]).unwrap();
608
609 result.assert_failure();
611 }
612
613 #[test]
614 #[should_panic(expected = "Expected transaction to fail")]
615 fn test_transaction_result_assert_failure_on_success() {
616 let mut svm = LiteSVM::new();
617 let payer = svm.create_funded_account(10_000_000_000).unwrap();
618
619 let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
620 let result = svm.send_instruction(ix, &[&payer]).unwrap();
621
622 result.assert_failure();
624 }
625
626 #[test]
627 fn test_transaction_result_assert_error() {
628 let mut svm = LiteSVM::new();
629 let payer = Keypair::new(); let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
632 let result = svm.send_instruction(ix, &[&payer]).unwrap();
633
634 result.assert_error("AccountNotFound");
636 }
637
638 #[test]
639 #[should_panic(expected = "Transaction failed with unexpected error")]
640 fn test_transaction_result_assert_error_wrong_message() {
641 let mut svm = LiteSVM::new();
642 let payer = Keypair::new(); let ix = system_instruction::transfer(&payer.pubkey(), &Keypair::new().pubkey(), 1_000_000);
645 let result = svm.send_instruction(ix, &[&payer]).unwrap();
646
647 result.assert_error("this error does not exist");
649 }
650
651 #[test]
652 fn test_send_multiple_instructions() {
653 let mut svm = LiteSVM::new();
654 let payer = svm.create_funded_account(10_000_000_000).unwrap();
655 let recipient1 = Keypair::new();
656 let recipient2 = Keypair::new();
657
658 let ix1 = system_instruction::transfer(&payer.pubkey(), &recipient1.pubkey(), 1_000_000);
660 let ix2 = system_instruction::transfer(&payer.pubkey(), &recipient2.pubkey(), 2_000_000);
661
662 let result = svm.send_instructions(&[ix1, ix2], &[&payer]).unwrap();
663 result.assert_success();
664
665 let balance1 = svm.get_balance(&recipient1.pubkey()).unwrap();
667 let balance2 = svm.get_balance(&recipient2.pubkey()).unwrap();
668 assert_eq!(balance1, 1_000_000);
669 assert_eq!(balance2, 2_000_000);
670 }
671
672 #[test]
673 fn test_send_instruction_no_signers() {
674 let mut svm = LiteSVM::new();
675 let payer = Keypair::new();
676 let recipient = Keypair::new();
677
678 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
679
680 let result = svm.send_instruction(ix, &[]);
682 assert!(result.is_err());
683 match result {
684 Err(TransactionError::BuildError(msg)) => {
685 assert!(msg.contains("No signers"));
686 }
687 _ => panic!("Expected BuildError"),
688 }
689 }
690
691 #[test]
692 fn test_send_instructions_no_signers() {
693 let mut svm = LiteSVM::new();
694 let payer = Keypair::new();
695 let recipient = Keypair::new();
696
697 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
698
699 let result = svm.send_instructions(&[ix], &[]);
701 assert!(result.is_err());
702 }
703
704 #[test]
705 fn test_transaction_result_debug() {
706 let mut svm = LiteSVM::new();
707 let payer = svm.create_funded_account(10_000_000_000).unwrap();
708 let recipient = Keypair::new();
709
710 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
711 let result = svm.send_instruction(ix, &[&payer]).unwrap();
712
713 let debug_str = format!("{:?}", result);
715 assert!(debug_str.contains("TransactionResult"));
716 }
717
718 #[test]
719 fn test_transaction_result_print_logs() {
720 let mut svm = LiteSVM::new();
721 let payer = svm.create_funded_account(10_000_000_000).unwrap();
722 let recipient = Keypair::new();
723
724 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
725 let result = svm.send_instruction(ix, &[&payer]).unwrap();
726
727 result.print_logs();
729 }
730
731 #[test]
732 fn test_send_transaction_result() {
733 let mut svm = LiteSVM::new();
734 let payer = svm.create_funded_account(10_000_000_000).unwrap();
735 let recipient = Keypair::new();
736
737 let ix = system_instruction::transfer(&payer.pubkey(), &recipient.pubkey(), 1_000_000);
738 let tx = Transaction::new_signed_with_payer(
739 &[ix],
740 Some(&payer.pubkey()),
741 &[&payer],
742 svm.latest_blockhash(),
743 );
744
745 let result = svm.send_transaction_result(tx).unwrap();
746 result.assert_success();
747 }
748}