1use anchor_lang::prelude::sysvar::SysvarId;
2use litesvm::LiteSVM;
3use std::collections::{HashMap, HashSet};
4use std::rc::Rc;
5use std::sync::Arc;
6
7pub use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
9
10pub type FastHashSet<T> = FxHashSet<T>;
12pub type FastHashMap<K, V> = FxHashMap<K, V>;
14use solana_account::Account;
15use solana_keypair::Keypair;
16use solana_message::inner_instruction::InnerInstructionsList;
17use solana_pubkey::Pubkey;
18use solana_signature::Signature;
19use solana_signer::Signer;
20use solana_transaction_context::TransactionReturnData;
21use solana_transaction_error::TransactionError;
22
23pub use crate::account_builders::AccountBuilderBase;
25pub use crate::account_builders::GenericAccountBuilder;
26pub use crate::account_builders::MintAccountBuilder;
27pub use crate::account_builders::TokenAccountBuilder;
28pub use crate::instruction_builder::InstructionBuilder;
29pub use crate::mock_oracles::{
30 MockPythOracleBuilder, PriceFeedMessage, PriceUpdateV2, VerificationLevel,
31 DEFAULT_PYTH_RECEIVER_ID, PYTH_DISCRIMINATOR,
32};
33pub use crate::program_builder::ProgramBuilder;
34pub use crate::transaction_builder::TransactionBuilder;
35use anchor_lang::prelude::{Clock, Rent};
36use anchor_lang::solana_program::instruction::Instruction;
37use anchor_lang::solana_program::program_pack::Pack;
38use anchor_lang::solana_program::system_program;
39use anchor_lang::{AnchorDeserialize, AnchorSerialize, Discriminator};
40use anyhow::{Context, Result};
41use spl_token::solana_program::program_option::COption;
42
43mod account_builders;
44mod instruction_builder;
45mod program_builder;
46pub mod schema;
47pub mod snapshot;
48
49pub use schema::{register_account_schemas, AccountSchema};
51mod transaction_builder;
52
53pub mod coverage;
55
56pub use coverage::{
58 build_cached_analysis, extract_functions, generate_bytecode_lcov, generate_coverage_html,
59 generate_coverage_html_cached, generate_source_lcov,
60};
61pub use coverage::{build_dwarf_source_map, DwarfSourceMap, SourceLocation};
62pub use coverage::{
63 CachedFunctionInfo, CachedProgramAnalysis, CoverageStats, CoverageWriteStats, FunctionInfo,
64 ReachableAnalysis,
65};
66
67pub use litesvm::InvocationInspectCallback;
68pub use litesvm;
70
71pub use serde_json;
73
74#[cfg(feature = "rpc-clone")]
76pub mod rpc_clone;
77#[cfg(feature = "rpc-clone")]
78pub use rpc_clone::AccountCloner;
79
80pub static TOTAL_ACTIONS_DISPATCHED: std::sync::atomic::AtomicU64 =
87 std::sync::atomic::AtomicU64::new(0);
88
89pub static TOTAL_ACTIONS_SUCCEEDED: std::sync::atomic::AtomicU64 =
92 std::sync::atomic::AtomicU64::new(0);
93
94pub static TOTAL_ACTION_VARIANTS: std::sync::atomic::AtomicUsize =
97 std::sync::atomic::AtomicUsize::new(0);
98
99#[inline]
103pub fn increment_action_count() {
104 TOTAL_ACTIONS_DISPATCHED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
105 ITERATION_DISPATCH_COUNT.with(|c| c.set(c.get() + 1));
106}
107
108#[inline]
110pub fn increment_action_success_count() {
111 TOTAL_ACTIONS_SUCCEEDED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
112}
113
114use serde::{Deserialize, Serialize};
119use std::cell::{Cell, RefCell};
120
121thread_local! {
122 static VIOLATION: RefCell<Option<String>> = RefCell::new(None);
124 static CURRENT_INSTRUCTION: RefCell<Option<String>> = RefCell::new(None);
126 static ACTION_HISTORY: RefCell<Vec<ActionRecord>> = RefCell::new(Vec::new());
128 static CURRENT_TEST_NAME: RefCell<Option<String>> = RefCell::new(None);
129 static CURRENT_ITERATION: RefCell<u64> = RefCell::new(0);
130 static TOTAL_ACTIONS_IN_SEQUENCE: RefCell<usize> = RefCell::new(0);
132 static VIOLATION_ACTION_INDEX: RefCell<Option<usize>> = RefCell::new(None);
133 static LAST_ERROR_CODE: Cell<Option<u32>> = const { Cell::new(None) };
135 static ITERATION_DISPATCH_COUNT: Cell<u64> = const { Cell::new(0) };
138 static SUCCEEDED_VARIANTS: RefCell<HashSet<usize>> = RefCell::new(HashSet::new());
140 static FUZZ_DEBUG: Cell<bool> = Cell::new(false);
142 static STATEFUL_CHAIN_MODE: Cell<bool> = const { Cell::new(false) };
144 static CORPUS_LOADING: Cell<bool> = const { Cell::new(false) };
146}
147
148thread_local! {
150 static FUZZ_DEBUG_INIT: Cell<bool> = const { Cell::new(false) };
151}
152
153#[inline]
155fn is_fuzz_debug() -> bool {
156 FUZZ_DEBUG_INIT.with(|init| {
157 if !init.get() {
158 let val = std::env::var("FUZZ_DEBUG").is_ok();
159 FUZZ_DEBUG.with(|c| c.set(val));
160 init.set(true);
161 val
162 } else {
163 FUZZ_DEBUG.with(|c| c.get())
164 }
165 })
166}
167
168#[inline]
171pub fn set_stateful_chain_mode(v: bool) {
172 STATEFUL_CHAIN_MODE.with(|f| f.set(v));
173}
174
175#[inline]
177pub fn is_stateful_chain_mode() -> bool {
178 STATEFUL_CHAIN_MODE.with(|f| f.get())
179}
180
181#[inline]
183pub fn is_debug_replay() -> bool {
184 std::env::var("FUZZ_DEBUG_REPLAY").is_ok()
186}
187
188pub fn compute_svm_debug_hash(
191 svm: &litesvm::LiteSVM,
192 tracked_accounts: &[solana_pubkey::Pubkey],
193) -> (u64, u64) {
194 use anchor_lang::prelude::Clock;
195 use rustc_hash::FxHasher;
196 use std::hash::{Hash, Hasher};
197 let mut hasher = FxHasher::default();
198 let clock: Clock = svm.get_sysvar();
199 let slot = clock.slot;
200 clock.slot.hash(&mut hasher);
201 clock.epoch.hash(&mut hasher);
202 let mut entries: Vec<(u64, usize, u64)> = Vec::with_capacity(tracked_accounts.len());
203 for pubkey in tracked_accounts {
204 if let Some(acct) = svm.get_account(pubkey) {
205 let mut dh = FxHasher::default();
206 acct.data.hash(&mut dh);
207 entries.push((acct.lamports, acct.data.len(), dh.finish()));
208 }
209 }
210 entries.sort();
211 for (lamports, data_len, data_hash) in &entries {
212 lamports.hash(&mut hasher);
213 data_len.hash(&mut hasher);
214 data_hash.hash(&mut hasher);
215 }
216 (hasher.finish(), slot)
217}
218
219pub fn set_corpus_loading(v: bool) {
221 CORPUS_LOADING.with(|f| f.set(v));
222}
223
224#[inline]
226pub fn is_corpus_loading() -> bool {
227 CORPUS_LOADING.with(|f| f.get())
228}
229
230#[doc(hidden)]
231pub fn set_current_instruction(name: Option<String>) {
233 CURRENT_INSTRUCTION.with(|c| {
234 *c.borrow_mut() = name;
235 });
236}
237
238pub fn get_current_instruction() -> Option<String> {
240 CURRENT_INSTRUCTION.with(|c| c.borrow().clone())
241}
242
243pub fn set_last_error_code(code: Option<u32>) {
245 LAST_ERROR_CODE.with(|c| c.set(code));
246}
247
248fn take_last_error_code() -> Option<u32> {
250 LAST_ERROR_CODE.with(|c| c.replace(None))
251}
252
253#[derive(Clone, Debug, Serialize, Deserialize)]
259pub struct FieldDelta {
260 pub field: String,
261 pub old_value: String,
262 pub new_value: String,
263}
264
265#[derive(Clone, Debug, Serialize, Deserialize)]
267pub struct ActionRecord {
268 pub name: String,
270 pub params: serde_json::Value,
272 pub success: bool,
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub error_code: Option<u32>,
277}
278
279#[derive(Clone, Debug, Serialize, Deserialize)]
281pub struct CrashMetadata {
282 pub test_name: String,
284 pub timestamp: String,
286 pub iteration: u64,
288 #[serde(skip_serializing_if = "Option::is_none")]
290 pub seed: Option<u64>,
291 pub actions: Vec<ActionRecord>,
293}
294
295pub fn set_current_test_name(name: &str) {
297 CURRENT_TEST_NAME.with(|t| {
298 *t.borrow_mut() = Some(name.to_string());
299 });
300}
301
302pub fn get_current_test_name() -> Option<String> {
304 CURRENT_TEST_NAME.with(|t| t.borrow().clone())
305}
306
307pub fn set_current_iteration(iteration: u64) {
309 CURRENT_ITERATION.with(|i| {
310 *i.borrow_mut() = iteration;
311 });
312}
313
314pub fn get_current_iteration() -> u64 {
316 CURRENT_ITERATION.with(|i| *i.borrow())
317}
318
319pub fn push_action_record(name: &str, params: serde_json::Value, success: bool) {
321 let error_code = take_last_error_code();
322 ACTION_HISTORY.with(|h| {
323 h.borrow_mut().push(ActionRecord {
324 name: name.to_string(),
325 params,
326 success,
327 error_code,
328 });
329 });
330}
331
332pub fn push_action_record_lite(name: &str, success: bool) {
337 let error_code = take_last_error_code();
338 ACTION_HISTORY.with(|h| {
339 h.borrow_mut().push(ActionRecord {
340 name: name.to_string(),
341 params: serde_json::Value::Null,
342 success,
343 error_code,
344 });
345 });
346}
347
348pub fn backfill_action_params(index: usize, params: serde_json::Value) {
350 ACTION_HISTORY.with(|h| {
351 let mut history = h.borrow_mut();
352 if let Some(record) = history.get_mut(index) {
353 record.params = params;
354 }
355 });
356}
357
358pub fn get_action_history() -> Vec<ActionRecord> {
360 ACTION_HISTORY.with(|h| h.borrow().clone())
361}
362
363#[inline]
366pub fn get_first_action_success() -> Option<bool> {
367 ACTION_HISTORY.with(|h| h.borrow().first().map(|r| r.success))
368}
369
370pub fn clear_action_history() {
372 ACTION_HISTORY.with(|h| h.borrow_mut().clear());
373}
374
375#[inline]
377pub fn reset_iteration_dispatch_count() {
378 ITERATION_DISPATCH_COUNT.with(|c| c.set(0));
379}
380
381#[inline]
384pub fn get_iteration_dispatch_count() -> u64 {
385 ITERATION_DISPATCH_COUNT.with(|c| c.get().max(1))
386}
387
388thread_local! {
393 pub(crate) static SEND_BATCH_PRE_NS: Cell<u64> = const { Cell::new(0) }; pub(crate) static SEND_BATCH_SVM_NS: Cell<u64> = const { Cell::new(0) }; pub(crate) static SEND_BATCH_POST_NS: Cell<u64> = const { Cell::new(0) }; pub(crate) static SEND_TX_BLOCKHASH_NS: Cell<u64> = const { Cell::new(0) }; pub(crate) static SEND_TX_SIGN_NS: Cell<u64> = const { Cell::new(0) }; pub(crate) static SEND_TX_EXEC_NS: Cell<u64> = const { Cell::new(0) }; }
401
402#[inline]
404pub fn reset_send_batch_timers() {
405 SEND_BATCH_PRE_NS.with(|c| c.set(0));
406 SEND_BATCH_SVM_NS.with(|c| c.set(0));
407 SEND_BATCH_POST_NS.with(|c| c.set(0));
408 SEND_TX_BLOCKHASH_NS.with(|c| c.set(0));
409 SEND_TX_SIGN_NS.with(|c| c.set(0));
410 SEND_TX_EXEC_NS.with(|c| c.set(0));
411}
412
413#[inline]
415pub fn get_send_batch_timers() -> (u64, u64, u64) {
416 (
417 SEND_BATCH_PRE_NS.with(|c| c.get()),
418 SEND_BATCH_SVM_NS.with(|c| c.get()),
419 SEND_BATCH_POST_NS.with(|c| c.get()),
420 )
421}
422
423#[inline]
425pub fn get_send_tx_breakdown() -> (u64, u64, u64) {
426 (
427 SEND_TX_BLOCKHASH_NS.with(|c| c.get()),
428 SEND_TX_SIGN_NS.with(|c| c.get()),
429 SEND_TX_EXEC_NS.with(|c| c.get()),
430 )
431}
432
433pub fn set_total_actions(count: usize) {
435 TOTAL_ACTIONS_IN_SEQUENCE.with(|t| *t.borrow_mut() = count);
436}
437
438pub fn set_violation_action_index(idx: usize) {
440 VIOLATION_ACTION_INDEX.with(|v| {
441 let mut guard = v.borrow_mut();
442 if guard.is_none() {
443 *guard = Some(idx);
444 }
445 });
446}
447
448pub fn get_violation_action_index() -> Option<usize> {
450 VIOLATION_ACTION_INDEX.with(|v| *v.borrow())
451}
452
453pub fn clear_violation_tracking() {
455 TOTAL_ACTIONS_IN_SEQUENCE.with(|t| *t.borrow_mut() = 0);
456 VIOLATION_ACTION_INDEX.with(|v| *v.borrow_mut() = None);
457}
458
459#[inline]
462pub fn clear_iteration_state() {
463 clear_action_history();
464 clear_violation_tracking();
465}
466
467pub fn has_variant_succeeded(variant_idx: usize) -> bool {
473 SUCCEEDED_VARIANTS.with(|s| s.borrow().contains(&variant_idx))
474}
475
476pub fn mark_variant_succeeded(variant_idx: usize) {
478 SUCCEEDED_VARIANTS.with(|s| {
479 s.borrow_mut().insert(variant_idx);
480 });
481}
482
483pub fn succeeded_variant_count() -> usize {
485 SUCCEEDED_VARIANTS.with(|s| s.borrow().len())
486}
487
488pub fn parse_action_desc(desc: &str) -> ActionRecord {
492 let (body, success) = if let Some(body) = desc.strip_suffix(" -> OK") {
493 (body, true)
494 } else if let Some(body) = desc.strip_suffix(" -> FAIL") {
495 (body, false)
496 } else {
497 (desc, true)
498 };
499 let (name, params) = if let Some(paren) = body.find('(') {
500 let name = &body[..paren];
501 let params_str = body[paren + 1..].trim_end_matches(')');
502 let mut map = serde_json::Map::new();
503 for part in params_str.split(", ") {
504 if let Some(eq) = part.find('=') {
505 let key = &part[..eq];
506 let val_str = &part[eq + 1..];
507 let val = if val_str == "null" {
508 serde_json::Value::Null
509 } else if val_str == "true" {
510 serde_json::Value::Bool(true)
511 } else if val_str == "false" {
512 serde_json::Value::Bool(false)
513 } else if let Ok(n) = val_str.parse::<u64>() {
514 serde_json::Value::Number(n.into())
515 } else if let Ok(n) = val_str.parse::<i64>() {
516 serde_json::Value::Number(n.into())
517 } else {
518 serde_json::Value::String(val_str.to_string())
519 };
520 map.insert(key.to_string(), val);
521 }
522 }
523 (name.to_string(), serde_json::Value::Object(map))
524 } else {
525 (
526 body.to_string(),
527 serde_json::Value::Object(serde_json::Map::new()),
528 )
529 };
530 ActionRecord {
531 name,
532 params,
533 success,
534 error_code: None,
535 }
536}
537
538pub fn build_crash_metadata(seed: Option<u64>) -> CrashMetadata {
540 let timestamp = chrono_lite_timestamp();
541 CrashMetadata {
542 test_name: get_current_test_name().unwrap_or_else(|| "unknown".to_string()),
543 timestamp,
544 iteration: get_current_iteration(),
545 seed,
546 actions: get_action_history(),
547 }
548}
549
550pub fn format_action_sequence() -> String {
554 use std::fmt::Write;
555
556 let history = get_action_history();
557 let total_actions = TOTAL_ACTIONS_IN_SEQUENCE.with(|t| *t.borrow());
558 let violation_idx = get_violation_action_index();
559
560 if history.is_empty() && total_actions == 0 {
561 return String::new();
562 }
563
564 let executed = history.len();
565 let skipped = total_actions.saturating_sub(executed);
566
567 let mut out = String::new();
568 let _ = writeln!(
569 out,
570 "\n=== FUZZ SEQUENCE ({} executed, {} skipped) ===",
571 executed, skipped
572 );
573
574 for (i, record) in history.iter().enumerate() {
575 let params_str = if let serde_json::Value::Object(map) = &record.params {
576 map.iter()
577 .map(|(k, v)| format!("{}={}", k, format_json_value(v)))
578 .collect::<Vec<_>>()
579 .join(", ")
580 } else {
581 String::new()
582 };
583
584 let status = if record.success { "OK" } else { "FAIL" };
585 let violation_marker = if violation_idx == Some(i) {
586 " [VIOLATION]"
587 } else {
588 ""
589 };
590
591 if params_str.is_empty() {
592 let _ = writeln!(
593 out,
594 " {}. {} -> {}{}",
595 i + 1,
596 record.name,
597 status,
598 violation_marker
599 );
600 } else {
601 let _ = writeln!(
602 out,
603 " {}. {}({}) -> {}{}",
604 i + 1,
605 record.name,
606 params_str,
607 status,
608 violation_marker
609 );
610 }
611 }
612
613 if skipped > 0 {
614 let _ = writeln!(
615 out,
616 " ... {} action(s) not executed (stopped on violation)",
617 skipped
618 );
619 }
620
621 let _ = writeln!(out, "================================");
622 out
623}
624
625pub fn print_action_sequence() {
626 let s = format_action_sequence();
627 if !s.is_empty() {
628 eprint!("{}", s);
629 }
630}
631
632pub fn format_json_value(v: &serde_json::Value) -> String {
634 match v {
635 serde_json::Value::Null => "null".to_string(),
636 serde_json::Value::Bool(b) => b.to_string(),
637 serde_json::Value::Number(n) => n.to_string(),
638 serde_json::Value::String(s) => format!("\"{}\"", s),
639 serde_json::Value::Array(arr) => {
640 let items: Vec<String> = arr.iter().map(format_json_value).collect();
641 format!("[{}]", items.join(", "))
642 }
643 serde_json::Value::Object(obj) => {
644 let items: Vec<String> = obj
645 .iter()
646 .map(|(k, v)| format!("{}: {}", k, format_json_value(v)))
647 .collect();
648 format!("{{{}}}", items.join(", "))
649 }
650 }
651}
652
653pub fn format_last_action_oneline() -> String {
657 let history = get_action_history();
658 match history.last() {
659 Some(record) => {
660 let params_str = if let serde_json::Value::Object(map) = &record.params {
661 map.iter()
662 .map(|(k, v)| format!("{}={}", k, format_json_value(v)))
663 .collect::<Vec<_>>()
664 .join(", ")
665 } else {
666 String::new()
667 };
668 let status = if record.success { "OK" } else { "FAIL" };
669 if params_str.is_empty() {
670 format!("{} -> {}", record.name, status)
671 } else {
672 format!("{}({}) -> {}", record.name, params_str, status)
673 }
674 }
675 None => String::new(),
676 }
677}
678
679pub fn format_all_actions_oneline() -> String {
682 let history = get_action_history();
683 let mut lines = Vec::with_capacity(history.len());
684 for record in &history {
685 let params_str = if let serde_json::Value::Object(map) = &record.params {
686 map.iter()
687 .map(|(k, v)| format!("{}={}", k, format_json_value(v)))
688 .collect::<Vec<_>>()
689 .join(", ")
690 } else {
691 String::new()
692 };
693 let status = if record.success { "OK" } else { "FAIL" };
694 if params_str.is_empty() {
695 lines.push(format!("{} -> {}", record.name, status));
696 } else {
697 lines.push(format!("{}({}) -> {}", record.name, params_str, status));
698 }
699 }
700 lines.join("\n")
701}
702
703pub fn write_crash_metadata(
705 crash_dir: &str,
706 input_hash: u64,
707 seed: Option<u64>,
708 input_bytes: &[u8],
709) {
710 write_crash_metadata_with_actions(crash_dir, input_hash, seed, input_bytes, None)
711}
712
713pub fn write_crash_metadata_with_actions(
716 crash_dir: &str,
717 input_hash: u64,
718 seed: Option<u64>,
719 input_bytes: &[u8],
720 full_actions: Option<Vec<ActionRecord>>,
721) {
722 let crash_id = format!("crash_{:016x}", input_hash);
723 let mut metadata = build_crash_metadata(seed);
724 if let Some(actions) = full_actions {
725 metadata.actions = actions;
726 }
727 let meta_dir = std::env::var("FUZZ_META_DIR").unwrap_or_else(|_| crash_dir.to_string());
728 let meta_filename = format!("{}/{}.meta.json", meta_dir, crash_id);
729 let input_filename = format!("{}/{}", crash_dir, crash_id);
730
731 if let Err(e) = std::fs::write(&input_filename, input_bytes) {
733 eprintln!(
734 "[META] Failed to write crash input {}: {}",
735 input_filename, e
736 );
737 }
738
739 match serde_json::to_string_pretty(&metadata) {
741 Ok(json) => {
742 if let Err(e) = std::fs::write(&meta_filename, json) {
743 eprintln!("[META] Failed to write {}: {}", meta_filename, e);
744 }
745 }
746 Err(e) => {
747 eprintln!("[META] Failed to serialize metadata: {}", e);
748 }
749 }
750}
751
752pub fn write_crash_metadata_for_id(crash_dir: &str, crash_id: &str, seed: Option<u64>) {
754 let metadata = build_crash_metadata(seed);
755 let meta_filename = format!("{}/{}.meta.json", crash_dir, crash_id);
756
757 match serde_json::to_string_pretty(&metadata) {
758 Ok(json) => {
759 if let Err(e) = std::fs::write(&meta_filename, json) {
760 eprintln!("[META] Failed to write {}: {}", meta_filename, e);
761 }
762 }
763 Err(e) => {
764 eprintln!("[META] Failed to serialize metadata: {}", e);
765 }
766 }
767}
768
769pub trait IntoActionSuccess {
776 fn into_success(self) -> bool;
777}
778
779impl IntoActionSuccess for () {
780 fn into_success(self) -> bool {
781 true
782 }
783}
784
785impl<T, E> IntoActionSuccess for Result<T, E> {
786 fn into_success(self) -> bool {
787 self.is_ok()
788 }
789}
790
791impl IntoActionSuccess for bool {
792 fn into_success(self) -> bool {
793 self
794 }
795}
796
797fn chrono_lite_timestamp() -> String {
799 use std::time::{SystemTime, UNIX_EPOCH};
800 let now = SystemTime::now()
801 .duration_since(UNIX_EPOCH)
802 .unwrap_or_default();
803 let secs = now.as_secs();
804 let days_since_epoch = secs / 86400;
807 let time_of_day = secs % 86400;
808 let hours = time_of_day / 3600;
809 let minutes = (time_of_day % 3600) / 60;
810 let seconds = time_of_day % 60;
811
812 let mut year = 1970;
814 let mut remaining_days = days_since_epoch as i64;
815 loop {
816 let days_in_year = if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
817 366
818 } else {
819 365
820 };
821 if remaining_days < days_in_year {
822 break;
823 }
824 remaining_days -= days_in_year;
825 year += 1;
826 }
827
828 let days_in_months = if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
829 [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
830 } else {
831 [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
832 };
833
834 let mut month = 1;
835 for days_in_month in days_in_months {
836 if remaining_days < days_in_month as i64 {
837 break;
838 }
839 remaining_days -= days_in_month as i64;
840 month += 1;
841 }
842 let day = remaining_days + 1;
843
844 format!(
845 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
846 year, month, day, hours, minutes, seconds
847 )
848}
849
850use std::sync::OnceLock;
855
856static DISCRIMINATOR_MAP: OnceLock<(usize, HashMap<Vec<u8>, String>)> = OnceLock::new();
861
862pub fn register_instruction_discriminators(discriminators: &[(&str, Vec<u8>)]) {
875 let disc_len = discriminators.first().map(|(_, d)| d.len()).unwrap_or(8);
877 let map: HashMap<Vec<u8>, String> = discriminators
878 .iter()
879 .map(|(name, disc)| (disc.clone(), name.to_string()))
880 .collect();
881 let _ = DISCRIMINATOR_MAP.set((disc_len, map));
882}
883
884pub fn lookup_instruction_by_discriminator(instruction_data: &[u8]) -> Option<String> {
889 let (disc_len, map) = DISCRIMINATOR_MAP.get()?;
890 if instruction_data.len() < *disc_len {
891 return None;
892 }
893 let disc = instruction_data[..*disc_len].to_vec();
894 map.get(&disc).cloned()
895}
896
897pub fn get_registered_discriminators() -> Vec<(String, Vec<u8>)> {
899 DISCRIMINATOR_MAP
900 .get()
901 .map(|(_, map)| map.iter().map(|(k, v)| (v.clone(), k.clone())).collect())
902 .unwrap_or_default()
903}
904
905pub fn record_violation(msg: String) {
909 VIOLATION.with(|v| {
910 let mut guard = v.borrow_mut();
912 if guard.is_none() {
913 *guard = Some(msg);
914 }
915 });
916}
917
918pub fn take_violation() -> Option<String> {
920 VIOLATION.with(|v| v.borrow_mut().take())
921}
922
923pub fn has_violation() -> bool {
925 VIOLATION.with(|v| v.borrow().is_some())
926}
927
928#[macro_export]
930macro_rules! fuzz_assert {
931 ($cond:expr $(,)?) => {
932 if !($cond) {
933 $crate::record_violation(format!(
934 "Assertion failed: {} at {}:{}",
935 stringify!($cond), file!(), line!()
936 ));
937 }
938 };
939 ($cond:expr, $($arg:tt)+) => {
940 if !($cond) {
941 $crate::record_violation(format!($($arg)+));
942 }
943 };
944}
945
946#[macro_export]
948macro_rules! fuzz_assert_eq {
949 ($left:expr, $right:expr $(,)?) => {
950 if $left != $right {
951 $crate::record_violation(format!(
952 "Assertion failed: {} == {} ({:?} != {:?}) at {}:{}",
953 stringify!($left), stringify!($right), $left, $right, file!(), line!()
954 ));
955 }
956 };
957 ($left:expr, $right:expr, $($arg:tt)+) => {
958 if $left != $right {
959 $crate::record_violation(format!($($arg)+));
960 }
961 };
962}
963
964#[macro_export]
966macro_rules! fuzz_assert_ne {
967 ($left:expr, $right:expr $(,)?) => {
968 if $left == $right {
969 $crate::record_violation(format!(
970 "Assertion failed: {} != {} ({:?} == {:?}) at {}:{}",
971 stringify!($left), stringify!($right), $left, $right, file!(), line!()
972 ));
973 }
974 };
975 ($left:expr, $right:expr, $($arg:tt)+) => {
976 if $left == $right {
977 $crate::record_violation(format!($($arg)+));
978 }
979 };
980}
981
982#[macro_export]
984macro_rules! fuzz_assert_lt {
985 ($left:expr, $right:expr $(,)?) => {
986 if !($left < $right) {
987 $crate::record_violation(format!(
988 "Assertion failed: {} < {} ({:?} >= {:?}) at {}:{}",
989 stringify!($left), stringify!($right), $left, $right, file!(), line!()
990 ));
991 }
992 };
993 ($left:expr, $right:expr, $($arg:tt)+) => {
994 if !($left < $right) {
995 $crate::record_violation(format!($($arg)+));
996 }
997 };
998}
999
1000#[macro_export]
1002macro_rules! fuzz_assert_le {
1003 ($left:expr, $right:expr $(,)?) => {
1004 if !($left <= $right) {
1005 $crate::record_violation(format!(
1006 "Assertion failed: {} <= {} ({:?} > {:?}) at {}:{}",
1007 stringify!($left), stringify!($right), $left, $right, file!(), line!()
1008 ));
1009 }
1010 };
1011 ($left:expr, $right:expr, $($arg:tt)+) => {
1012 if !($left <= $right) {
1013 $crate::record_violation(format!($($arg)+));
1014 }
1015 };
1016}
1017
1018#[macro_export]
1020macro_rules! fuzz_assert_gt {
1021 ($left:expr, $right:expr $(,)?) => {
1022 if !($left > $right) {
1023 $crate::record_violation(format!(
1024 "Assertion failed: {} > {} ({:?} <= {:?}) at {}:{}",
1025 stringify!($left), stringify!($right), $left, $right, file!(), line!()
1026 ));
1027 }
1028 };
1029 ($left:expr, $right:expr, $($arg:tt)+) => {
1030 if !($left > $right) {
1031 $crate::record_violation(format!($($arg)+));
1032 }
1033 };
1034}
1035
1036#[macro_export]
1038macro_rules! fuzz_assert_ge {
1039 ($left:expr, $right:expr $(,)?) => {
1040 if !($left >= $right) {
1041 $crate::record_violation(format!(
1042 "Assertion failed: {} >= {} ({:?} < {:?}) at {}:{}",
1043 stringify!($left), stringify!($right), $left, $right, file!(), line!()
1044 ));
1045 }
1046 };
1047 ($left:expr, $right:expr, $($arg:tt)+) => {
1048 if !($left >= $right) {
1049 $crate::record_violation(format!($($arg)+));
1050 }
1051 };
1052}
1053
1054#[macro_export]
1056macro_rules! fuzz_assert_approx_eq {
1057 ($left:expr, $right:expr, $delta:expr $(,)?) => {{
1058 let diff = if $left > $right { $left - $right } else { $right - $left };
1059 if diff > $delta {
1060 $crate::record_violation(format!(
1061 "Assertion failed: |{} - {}| <= {} (|{:?} - {:?}| = {:?} > {:?}) at {}:{}",
1062 stringify!($left), stringify!($right), stringify!($delta),
1063 $left, $right, diff, $delta, file!(), line!()
1064 ));
1065 }
1066 }};
1067 ($left:expr, $right:expr, $delta:expr, $($arg:tt)+) => {{
1068 let diff = if $left > $right { $left - $right } else { $right - $left };
1069 if diff > $delta {
1070 $crate::record_violation(format!($($arg)+));
1071 }
1072 }};
1073}
1074
1075mod mock_oracles;
1077
1078#[derive(Debug, Clone)]
1080pub enum TxOutcome {
1081 Success {
1083 compute_units: u64,
1084 logs: Vec<String>,
1085 signature: Signature,
1086 inner_instructions: InnerInstructionsList,
1087 return_data: TransactionReturnData,
1088 fee: u64,
1089 },
1090 ProgramError {
1092 error: TransactionError,
1094 error_code: Option<u32>,
1096 instruction_index: Option<u8>,
1098 logs: Vec<String>,
1099 signature: Signature,
1100 inner_instructions: InnerInstructionsList,
1101 return_data: TransactionReturnData,
1102 fee: u64,
1103 },
1104}
1105
1106#[derive(Debug, Clone)]
1108pub struct TxError {
1109 pub error: TransactionError,
1110 pub error_code: Option<u32>,
1111 pub instruction_index: Option<u8>,
1112 pub logs: Vec<String>,
1113 pub signature: Signature,
1114 pub inner_instructions: InnerInstructionsList,
1115 pub return_data: TransactionReturnData,
1116 pub fee: u64,
1117}
1118
1119impl std::error::Error for TxError {}
1120
1121impl std::fmt::Display for TxError {
1122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1123 write!(f, "Transaction failed")?;
1124 if let Some(code) = self.error_code {
1125 write!(f, " (error code: {})", code)?;
1126 }
1127 if let Some(idx) = self.instruction_index {
1128 write!(f, " at instruction {}", idx)?;
1129 }
1130 Ok(())
1131 }
1132}
1133
1134impl TxOutcome {
1135 pub fn is_success(&self) -> bool {
1136 matches!(self, TxOutcome::Success { .. })
1137 }
1138
1139 pub fn is_error(&self) -> bool {
1140 matches!(self, TxOutcome::ProgramError { .. })
1141 }
1142
1143 pub fn error_code(&self) -> Option<u32> {
1144 match self {
1145 TxOutcome::ProgramError { error_code, .. } => *error_code,
1146 _ => None,
1147 }
1148 }
1149
1150 pub fn logs(&self) -> &[String] {
1151 match self {
1152 TxOutcome::Success { logs, .. } => logs,
1153 TxOutcome::ProgramError { logs, .. } => logs,
1154 }
1155 }
1156
1157 pub fn compute_units(&self) -> Option<u64> {
1158 match self {
1159 TxOutcome::Success { compute_units, .. } => Some(*compute_units),
1160 _ => None,
1161 }
1162 }
1163
1164 pub fn signature(&self) -> &Signature {
1165 match self {
1166 TxOutcome::Success { signature, .. } => signature,
1167 TxOutcome::ProgramError { signature, .. } => signature,
1168 }
1169 }
1170
1171 pub fn fee(&self) -> u64 {
1172 match self {
1173 TxOutcome::Success { fee, .. } => *fee,
1174 TxOutcome::ProgramError { fee, .. } => *fee,
1175 }
1176 }
1177
1178 pub fn inner_instructions(&self) -> &InnerInstructionsList {
1179 match self {
1180 TxOutcome::Success {
1181 inner_instructions, ..
1182 } => inner_instructions,
1183 TxOutcome::ProgramError {
1184 inner_instructions, ..
1185 } => inner_instructions,
1186 }
1187 }
1188
1189 pub fn return_data(&self) -> &TransactionReturnData {
1190 match self {
1191 TxOutcome::Success { return_data, .. } => return_data,
1192 TxOutcome::ProgramError { return_data, .. } => return_data,
1193 }
1194 }
1195
1196 pub fn unwrap(self) {
1198 match self {
1199 TxOutcome::Success { .. } => {}
1200 TxOutcome::ProgramError {
1201 error,
1202 error_code,
1203 logs,
1204 ..
1205 } => {
1206 let mut msg = format!("Transaction failed: {:?}", error);
1207 if let Some(code) = error_code {
1208 msg.push_str(&format!(" (code: {})", code));
1209 }
1210 msg.push_str("\nLogs:\n");
1211 for log in &logs {
1212 msg.push_str(&format!(" {}\n", log));
1213 }
1214 panic!("{}", msg);
1215 }
1216 }
1217 }
1218
1219 pub fn expect(self, msg: &str) {
1221 match self {
1222 TxOutcome::Success { .. } => {}
1223 TxOutcome::ProgramError { logs, .. } => {
1224 let mut full_msg = format!("{}\nLogs:\n", msg);
1225 for log in &logs {
1226 full_msg.push_str(&format!(" {}\n", log));
1227 }
1228 panic!("{}", full_msg);
1229 }
1230 }
1231 }
1232
1233 pub fn into_result(self) -> std::result::Result<(), TxError> {
1235 match self {
1236 TxOutcome::Success { .. } => Ok(()),
1237 TxOutcome::ProgramError {
1238 error,
1239 error_code,
1240 instruction_index,
1241 logs,
1242 signature,
1243 inner_instructions,
1244 return_data,
1245 fee,
1246 } => Err(TxError {
1247 error,
1248 error_code,
1249 instruction_index,
1250 logs,
1251 signature,
1252 inner_instructions,
1253 return_data,
1254 fee,
1255 }),
1256 }
1257 }
1258}
1259
1260pub fn parse_error_code(err: &TransactionError) -> Option<u32> {
1263 use solana_instruction::error::InstructionError;
1264 match err {
1265 TransactionError::InstructionError(_, InstructionError::Custom(code)) => Some(*code),
1266 _ => None,
1267 }
1268}
1269
1270pub fn parse_instruction_index(err: &TransactionError) -> Option<u8> {
1272 match err {
1273 TransactionError::InstructionError(idx, _) => Some(*idx),
1274 _ => None,
1275 }
1276}
1277
1278pub fn tx_result_to_outcome(result: litesvm::types::TransactionResult) -> TxOutcome {
1280 let outcome = match result {
1281 Ok(meta) => TxOutcome::Success {
1282 compute_units: meta.compute_units_consumed,
1283 logs: meta.logs,
1284 signature: meta.signature,
1285 inner_instructions: meta.inner_instructions,
1286 return_data: meta.return_data,
1287 fee: meta.fee,
1288 },
1289 Err(failed) => {
1290 let error_code = parse_error_code(&failed.err);
1291 let instruction_index = parse_instruction_index(&failed.err);
1292 TxOutcome::ProgramError {
1293 error: failed.err,
1294 error_code,
1295 instruction_index,
1296 logs: failed.meta.logs,
1297 signature: failed.meta.signature,
1298 inner_instructions: failed.meta.inner_instructions,
1299 return_data: failed.meta.return_data,
1300 fee: failed.meta.fee,
1301 }
1302 }
1303 };
1304 set_last_error_code(outcome.error_code());
1306 outcome
1307}
1308
1309pub mod fuzz_types {
1311 pub use solana_program_runtime::invoke_context::{Executable, InvokeContext};
1312 pub use solana_pubkey::Pubkey;
1313 pub use solana_sbpf::ebpf;
1314 pub use solana_sbpf::static_analysis::Analysis;
1315 pub use solana_transaction::sanitized::SanitizedTransaction;
1316 pub use solana_transaction_context::{IndexOfAccount, InstructionContext};
1317}
1318
1319#[derive(Clone)]
1321pub struct ProgramData {
1322 pub program_id: Pubkey,
1323 pub data: Vec<u8>,
1324}
1325
1326pub struct TestContext {
1327 pub svm: LiteSVM,
1328 pub pending_instructions: Vec<Instruction>,
1329 pending_signers: Vec<Keypair>,
1330 programs: std::sync::Arc<Vec<ProgramData>>,
1333 tracked_accounts: Arc<HashSet<Pubkey>>,
1335 program_coverage_totals: Arc<HashMap<Pubkey, (usize, usize)>>,
1339 pub snapshot: Option<snapshot::SvmSnapshot>,
1341 pub dirty_tracker: snapshot::DirtyTracker,
1343 pub sigverify: bool,
1347}
1348
1349impl Clone for TestContext {
1350 fn clone(&self) -> Self {
1351 Self {
1352 svm: self.svm.clone(),
1353 pending_instructions: self.pending_instructions.clone(),
1354 pending_signers: self
1355 .pending_signers
1356 .iter()
1357 .map(|k| k.insecure_clone())
1358 .collect(),
1359 programs: self.programs.clone(),
1360 tracked_accounts: self.tracked_accounts.clone(),
1361 program_coverage_totals: self.program_coverage_totals.clone(),
1362 snapshot: None,
1364 dirty_tracker: self.dirty_tracker.clone(),
1366 sigverify: self.sigverify,
1367 }
1368 }
1369}
1370
1371pub struct EmptyInvocationCallback;
1374
1375impl InvocationInspectCallback for EmptyInvocationCallback {
1376 fn before_invocation(
1377 &self,
1378 _tx: &solana_transaction::sanitized::SanitizedTransaction,
1379 _program_indices: &[solana_transaction_context::IndexOfAccount],
1380 _invoke_context: &solana_program_runtime::invoke_context::InvokeContext,
1381 ) {
1382 }
1383
1384 fn after_invocation(
1385 &self,
1386 _invoke_context: &solana_program_runtime::invoke_context::InvokeContext,
1387 _register_tracing_enabled: bool,
1388 ) {
1389 }
1390}
1391
1392impl TestContext {
1393 pub fn new() -> Self {
1394 let svm = if std::env::var("CRUCIBLE_FUZZ_DEBUGGABLE").is_ok() {
1399 let mut svm = LiteSVM::new_debuggable(true)
1400 .with_transaction_history(0)
1401 .with_sigverify(false)
1402 .with_blockhash_check(false);
1403 svm.set_invocation_inspect_callback(EmptyInvocationCallback);
1404 svm
1405 } else {
1406 LiteSVM::new()
1407 .with_transaction_history(0)
1408 .with_sigverify(false)
1409 .with_blockhash_check(false)
1410 };
1411
1412 Self {
1413 svm,
1414 pending_instructions: Vec::new(),
1415 pending_signers: Vec::new(),
1416 programs: std::sync::Arc::new(Vec::new()),
1417 tracked_accounts: Arc::new(HashSet::new()),
1418 program_coverage_totals: Arc::new(HashMap::new()),
1419 snapshot: None,
1420 dirty_tracker: snapshot::DirtyTracker::new(),
1421 sigverify: false,
1422 }
1423 }
1424
1425 pub fn with_invocation_callback<C: InvocationInspectCallback + 'static>(callback: C) -> Self {
1426 let mut svm = LiteSVM::new_debuggable(true)
1427 .with_transaction_history(0)
1428 .with_sigverify(false)
1429 .with_blockhash_check(false);
1430 svm.set_invocation_inspect_callback(callback);
1431 Self {
1432 svm,
1433 pending_instructions: Vec::new(),
1434 pending_signers: Vec::new(),
1435 programs: std::sync::Arc::new(Vec::new()),
1436 tracked_accounts: Arc::new(HashSet::new()),
1437 program_coverage_totals: Arc::new(HashMap::new()),
1438 snapshot: None,
1439 dirty_tracker: snapshot::DirtyTracker::new(),
1440 sigverify: false,
1441 }
1442 }
1443
1444 pub fn with_compute_budget(mut self, compute_unit_limit: u64) -> Self {
1445 use solana_compute_budget::compute_budget::ComputeBudget;
1446 let mut budget = ComputeBudget::new_with_defaults(true, true);
1447 budget.compute_unit_limit = compute_unit_limit;
1448 self.svm = self.svm.with_compute_budget(budget);
1449 self
1450 }
1451
1452 pub fn analyze_program_coverage(program_data: &[u8]) -> Option<(usize, usize)> {
1457 use solana_sbpf::ebpf;
1458 use solana_sbpf::elf::Executable;
1459 use solana_sbpf::program::BuiltinProgram;
1460 use solana_sbpf::static_analysis::Analysis;
1461 use solana_sbpf::vm::ContextObject;
1462
1463 struct DummyContext;
1464 impl ContextObject for DummyContext {
1465 fn consume(&mut self, _amount: u64) {}
1466 fn get_remaining(&self) -> u64 {
1467 0
1468 }
1469 }
1470
1471 let loader = Arc::new(BuiltinProgram::<DummyContext>::new_mock());
1472 let executable = Executable::from_elf(program_data, loader).ok()?;
1473 let analysis = Analysis::from_executable(&executable).ok()?;
1474
1475 let mut visited = HashSet::new();
1479 let mut queue = std::collections::VecDeque::new();
1480
1481 if analysis.cfg_nodes.contains_key(&analysis.entrypoint) {
1483 visited.insert(analysis.entrypoint);
1484 queue.push_back(analysis.entrypoint);
1485 }
1486
1487 for (&pc, _) in &analysis.functions {
1490 if analysis.cfg_nodes.contains_key(&pc) {
1491 if visited.insert(pc) {
1492 queue.push_back(pc);
1493 }
1494 }
1495 }
1496
1497 while let Some(node_id) = queue.pop_front() {
1498 if let Some(cfg_node) = analysis.cfg_nodes.get(&node_id) {
1499 for &dest in &cfg_node.destinations {
1500 if visited.insert(dest) {
1501 queue.push_back(dest);
1502 }
1503 }
1504 }
1505 }
1506
1507 let mut total_conditional: usize = 0;
1509 let mut total_instructions: usize = 0;
1510 for (&node_id, cfg_node) in &analysis.cfg_nodes {
1511 if !visited.contains(&node_id) {
1512 continue;
1513 }
1514
1515 total_instructions += cfg_node.instructions.end - cfg_node.instructions.start;
1516
1517 if cfg_node.instructions.is_empty() {
1518 continue;
1519 }
1520
1521 let last_insn = &analysis.instructions[cfg_node.instructions.end - 1];
1522 let is_jmp = last_insn.opc & 7 == ebpf::BPF_JMP;
1523 if is_jmp {
1524 let opc = last_insn.opc;
1525 let is_conditional = opc != 0x05 && opc != 0x85 && opc != 0x8d && opc != 0x95;
1526 if is_conditional {
1527 total_conditional += cfg_node.destinations.len();
1528 }
1529 }
1530 }
1531
1532 Some((total_conditional, total_instructions))
1533 }
1534
1535 pub fn add_program(&mut self, program_id: &Pubkey, program_path: &str) -> Result<()> {
1536 let actual_path = if let Ok(override_path) = std::env::var("FUZZ_PROGRAM_SO") {
1537 eprintln!(
1538 "[COVERAGE] Program binary override: {} -> {}",
1539 program_path, override_path
1540 );
1541 override_path
1542 } else {
1543 program_path.to_string()
1544 };
1545 let program_data = std::fs::read(&actual_path)?;
1546 self.add_program_from_bytes(program_id, &program_data)
1547 }
1548
1549 pub fn add_program_from_bytes(
1554 &mut self,
1555 program_id: &Pubkey,
1556 program_data: &[u8],
1557 ) -> Result<()> {
1558 let override_bytes;
1563 let program_data: &[u8] = if let Ok(override_path) = std::env::var("FUZZ_PROGRAM_SO") {
1564 eprintln!(
1565 "[COVERAGE] Program binary override: <{} bytes> -> {}",
1566 program_data.len(),
1567 override_path
1568 );
1569 override_bytes = std::fs::read(&override_path).with_context(|| {
1570 format!("failed to read FUZZ_PROGRAM_SO override at {override_path}")
1571 })?;
1572 &override_bytes
1573 } else {
1574 program_data
1575 };
1576
1577 if let Some((total_edges, total_instructions)) =
1579 Self::analyze_program_coverage(program_data)
1580 {
1581 Arc::make_mut(&mut self.program_coverage_totals)
1582 .insert(*program_id, (total_edges, total_instructions));
1583 }
1584
1585 self.svm
1586 .add_program(program_id.clone(), program_data)
1587 .map_err(|e| anyhow::anyhow!("failed to load program {}: {:?}", program_id, e))?;
1588 std::sync::Arc::make_mut(&mut self.programs).push(ProgramData {
1590 program_id: *program_id,
1591 data: program_data.to_vec(),
1592 });
1593 Ok(())
1594 }
1595
1596 #[cfg(feature = "rpc-clone")]
1601 pub fn clone_from_rpc(&mut self, rpc_url: &str) -> AccountCloner<'_> {
1602 AccountCloner::new(self, rpc_url)
1603 }
1604
1605 pub fn from_svm(svm: LiteSVM) -> Self {
1606 Self {
1607 svm,
1608 pending_instructions: Vec::new(),
1609 pending_signers: Vec::new(),
1610 programs: std::sync::Arc::new(Vec::new()),
1611 tracked_accounts: Arc::new(HashSet::new()),
1612 program_coverage_totals: Arc::new(HashMap::new()),
1613 snapshot: None,
1614 dirty_tracker: snapshot::DirtyTracker::new(),
1615 sigverify: false,
1616 }
1617 }
1618
1619 pub fn into_svm(self) -> LiteSVM {
1620 self.svm
1621 }
1622
1623 pub fn clone_with_invocation_callback<C: InvocationInspectCallback + 'static>(
1631 &self,
1632 callback: C,
1633 ) -> Self {
1634 let mut cloned_svm = self.svm.clone();
1637 cloned_svm.set_invocation_inspect_callback(callback);
1638
1639 Self {
1640 svm: cloned_svm,
1641 pending_instructions: self.pending_instructions.clone(),
1642 pending_signers: self
1643 .pending_signers
1644 .iter()
1645 .map(|k| k.insecure_clone())
1646 .collect(),
1647 programs: self.programs.clone(),
1648 tracked_accounts: self.tracked_accounts.clone(),
1649 program_coverage_totals: self.program_coverage_totals.clone(),
1650 snapshot: None,
1651 dirty_tracker: snapshot::DirtyTracker::new(),
1652 sigverify: false,
1653 }
1654 }
1655
1656 pub fn set_invocation_callback<C: InvocationInspectCallback + 'static>(&mut self, callback: C) {
1669 self.svm.set_invocation_inspect_callback(callback);
1670 }
1671
1672 pub fn track_account(&mut self, pubkey: Pubkey) {
1675 Arc::make_mut(&mut self.tracked_accounts).insert(pubkey);
1676 }
1677
1678 pub fn tracked_accounts_count(&self) -> usize {
1680 self.tracked_accounts.len()
1681 }
1682
1683 pub fn programs_count(&self) -> usize {
1685 self.programs.len()
1686 }
1687
1688 pub fn svm_account_count(&self) -> usize {
1691 self.svm.accounts_db().inner.len()
1692 }
1693
1694 pub fn account_exists(&self, pubkey: &Pubkey) -> bool {
1696 self.svm.get_account(pubkey).is_some()
1697 }
1698
1699 pub fn get_program_coverage_totals(&self) -> &HashMap<Pubkey, (usize, usize)> {
1703 &self.program_coverage_totals
1704 }
1705
1706 pub fn get_program_binaries(&self) -> HashMap<Pubkey, Vec<u8>> {
1710 self.programs
1711 .iter()
1712 .map(|p| (p.program_id, p.data.clone()))
1713 .collect()
1714 }
1715
1716 pub fn get_program_binary(&self, pubkey: &Pubkey) -> Option<&[u8]> {
1718 self.programs
1719 .iter()
1720 .find(|p| &p.program_id == pubkey)
1721 .map(|p| p.data.as_slice())
1722 }
1723
1724 pub fn take_snapshot(&mut self) {
1731 let db_keys: Vec<Pubkey> = self.svm.accounts_db().inner.keys().copied().collect();
1734 {
1735 let tracked = Arc::make_mut(&mut self.tracked_accounts);
1736 for pubkey in &db_keys {
1737 tracked.insert(*pubkey);
1738 }
1739 }
1740 self.snapshot = Some(snapshot::SvmSnapshot::take_all(&self.svm));
1745 self.dirty_tracker.clear();
1747 }
1748
1749 pub fn begin_iteration(&mut self) {
1752 self.dirty_tracker.clear();
1753 self.pending_instructions.clear();
1755 self.pending_signers.clear();
1756 }
1757
1758 pub fn restore_snapshot(&mut self) -> usize {
1761 if let Some(ref snap) = self.snapshot {
1762 snap.restore(&mut self.svm, &self.dirty_tracker)
1763 } else {
1764 0
1765 }
1766 }
1767
1768 pub fn has_snapshot(&self) -> bool {
1770 self.snapshot.is_some()
1771 }
1772
1773 pub fn dirty_tracker(&self) -> &snapshot::DirtyTracker {
1775 &self.dirty_tracker
1776 }
1777
1778 pub fn create_account(&mut self) -> GenericAccountBuilder<'_> {
1782 GenericAccountBuilder {
1783 ctx: self,
1784 address: Pubkey::default(),
1785 account_state: Account {
1786 lamports: 0,
1787 data: vec![],
1788 owner: system_program::id(),
1789 executable: false,
1790 rent_epoch: 0,
1791 },
1792 }
1793 }
1794
1795 pub fn create_mint(&mut self) -> MintAccountBuilder<'_> {
1797 let rent = Rent::default();
1798 MintAccountBuilder {
1799 ctx: self,
1800 address: Pubkey::default(),
1801 account_state: Account {
1802 lamports: rent.minimum_balance(spl_token::state::Mint::LEN),
1803 data: vec![0; spl_token::state::Mint::LEN],
1804 owner: spl_token::id(),
1805 executable: false,
1806 rent_epoch: 0,
1807 },
1808 mint: spl_token::state::Mint {
1809 mint_authority: COption::None,
1810 supply: 0,
1811 decimals: 0,
1812 is_initialized: true,
1813 freeze_authority: COption::None,
1814 },
1815 }
1816 }
1817 pub fn create_token_account(&mut self) -> TokenAccountBuilder<'_> {
1818 let rent = Rent::default();
1819 TokenAccountBuilder {
1820 ctx: self,
1821 address: Pubkey::default(),
1822 account_state: Account {
1823 lamports: rent.minimum_balance(spl_token::state::Account::LEN),
1824 data: vec![0; spl_token::state::Account::LEN],
1825 owner: spl_token::id(),
1826 executable: false,
1827 rent_epoch: 0,
1828 },
1829 token_state: spl_token::state::Account {
1830 mint: Pubkey::default(),
1831 owner: Pubkey::default(),
1832 amount: 0,
1833 delegate: COption::None,
1834 state: spl_token::state::AccountState::Initialized,
1835 is_native: COption::None,
1836 delegated_amount: 0,
1837 close_authority: COption::None,
1838 },
1839 }
1840 }
1841
1842 pub fn transfer_tokens(
1844 &mut self,
1845 from: &Pubkey,
1846 to: &Pubkey,
1847 owner: &Keypair,
1848 amount: u64,
1849 ) -> anyhow::Result<()> {
1850 self.raw_call(spl_token::instruction::transfer(
1851 &spl_token::id(),
1852 from,
1853 to,
1854 &owner.pubkey(),
1855 &[],
1856 amount,
1857 )?)
1858 .signers(&[owner])
1859 .send()?;
1860 Ok(())
1861 }
1862
1863 pub fn mint_to(
1864 &mut self,
1865 mint: &Pubkey,
1866 destination: &Pubkey,
1867 amount: u64,
1868 authority: &Rc<Keypair>,
1869 ) -> anyhow::Result<()> {
1870 self.raw_call(spl_token::instruction::mint_to(
1871 &spl_token::id(),
1872 mint,
1873 destination,
1874 &authority.pubkey(),
1875 &[],
1876 amount,
1877 )?)
1878 .signers(&[&**authority])
1879 .send()?;
1880 Ok(())
1881 }
1882
1883 pub fn warp_to_slot(&mut self, slot: u64) {
1884 self.dirty_tracker.mark_clock_dirty(slot);
1885 self.svm.warp_to_slot(slot);
1886 }
1887
1888 pub fn advance_slots(&mut self, slots: u64) {
1889 let current_slot = self.slot();
1890 let target_slot = current_slot + slots;
1891 self.dirty_tracker.mark_clock_dirty(target_slot);
1892 self.svm.warp_to_slot(target_slot);
1893 }
1894
1895 pub fn set_sysvar<T>(&mut self, sysvar: &T) -> ()
1896 where
1897 T: solana_sysvar::SysvarSerialize,
1898 {
1899 self.svm.set_sysvar::<T>(sysvar);
1900 }
1901
1902 pub fn slot(&self) -> u64 {
1905 self.svm.get_sysvar::<Clock>().slot
1906 }
1907
1908 pub fn next_slot(&self) -> u64 {
1910 self.slot() + 1
1911 }
1912
1913 pub fn account_has_data(&self, pubkey: &Pubkey, min_size: usize) -> bool {
1939 self.svm
1940 .get_account(pubkey)
1941 .map(|acc| acc.data.len() >= min_size)
1942 .unwrap_or(false)
1943 }
1944
1945 pub fn get_account(&self, address: &Pubkey) -> Result<Account> {
1946 self.read_account(address)
1947 }
1948
1949 pub fn read_account(&self, address: &Pubkey) -> Result<Account> {
1951 self.svm
1952 .get_account(address)
1953 .ok_or_else(|| anyhow::anyhow!("Account not found: {}", address))
1954 }
1955
1956 pub fn read_anchor_account<T: AnchorDeserialize + Discriminator>(
1959 &self,
1960 address: &Pubkey,
1961 ) -> Result<T> {
1962 let account = self.read_account(address)?;
1963 let disc_len = T::DISCRIMINATOR.len();
1964
1965 if account.data.len() < disc_len {
1966 return Err(anyhow::anyhow!(
1967 "Account data too small for discriminator (need {} bytes, got {})",
1968 disc_len,
1969 account.data.len()
1970 ));
1971 }
1972
1973 T::deserialize(&mut &account.data[disc_len..])
1975 .map_err(|e| anyhow::anyhow!("Failed to deserialize account: {}", e))
1976 }
1977
1978 pub fn read_account_with_discriminator<T: AnchorDeserialize>(
1980 &self,
1981 address: &Pubkey,
1982 discriminator_len: usize,
1983 ) -> Result<T> {
1984 let account = self.read_account(address)?;
1985
1986 if account.data.len() < discriminator_len {
1987 return Err(anyhow::anyhow!(
1988 "Account data too small for discriminator (need {} bytes, got {})",
1989 discriminator_len,
1990 account.data.len()
1991 ));
1992 }
1993
1994 T::deserialize(&mut &account.data[discriminator_len..])
1995 .map_err(|e| anyhow::anyhow!("Failed to deserialize account: {}", e))
1996 }
1997
1998 pub fn token_balance(&self, token_account: &Pubkey) -> u64 {
1999 self.svm
2000 .get_account(token_account)
2001 .and_then(|acc| spl_token::state::Account::unpack(&acc.data).ok())
2002 .map(|state| state.amount)
2003 .unwrap_or(0)
2004 }
2005
2006 pub fn write_account(&mut self, address: &Pubkey, account: Account) -> Result<()> {
2010 Arc::make_mut(&mut self.tracked_accounts).insert(*address);
2011 self.dirty_tracker.mark_account_dirty(address);
2012 let _ = self.svm.set_account(*address, account);
2013 Ok(())
2014 }
2015
2016 pub fn write_anchor_account<T: AnchorSerialize + Discriminator>(
2018 &mut self,
2019 address: &Pubkey,
2020 data: &T,
2021 ) -> Result<()> {
2022 let mut account = self.read_account(address)?;
2024
2025 let mut account_data = T::DISCRIMINATOR.to_vec();
2027 data.serialize(&mut account_data)?;
2028
2029 account.data = account_data;
2031 self.dirty_tracker.mark_account_dirty(address);
2032 let _ = self.svm.set_account(*address, account);
2033
2034 Ok(())
2035 }
2036
2037 pub fn read_zero_copy_account<T: bytemuck::Pod>(&self, address: &Pubkey) -> Result<T> {
2050 self.read_zero_copy_account_with_discriminator(address, 8)
2051 }
2052
2053 pub fn read_zero_copy_account_with_discriminator<T: bytemuck::Pod>(
2063 &self,
2064 address: &Pubkey,
2065 discriminator_len: usize,
2066 ) -> Result<T> {
2067 let account = self.read_account(address)?;
2068 let required_size = discriminator_len + std::mem::size_of::<T>();
2069 if account.data.len() < required_size {
2070 return Err(anyhow::anyhow!(
2071 "Account data too small for zero-copy struct: got {} bytes, need {} bytes (discriminator: {})",
2072 account.data.len(),
2073 required_size,
2074 discriminator_len
2075 ));
2076 }
2077 Ok(*bytemuck::from_bytes::<T>(
2078 &account.data[discriminator_len..discriminator_len + std::mem::size_of::<T>()],
2079 ))
2080 }
2081
2082 pub fn write_zero_copy_account<T: bytemuck::Pod>(
2094 &mut self,
2095 address: &Pubkey,
2096 data: &T,
2097 ) -> Result<()> {
2098 self.write_zero_copy_account_with_discriminator(address, data, 8)
2099 }
2100
2101 pub fn write_zero_copy_account_with_discriminator<T: bytemuck::Pod>(
2103 &mut self,
2104 address: &Pubkey,
2105 data: &T,
2106 discriminator_len: usize,
2107 ) -> Result<()> {
2108 let mut account = self.read_account(address)?;
2109 let bytes = bytemuck::bytes_of(data);
2110 let required_size = discriminator_len + bytes.len();
2111 if account.data.len() < required_size {
2112 return Err(anyhow::anyhow!(
2113 "Account data too small for zero-copy struct: got {} bytes, need {} bytes",
2114 account.data.len(),
2115 required_size
2116 ));
2117 }
2118 account.data[discriminator_len..discriminator_len + bytes.len()].copy_from_slice(bytes);
2119 Arc::make_mut(&mut self.tracked_accounts).insert(*address);
2120 self.dirty_tracker.mark_account_dirty(address);
2121 let _ = self.svm.set_account(*address, account);
2122 Ok(())
2123 }
2124
2125 pub fn update_account<F>(&mut self, pubkey: &Pubkey, f: F) -> Result<()>
2136 where
2137 F: FnOnce(&mut Vec<u8>),
2138 {
2139 let mut account = self.read_account(pubkey)?;
2140 f(&mut account.data);
2141 self.write_account(pubkey, account)
2142 }
2143
2144 pub fn raw_call(&mut self, instruction: Instruction) -> InstructionBuilder<'_> {
2148 InstructionBuilder {
2149 ctx: self,
2150 instruction,
2151 signers: vec![],
2152 fee_payer: None,
2153 }
2154 }
2155
2156 pub fn program(&mut self, program_id: Pubkey) -> ProgramBuilder<'_> {
2158 ProgramBuilder {
2159 ctx: self,
2160 instruction: Instruction {
2161 program_id,
2162 accounts: vec![],
2163 data: vec![],
2164 },
2165 signers: vec![],
2166 fee_payer: None, }
2168 }
2169
2170 pub fn transaction(&mut self) -> TransactionBuilder<'_> {
2172 TransactionBuilder {
2173 ctx: self,
2174 instructions: vec![],
2175 signers: vec![],
2176 }
2177 }
2178
2179 pub fn send_batch(&mut self) -> Result<Option<TxOutcome>> {
2180 if self.pending_instructions.is_empty() {
2182 return Ok(None);
2183 }
2184
2185 let debug = is_fuzz_debug();
2186 let num_ixs = self.pending_instructions.len();
2187
2188 let mut seen = std::collections::HashSet::new();
2190 let unique_signers: Vec<&Keypair> = self
2191 .pending_signers
2192 .iter()
2193 .filter(|k| seen.insert(k.pubkey()))
2194 .collect();
2195
2196 let fee_payer_pubkey = unique_signers
2197 .first()
2198 .map(|k| k.pubkey())
2199 .unwrap_or_default();
2200
2201 let default_kp = Keypair::new();
2202
2203 let fee_payer = unique_signers.first().map(|k| *k).ok_or(anyhow::anyhow!(
2204 "At least one signer required for send_batch. The first signer is the fee payer."
2205 ))?;
2206
2207 if debug {
2208 eprintln!("[TX] Sending batch with {} instructions", num_ixs);
2209 for (i, ix) in self.pending_instructions.iter().enumerate() {
2210 eprintln!("[TX] ix[{}]: program={}", i, ix.program_id);
2211 }
2212 }
2213
2214 let __t_pre = std::time::Instant::now();
2216 self.dirty_tracker
2217 .record_tx(&self.pending_instructions, &fee_payer_pubkey);
2218 SEND_BATCH_PRE_NS.with(|c| c.set(c.get() + __t_pre.elapsed().as_nanos() as u64));
2219
2220 let __t_svm = std::time::Instant::now();
2222 let instructions = std::mem::take(&mut self.pending_instructions);
2223 let result = instruction_builder::send_transaction(
2224 &mut self.svm,
2225 instructions,
2226 &unique_signers,
2227 fee_payer,
2228 self.sigverify,
2229 )?;
2230 SEND_BATCH_SVM_NS.with(|c| c.set(c.get() + __t_svm.elapsed().as_nanos() as u64));
2231
2232 let __t_post = std::time::Instant::now();
2234 let outcome = tx_result_to_outcome(result);
2235
2236 increment_action_count();
2238 if outcome.is_success() {
2239 increment_action_success_count();
2240 }
2241
2242 if debug {
2243 match &outcome {
2244 TxOutcome::Success {
2245 compute_units,
2246 logs,
2247 ..
2248 } => {
2249 eprintln!("[TX] SUCCESS - compute_units={}, logs:", compute_units);
2250 for log in logs {
2251 eprintln!("[TX] {}", log);
2252 }
2253 }
2254 TxOutcome::ProgramError {
2255 error,
2256 error_code,
2257 logs,
2258 ..
2259 } => {
2260 eprintln!("[TX] FAILED - error: {:?}", error);
2261 if let Some(code) = error_code {
2262 eprintln!("[TX] error code: {}", code);
2263 }
2264 eprintln!("[TX] logs:");
2265 for log in logs {
2266 eprintln!("[TX] {}", log);
2267 }
2268 }
2269 }
2270 }
2271 SEND_BATCH_POST_NS.with(|c| c.set(c.get() + __t_post.elapsed().as_nanos() as u64));
2272
2273 self.pending_signers.clear();
2275
2276 Ok(Some(outcome))
2277 }
2278}
2279
2280#[cfg(test)]
2285mod tests {
2286 use super::*;
2287
2288 #[test]
2293 fn test_violation_tracking_basic() {
2294 let _ = take_violation();
2296
2297 assert!(!has_violation());
2299 assert!(take_violation().is_none());
2300
2301 record_violation("test violation".to_string());
2303
2304 assert!(has_violation());
2306
2307 let v = take_violation();
2309 assert_eq!(v, Some("test violation".to_string()));
2310 assert!(!has_violation());
2311 assert!(take_violation().is_none());
2312 }
2313
2314 #[test]
2315 fn test_violation_only_records_first() {
2316 let _ = take_violation();
2317
2318 record_violation("first".to_string());
2319 record_violation("second".to_string());
2320 record_violation("third".to_string());
2321
2322 let v = take_violation();
2324 assert_eq!(v, Some("first".to_string()));
2325 }
2326
2327 #[test]
2332 fn test_action_history() {
2333 clear_action_history();
2334
2335 assert!(get_action_history().is_empty());
2336
2337 push_action_record("action_deposit", serde_json::json!({"amount": 100}), true);
2338 push_action_record("action_withdraw", serde_json::json!({"amount": 50}), false);
2339
2340 let history = get_action_history();
2341 assert_eq!(history.len(), 2);
2342 assert_eq!(history[0].name, "action_deposit");
2343 assert!(history[0].success);
2344 assert_eq!(history[0].error_code, None);
2345 assert_eq!(history[1].name, "action_withdraw");
2346 assert!(!history[1].success);
2347 assert_eq!(history[1].error_code, None);
2348
2349 clear_action_history();
2351 set_last_error_code(Some(6051));
2352 push_action_record("action_borrow", serde_json::json!({}), false);
2353 let history = get_action_history();
2354 assert_eq!(history[0].error_code, Some(6051));
2355 push_action_record("action_deposit", serde_json::json!({}), true);
2357 let history = get_action_history();
2358 assert_eq!(history[1].error_code, None);
2359
2360 clear_action_history();
2361 assert!(get_action_history().is_empty());
2362 }
2363
2364 #[test]
2370 fn test_violation_action_index() {
2371 clear_violation_tracking();
2372
2373 assert!(get_violation_action_index().is_none());
2374
2375 set_total_actions(5);
2376 set_violation_action_index(2);
2377
2378 assert_eq!(get_violation_action_index(), Some(2));
2379
2380 set_violation_action_index(3);
2382 assert_eq!(get_violation_action_index(), Some(2));
2383
2384 clear_violation_tracking();
2385 assert!(get_violation_action_index().is_none());
2386 }
2387
2388 #[test]
2393 fn test_into_action_success_unit() {
2394 assert!(().into_success());
2395 }
2396
2397 #[test]
2398 fn test_into_action_success_result() {
2399 let ok: Result<(), &str> = Ok(());
2400 let err: Result<(), &str> = Err("error");
2401
2402 assert!(ok.into_success());
2403 assert!(!err.into_success());
2404 }
2405
2406 #[test]
2407 fn test_into_action_success_bool() {
2408 assert!(true.into_success());
2409 assert!(!false.into_success());
2410 }
2411
2412 #[test]
2417 fn test_iteration_tracking() {
2418 set_current_iteration(42);
2419 assert_eq!(get_current_iteration(), 42);
2420
2421 set_current_iteration(100);
2422 assert_eq!(get_current_iteration(), 100);
2423 }
2424
2425 #[test]
2426 fn test_test_name_tracking() {
2427 set_current_test_name("my_test");
2428 assert_eq!(get_current_test_name(), Some("my_test".to_string()));
2429 }
2430
2431 #[test]
2436 fn test_build_crash_metadata() {
2437 clear_action_history();
2438 set_current_test_name("test_func");
2439 set_current_iteration(999);
2440
2441 push_action_record("action_a", serde_json::json!({"x": 1}), true);
2442
2443 let meta = build_crash_metadata(Some(12345));
2444
2445 assert_eq!(meta.test_name, "test_func");
2446 assert_eq!(meta.iteration, 999);
2447 assert_eq!(meta.seed, Some(12345));
2448 assert_eq!(meta.actions.len(), 1);
2449 assert_eq!(meta.actions[0].name, "action_a");
2450 assert_eq!(meta.actions[0].error_code, None);
2451 }
2452
2453 #[test]
2458 fn test_tx_outcome_helpers() {
2459 let success = TxOutcome::Success {
2460 compute_units: 100,
2461 logs: vec!["log1".to_string()],
2462 signature: Signature::default(),
2463 inner_instructions: vec![],
2464 return_data: TransactionReturnData::default(),
2465 fee: 5000,
2466 };
2467
2468 assert!(success.is_success());
2469 assert!(!success.is_error());
2470 assert!(success.error_code().is_none());
2471 assert_eq!(success.compute_units(), Some(100));
2472 assert_eq!(success.logs().len(), 1);
2473 assert_eq!(success.fee(), 5000);
2474
2475 let error = TxOutcome::ProgramError {
2476 error: TransactionError::AccountInUse,
2477 error_code: Some(6051),
2478 instruction_index: Some(0),
2479 logs: vec!["error log".to_string()],
2480 signature: Signature::default(),
2481 inner_instructions: vec![],
2482 return_data: TransactionReturnData::default(),
2483 fee: 0,
2484 };
2485
2486 assert!(!error.is_success());
2487 assert!(error.is_error());
2488 assert_eq!(error.error_code(), Some(6051));
2489 assert!(error.compute_units().is_none());
2490 assert_eq!(error.fee(), 0);
2491 }
2492
2493 #[test]
2494 fn test_tx_outcome_into_result() {
2495 let success = TxOutcome::Success {
2496 compute_units: 100,
2497 logs: vec![],
2498 signature: Signature::default(),
2499 inner_instructions: vec![],
2500 return_data: TransactionReturnData::default(),
2501 fee: 0,
2502 };
2503 assert!(success.into_result().is_ok());
2504
2505 let error = TxOutcome::ProgramError {
2506 error: TransactionError::AccountInUse,
2507 error_code: Some(6051),
2508 instruction_index: Some(0),
2509 logs: vec![],
2510 signature: Signature::default(),
2511 inner_instructions: vec![],
2512 return_data: TransactionReturnData::default(),
2513 fee: 0,
2514 };
2515 let err = error.into_result().unwrap_err();
2516 assert_eq!(err.error_code, Some(6051));
2517 assert_eq!(err.fee, 0);
2518 }
2519
2520 #[test]
2525 fn test_format_json_value() {
2526 assert_eq!(format_json_value(&serde_json::json!(null)), "null");
2527 assert_eq!(format_json_value(&serde_json::json!(true)), "true");
2528 assert_eq!(format_json_value(&serde_json::json!(42)), "42");
2529 assert_eq!(format_json_value(&serde_json::json!("hello")), "\"hello\"");
2530 assert_eq!(
2531 format_json_value(&serde_json::json!([1, 2, 3])),
2532 "[1, 2, 3]"
2533 );
2534 assert_eq!(format_json_value(&serde_json::json!({"a": 1})), "{a: 1}");
2535 }
2536
2537 #[test]
2542 fn test_snapshot_basic_roundtrip() {
2543 let mut ctx = TestContext::new();
2544
2545 let pk1 = Pubkey::new_unique();
2547 let pk2 = Pubkey::new_unique();
2548 let owner = Pubkey::new_unique();
2549
2550 ctx.write_account(
2551 &pk1,
2552 Account {
2553 lamports: 1_000_000,
2554 data: vec![1, 2, 3, 4],
2555 owner,
2556 executable: false,
2557 rent_epoch: 0,
2558 },
2559 )
2560 .unwrap();
2561
2562 ctx.write_account(
2563 &pk2,
2564 Account {
2565 lamports: 2_000_000,
2566 data: vec![10, 20, 30],
2567 owner,
2568 executable: false,
2569 rent_epoch: 0,
2570 },
2571 )
2572 .unwrap();
2573
2574 ctx.take_snapshot();
2576 assert!(ctx.has_snapshot());
2577
2578 ctx.begin_iteration();
2580
2581 ctx.write_account(
2583 &pk1,
2584 Account {
2585 lamports: 999,
2586 data: vec![99, 99, 99, 99],
2587 owner,
2588 executable: false,
2589 rent_epoch: 0,
2590 },
2591 )
2592 .unwrap();
2593
2594 ctx.write_account(
2595 &pk2,
2596 Account {
2597 lamports: 0,
2598 data: vec![],
2599 owner,
2600 executable: false,
2601 rent_epoch: 0,
2602 },
2603 )
2604 .unwrap();
2605
2606 let acc1 = ctx.read_account(&pk1).unwrap();
2608 assert_eq!(acc1.lamports, 999);
2609 assert_eq!(acc1.data, vec![99, 99, 99, 99]);
2610
2611 let restored = ctx.restore_snapshot();
2613 assert_eq!(restored, 2); let acc1 = ctx.read_account(&pk1).unwrap();
2617 assert_eq!(acc1.lamports, 1_000_000);
2618 assert_eq!(acc1.data, vec![1, 2, 3, 4]);
2619
2620 let acc2 = ctx.read_account(&pk2).unwrap();
2621 assert_eq!(acc2.lamports, 2_000_000);
2622 assert_eq!(acc2.data, vec![10, 20, 30]);
2623 }
2624
2625 #[test]
2626 fn test_snapshot_created_account_removed_on_restore() {
2627 let mut ctx = TestContext::new();
2628
2629 let pk_initial = Pubkey::new_unique();
2631 let owner = Pubkey::new_unique();
2632 ctx.write_account(
2633 &pk_initial,
2634 Account {
2635 lamports: 1_000_000,
2636 data: vec![1, 2, 3],
2637 owner,
2638 executable: false,
2639 rent_epoch: 0,
2640 },
2641 )
2642 .unwrap();
2643
2644 ctx.take_snapshot();
2645 ctx.begin_iteration();
2646
2647 let pk_new = Pubkey::new_unique();
2649 ctx.write_account(
2650 &pk_new,
2651 Account {
2652 lamports: 500_000,
2653 data: vec![42],
2654 owner,
2655 executable: false,
2656 rent_epoch: 0,
2657 },
2658 )
2659 .unwrap();
2660
2661 assert!(ctx.read_account(&pk_new).is_ok());
2663
2664 ctx.restore_snapshot();
2666
2667 let acc = ctx.svm.get_account(&pk_new);
2669 match acc {
2670 Some(a) => assert_eq!(a.lamports, 0, "Created account should be zeroed on restore"),
2671 None => {} }
2673
2674 let acc_initial = ctx.read_account(&pk_initial).unwrap();
2676 assert_eq!(acc_initial.lamports, 1_000_000);
2677 }
2678
2679 #[test]
2680 fn test_snapshot_clock_restore() {
2681 let mut ctx = TestContext::new();
2682
2683 ctx.warp_to_slot(100);
2685 let original_slot = ctx.slot();
2686 assert_eq!(original_slot, 100);
2687
2688 ctx.take_snapshot();
2689 ctx.begin_iteration();
2690
2691 ctx.warp_to_slot(500);
2693 assert_eq!(ctx.slot(), 500);
2694
2695 ctx.restore_snapshot();
2697
2698 assert_eq!(ctx.slot(), 100);
2700 }
2701
2702 #[test]
2703 fn test_snapshot_multiple_iterations() {
2704 let mut ctx = TestContext::new();
2705
2706 let pk = Pubkey::new_unique();
2707 let owner = Pubkey::new_unique();
2708
2709 ctx.write_account(
2710 &pk,
2711 Account {
2712 lamports: 1_000_000,
2713 data: vec![0; 32],
2714 owner,
2715 executable: false,
2716 rent_epoch: 0,
2717 },
2718 )
2719 .unwrap();
2720
2721 ctx.take_snapshot();
2722
2723 for i in 0..5 {
2725 ctx.begin_iteration();
2726
2727 ctx.write_account(
2729 &pk,
2730 Account {
2731 lamports: (i + 1) * 100,
2732 data: vec![i as u8; 32],
2733 owner,
2734 executable: false,
2735 rent_epoch: 0,
2736 },
2737 )
2738 .unwrap();
2739
2740 let acc = ctx.read_account(&pk).unwrap();
2742 assert_eq!(acc.lamports, (i + 1) * 100);
2743
2744 ctx.restore_snapshot();
2746
2747 let acc = ctx.read_account(&pk).unwrap();
2749 assert_eq!(acc.lamports, 1_000_000, "Failed on iteration {}", i);
2750 assert_eq!(acc.data, vec![0; 32], "Failed on iteration {}", i);
2751 }
2752 }
2753
2754 #[test]
2755 fn test_snapshot_dirty_tracker_tracks_write_account() {
2756 let mut ctx = TestContext::new();
2757
2758 let pk1 = Pubkey::new_unique();
2759 let pk2 = Pubkey::new_unique();
2760 let owner = Pubkey::new_unique();
2761
2762 ctx.write_account(
2763 &pk1,
2764 Account {
2765 lamports: 100,
2766 data: vec![],
2767 owner,
2768 executable: false,
2769 rent_epoch: 0,
2770 },
2771 )
2772 .unwrap();
2773
2774 ctx.write_account(
2775 &pk2,
2776 Account {
2777 lamports: 200,
2778 data: vec![],
2779 owner,
2780 executable: false,
2781 rent_epoch: 0,
2782 },
2783 )
2784 .unwrap();
2785
2786 assert!(ctx.dirty_tracker.dirty_accounts().contains(&pk1));
2788 assert!(ctx.dirty_tracker.dirty_accounts().contains(&pk2));
2789 assert_eq!(ctx.dirty_tracker.dirty_count(), 2);
2790
2791 ctx.begin_iteration();
2793 assert_eq!(ctx.dirty_tracker.dirty_count(), 0);
2794 assert!(ctx.pending_instructions.is_empty());
2795 }
2796
2797 #[test]
2798 fn test_snapshot_dirty_tracker_tracks_clock() {
2799 let mut ctx = TestContext::new();
2800
2801 assert!(!ctx.dirty_tracker.is_clock_dirty());
2802
2803 ctx.warp_to_slot(100);
2804 assert!(ctx.dirty_tracker.is_clock_dirty());
2805
2806 ctx.begin_iteration();
2807 assert!(!ctx.dirty_tracker.is_clock_dirty());
2808
2809 ctx.advance_slots(10);
2810 assert!(ctx.dirty_tracker.is_clock_dirty());
2811 }
2812
2813 #[test]
2814 fn test_snapshot_no_snapshot_returns_zero() {
2815 let mut ctx = TestContext::new();
2816
2817 assert!(!ctx.has_snapshot());
2819 let restored = ctx.restore_snapshot();
2820 assert_eq!(restored, 0);
2821 }
2822
2823 #[test]
2824 fn test_snapshot_unmodified_accounts_untouched() {
2825 let mut ctx = TestContext::new();
2826
2827 let pk_modified = Pubkey::new_unique();
2828 let pk_untouched = Pubkey::new_unique();
2829 let owner = Pubkey::new_unique();
2830
2831 ctx.write_account(
2832 &pk_modified,
2833 Account {
2834 lamports: 100,
2835 data: vec![1, 2, 3],
2836 owner,
2837 executable: false,
2838 rent_epoch: 0,
2839 },
2840 )
2841 .unwrap();
2842
2843 ctx.write_account(
2844 &pk_untouched,
2845 Account {
2846 lamports: 200,
2847 data: vec![4, 5, 6],
2848 owner,
2849 executable: false,
2850 rent_epoch: 0,
2851 },
2852 )
2853 .unwrap();
2854
2855 ctx.take_snapshot();
2856 ctx.begin_iteration();
2857
2858 ctx.write_account(
2860 &pk_modified,
2861 Account {
2862 lamports: 999,
2863 data: vec![9, 9, 9],
2864 owner,
2865 executable: false,
2866 rent_epoch: 0,
2867 },
2868 )
2869 .unwrap();
2870
2871 let restored = ctx.restore_snapshot();
2873 assert_eq!(restored, 1);
2874
2875 let acc = ctx.read_account(&pk_modified).unwrap();
2877 assert_eq!(acc.lamports, 100);
2878
2879 let acc = ctx.read_account(&pk_untouched).unwrap();
2881 assert_eq!(acc.lamports, 200);
2882 assert_eq!(acc.data, vec![4, 5, 6]);
2883 }
2884
2885 #[test]
2886 fn test_snapshot_clone_does_not_inherit_snapshot() {
2887 let mut ctx = TestContext::new();
2888
2889 let pk = Pubkey::new_unique();
2890 ctx.write_account(
2891 &pk,
2892 Account {
2893 lamports: 100,
2894 data: vec![],
2895 owner: Pubkey::new_unique(),
2896 executable: false,
2897 rent_epoch: 0,
2898 },
2899 )
2900 .unwrap();
2901
2902 ctx.take_snapshot();
2903 assert!(ctx.has_snapshot());
2904
2905 let cloned = ctx.clone();
2907 assert!(!cloned.has_snapshot());
2908
2909 assert_eq!(cloned.dirty_tracker.dirty_count(), 0);
2911 }
2912
2913 #[test]
2914 fn test_snapshot_includes_dirty_tracker_accounts() {
2915 let mut ctx = TestContext::new();
2918
2919 let pk_tracked = Pubkey::new_unique();
2920 let pk_cpi = Pubkey::new_unique();
2921 let owner = Pubkey::new_unique();
2922
2923 ctx.write_account(
2925 &pk_tracked,
2926 Account {
2927 lamports: 100,
2928 data: vec![1],
2929 owner,
2930 executable: false,
2931 rent_epoch: 0,
2932 },
2933 )
2934 .unwrap();
2935
2936 let _ = ctx.svm.set_account(
2939 pk_cpi,
2940 Account {
2941 lamports: 200,
2942 data: vec![2],
2943 owner,
2944 executable: false,
2945 rent_epoch: 0,
2946 },
2947 );
2948 ctx.dirty_tracker.mark_account_dirty(&pk_cpi);
2949
2950 ctx.take_snapshot();
2952 assert!(ctx.has_snapshot());
2953
2954 ctx.begin_iteration();
2955
2956 let _ = ctx.svm.set_account(
2958 pk_cpi,
2959 Account {
2960 lamports: 999,
2961 data: vec![9],
2962 owner,
2963 executable: false,
2964 rent_epoch: 0,
2965 },
2966 );
2967 ctx.dirty_tracker.mark_account_dirty(&pk_cpi);
2968
2969 ctx.restore_snapshot();
2971 let acc = ctx.svm.get_account(&pk_cpi).unwrap();
2972 assert_eq!(acc.lamports, 200);
2973 assert_eq!(acc.data, vec![2]);
2974 }
2975
2976 #[test]
2977 fn test_snapshot_programs_arc_clone() {
2978 let mut ctx = TestContext::new();
2979
2980 let pk = Pubkey::new_unique();
2982 ctx.write_account(
2983 &pk,
2984 Account {
2985 lamports: 100,
2986 data: vec![0; 1024],
2987 owner: Pubkey::new_unique(),
2988 executable: false,
2989 rent_epoch: 0,
2990 },
2991 )
2992 .unwrap();
2993
2994 let cloned = ctx.clone();
2996 assert_eq!(ctx.programs_count(), cloned.programs_count());
2997 }
2998
2999 #[test]
3004 fn parse_error_code_custom() {
3005 use solana_instruction::error::InstructionError;
3006 let err = TransactionError::InstructionError(0, InstructionError::Custom(6051));
3007 assert_eq!(parse_error_code(&err), Some(6051));
3008 }
3009
3010 #[test]
3011 fn parse_error_code_custom_zero() {
3012 use solana_instruction::error::InstructionError;
3013 let err = TransactionError::InstructionError(1, InstructionError::Custom(0));
3014 assert_eq!(parse_error_code(&err), Some(0));
3015 }
3016
3017 #[test]
3018 fn parse_error_code_custom_large() {
3019 use solana_instruction::error::InstructionError;
3020 let err = TransactionError::InstructionError(0, InstructionError::Custom(u32::MAX));
3021 assert_eq!(parse_error_code(&err), Some(u32::MAX));
3022 }
3023
3024 #[test]
3025 fn parse_error_code_non_custom_instruction_error() {
3026 use solana_instruction::error::InstructionError;
3027 let err = TransactionError::InstructionError(0, InstructionError::GenericError);
3028 assert_eq!(parse_error_code(&err), None);
3029 }
3030
3031 #[test]
3032 fn parse_error_code_non_instruction_error() {
3033 let err = TransactionError::AccountInUse;
3034 assert_eq!(parse_error_code(&err), None);
3035 }
3036
3037 #[test]
3042 fn parse_instruction_index_basic() {
3043 use solana_instruction::error::InstructionError;
3044 let err = TransactionError::InstructionError(0, InstructionError::Custom(42));
3045 assert_eq!(parse_instruction_index(&err), Some(0));
3046 }
3047
3048 #[test]
3049 fn parse_instruction_index_nonzero() {
3050 use solana_instruction::error::InstructionError;
3051 let err = TransactionError::InstructionError(3, InstructionError::GenericError);
3052 assert_eq!(parse_instruction_index(&err), Some(3));
3053 }
3054
3055 #[test]
3056 fn parse_instruction_index_max_u8() {
3057 use solana_instruction::error::InstructionError;
3058 let err = TransactionError::InstructionError(255, InstructionError::Custom(1));
3059 assert_eq!(parse_instruction_index(&err), Some(255));
3060 }
3061
3062 #[test]
3063 fn parse_instruction_index_non_instruction_error() {
3064 let err = TransactionError::AccountInUse;
3065 assert_eq!(parse_instruction_index(&err), None);
3066 }
3067
3068 fn clear_format_tls() {
3074 clear_action_history();
3075 clear_violation_tracking();
3076 }
3077
3078 #[test]
3079 fn format_action_sequence_empty() {
3080 clear_format_tls();
3081 let out = format_action_sequence();
3082 assert!(out.is_empty(), "expected empty for no history: {:?}", out);
3083 }
3084
3085 #[test]
3086 fn format_action_sequence_single_ok() {
3087 clear_format_tls();
3088 set_total_actions(1);
3089 push_action_record("action_deposit", serde_json::json!({"amount": 500}), true);
3090
3091 let out = format_action_sequence();
3092 assert!(out.contains("1 executed, 0 skipped"), "header: {out}");
3093 assert!(
3094 out.contains("action_deposit(amount=500) -> OK"),
3095 "body: {out}"
3096 );
3097 }
3098
3099 #[test]
3100 fn format_action_sequence_single_fail() {
3101 clear_format_tls();
3102 set_total_actions(1);
3103 push_action_record("action_withdraw", serde_json::json!({}), false);
3104
3105 let out = format_action_sequence();
3106 assert!(out.contains("action_withdraw -> FAIL"), "body: {out}");
3107 }
3108
3109 #[test]
3110 fn format_action_sequence_multiple_params() {
3111 clear_format_tls();
3112 set_total_actions(3);
3113 push_action_record(
3114 "action_deposit",
3115 serde_json::json!({"user": 0, "amount": 100}),
3116 true,
3117 );
3118 push_action_record("action_borrow", serde_json::json!({"user": 1}), true);
3119 push_action_record("action_repay", serde_json::json!({}), false);
3120
3121 let out = format_action_sequence();
3122 assert!(out.contains("3 executed, 0 skipped"), "header: {out}");
3123 assert!(out.contains("1. action_deposit("), "first: {out}");
3124 assert!(
3125 out.contains("2. action_borrow(user=1) -> OK"),
3126 "second: {out}"
3127 );
3128 assert!(out.contains("3. action_repay -> FAIL"), "third: {out}");
3129 }
3130
3131 #[test]
3132 fn format_action_sequence_with_violation_marker() {
3133 clear_format_tls();
3134 set_total_actions(3);
3135 push_action_record("action_a", serde_json::json!({}), true);
3136 push_action_record("action_b", serde_json::json!({}), true);
3137 set_violation_action_index(1);
3139
3140 let out = format_action_sequence();
3141 assert!(out.contains("2 executed, 1 skipped"), "header: {out}");
3142 assert!(
3143 !out.contains("1. action_a")
3144 || !out.contains("[VIOLATION]")
3145 || out.contains("action_a -> OK\n")
3146 || !out.matches("[VIOLATION]").count() > 1,
3147 "violation should only be on action_b"
3148 );
3149 assert!(
3150 out.contains("action_b -> OK [VIOLATION]"),
3151 "violation marker: {out}"
3152 );
3153 assert!(out.contains("1 action(s) not executed"), "skipped: {out}");
3154 }
3155
3156 #[test]
3157 fn format_action_sequence_skipped_actions() {
3158 clear_format_tls();
3159 set_total_actions(10);
3160 push_action_record("action_only", serde_json::json!({}), true);
3161
3162 let out = format_action_sequence();
3163 assert!(out.contains("1 executed, 9 skipped"), "header: {out}");
3164 assert!(out.contains("9 action(s) not executed"), "footer: {out}");
3165 }
3166
3167 #[test]
3168 fn format_action_sequence_no_params_no_parens() {
3169 clear_format_tls();
3170 set_total_actions(1);
3171 ACTION_HISTORY.with(|h| {
3173 h.borrow_mut().push(ActionRecord {
3174 name: "action_foo".to_string(),
3175 params: serde_json::Value::Null,
3176 success: true,
3177 error_code: None,
3178 });
3179 });
3180
3181 let out = format_action_sequence();
3182 assert!(out.contains("action_foo -> OK"), "body: {out}");
3184 assert!(
3185 !out.contains("action_foo("),
3186 "should not have parens: {out}"
3187 );
3188 }
3189
3190 #[test]
3195 fn format_last_action_oneline_empty() {
3196 clear_format_tls();
3197 let out = format_last_action_oneline();
3198 assert!(out.is_empty());
3199 }
3200
3201 #[test]
3202 fn format_last_action_oneline_success_with_params() {
3203 clear_format_tls();
3204 push_action_record("action_deposit", serde_json::json!({"amount": 42}), true);
3205
3206 let out = format_last_action_oneline();
3207 assert_eq!(out, "action_deposit(amount=42) -> OK");
3208 }
3209
3210 #[test]
3211 fn format_last_action_oneline_fail_no_params() {
3212 clear_format_tls();
3213 push_action_record("action_withdraw", serde_json::json!({}), false);
3214
3215 let out = format_last_action_oneline();
3216 assert_eq!(out, "action_withdraw -> FAIL");
3217 }
3218
3219 #[test]
3220 fn format_last_action_oneline_returns_last() {
3221 clear_format_tls();
3222 push_action_record("action_first", serde_json::json!({}), true);
3223 push_action_record("action_second", serde_json::json!({"x": 1}), false);
3224
3225 let out = format_last_action_oneline();
3226 assert!(
3227 out.starts_with("action_second"),
3228 "should return last: {out}"
3229 );
3230 assert!(out.contains("FAIL"), "last was failure: {out}");
3231 }
3232
3233 #[test]
3234 fn format_last_action_oneline_null_params() {
3235 clear_format_tls();
3236 ACTION_HISTORY.with(|h| {
3237 h.borrow_mut().push(ActionRecord {
3238 name: "action_lite".to_string(),
3239 params: serde_json::Value::Null,
3240 success: true,
3241 error_code: None,
3242 });
3243 });
3244
3245 let out = format_last_action_oneline();
3246 assert_eq!(out, "action_lite -> OK");
3247 }
3248
3249 #[test]
3254 fn format_action_sequence_nested_params() {
3255 clear_format_tls();
3256 set_total_actions(1);
3257 push_action_record(
3258 "action_complex",
3259 serde_json::json!({"arr": [1, 2], "flag": true, "label": "hi"}),
3260 true,
3261 );
3262
3263 let out = format_action_sequence();
3264 assert!(out.contains("arr=[1, 2]"), "array param: {out}");
3265 assert!(out.contains("flag=true"), "bool param: {out}");
3266 assert!(out.contains("label=\"hi\""), "string param: {out}");
3267 }
3268
3269 #[test]
3274 fn write_crash_metadata_creates_files() {
3275 let tmp = tempfile::tempdir().unwrap();
3276 let crash_dir = tmp.path().to_str().unwrap();
3277
3278 clear_format_tls();
3280 set_current_test_name("my_test");
3281 set_current_iteration(42);
3282 push_action_record("action_a", serde_json::json!({"x": 1}), true);
3283
3284 let input_bytes = b"crash_input_data";
3285 let hash: u64 = 0xDEADBEEF;
3286 write_crash_metadata(crash_dir, hash, Some(999), input_bytes);
3287
3288 let crash_id = format!("crash_{:016x}", hash);
3289
3290 let input_path = tmp.path().join(&crash_id);
3292 assert!(input_path.exists(), "input file should exist");
3293 assert_eq!(std::fs::read(&input_path).unwrap(), input_bytes);
3294
3295 let meta_path = tmp.path().join(format!("{}.meta.json", crash_id));
3297 assert!(meta_path.exists(), "meta file should exist");
3298 let meta_str = std::fs::read_to_string(&meta_path).unwrap();
3299 let meta: serde_json::Value = serde_json::from_str(&meta_str).unwrap();
3300
3301 assert_eq!(meta["test_name"], "my_test");
3302 assert_eq!(meta["iteration"], 42);
3303 assert_eq!(meta["seed"], 999);
3304 assert_eq!(meta["actions"].as_array().unwrap().len(), 1);
3305 assert_eq!(meta["actions"][0]["name"], "action_a");
3306 }
3307
3308 #[test]
3309 fn write_crash_metadata_no_seed() {
3310 let tmp = tempfile::tempdir().unwrap();
3311 let crash_dir = tmp.path().to_str().unwrap();
3312
3313 clear_format_tls();
3314 set_current_test_name("test2");
3315
3316 write_crash_metadata(crash_dir, 0x1234, None, b"data");
3317
3318 let meta_path = tmp.path().join("crash_0000000000001234.meta.json");
3319 let meta_str = std::fs::read_to_string(&meta_path).unwrap();
3320 let meta: serde_json::Value = serde_json::from_str(&meta_str).unwrap();
3321
3322 assert!(
3324 meta.get("seed").is_none() || meta["seed"].is_null(),
3325 "seed should be absent or null: {:?}",
3326 meta.get("seed")
3327 );
3328 }
3329
3330 #[test]
3335 fn write_crash_metadata_for_id_creates_meta() {
3336 let tmp = tempfile::tempdir().unwrap();
3337 let crash_dir = tmp.path().to_str().unwrap();
3338
3339 clear_format_tls();
3340 set_current_test_name("tmin_test");
3341 set_current_iteration(7);
3342 push_action_record("action_min", serde_json::json!({}), false);
3343
3344 write_crash_metadata_for_id(crash_dir, "crash_abc", Some(55));
3345
3346 let meta_path = tmp.path().join("crash_abc.meta.json");
3347 assert!(meta_path.exists(), "meta file should exist");
3348 let meta: serde_json::Value =
3349 serde_json::from_str(&std::fs::read_to_string(&meta_path).unwrap()).unwrap();
3350
3351 assert_eq!(meta["test_name"], "tmin_test");
3352 assert_eq!(meta["seed"], 55);
3353 assert_eq!(meta["actions"][0]["name"], "action_min");
3354 assert_eq!(meta["actions"][0]["success"], false);
3355 }
3356
3357 #[test]
3362 fn into_action_success_unit() {
3363 assert!(().into_success());
3364 }
3365
3366 #[test]
3367 fn into_action_success_result_ok() {
3368 let r: Result<(), String> = Ok(());
3369 assert!(r.into_success());
3370 }
3371
3372 #[test]
3373 fn into_action_success_result_err() {
3374 let r: Result<(), &str> = Err("boom");
3375 assert!(!r.into_success());
3376 }
3377
3378 #[test]
3379 fn into_action_success_bool() {
3380 assert!(true.into_success());
3381 assert!(!false.into_success());
3382 }
3383
3384 #[test]
3389 fn action_history_push_and_clear() {
3390 clear_action_history();
3391 push_action_record("a1", serde_json::json!({}), true);
3392 push_action_record("a2", serde_json::json!({"k": "v"}), false);
3393 let h = get_action_history();
3394 assert_eq!(h.len(), 2);
3395 assert_eq!(h[0].name, "a1");
3396 assert!(h[0].success);
3397 assert_eq!(h[1].name, "a2");
3398 assert!(!h[1].success);
3399
3400 clear_action_history();
3401 assert!(get_action_history().is_empty());
3402 }
3403
3404 #[test]
3405 fn backfill_action_params_updates_entry() {
3406 clear_action_history();
3407 push_action_record_lite("action_x", true);
3408 let h = get_action_history();
3409 assert!(h[0].params.is_null(), "lite record should have null params");
3410
3411 backfill_action_params(0, serde_json::json!({"filled": true}));
3412 let h = get_action_history();
3413 assert_eq!(h[0].params["filled"], true);
3414 }
3415
3416 #[test]
3417 fn backfill_action_params_out_of_bounds_noop() {
3418 clear_action_history();
3419 push_action_record_lite("a", true);
3420 backfill_action_params(99, serde_json::json!({"x": 1}));
3422 let h = get_action_history();
3423 assert!(h[0].params.is_null(), "original should be unchanged");
3424 }
3425
3426 #[test]
3431 fn violation_action_index_only_records_first() {
3432 clear_violation_tracking();
3433 set_violation_action_index(3);
3434 set_violation_action_index(7); assert_eq!(get_violation_action_index(), Some(3));
3436 }
3437
3438 #[test]
3439 fn clear_violation_tracking_resets_all() {
3440 set_total_actions(10);
3441 set_violation_action_index(5);
3442 clear_violation_tracking();
3443
3444 assert_eq!(get_violation_action_index(), None);
3445 clear_action_history();
3447 let out = format_action_sequence();
3448 assert!(out.is_empty(), "should be empty after full clear");
3449 }
3450
3451 #[test]
3456 fn succeeded_variants_tracking() {
3457 SUCCEEDED_VARIANTS.with(|s| s.borrow_mut().clear());
3459
3460 assert!(!has_variant_succeeded(0));
3461 assert!(!has_variant_succeeded(1));
3462 assert_eq!(succeeded_variant_count(), 0);
3463
3464 mark_variant_succeeded(0);
3465 assert!(has_variant_succeeded(0));
3466 assert!(!has_variant_succeeded(1));
3467 assert_eq!(succeeded_variant_count(), 1);
3468
3469 mark_variant_succeeded(0); assert_eq!(succeeded_variant_count(), 1);
3471
3472 mark_variant_succeeded(5);
3473 assert_eq!(succeeded_variant_count(), 2);
3474 }
3475
3476 #[test]
3481 fn dispatch_count_reset_and_increment() {
3482 reset_iteration_dispatch_count();
3483 assert_eq!(get_iteration_dispatch_count(), 1);
3485
3486 increment_action_count();
3487 increment_action_count();
3488 assert_eq!(get_iteration_dispatch_count(), 2);
3489
3490 reset_iteration_dispatch_count();
3491 assert_eq!(get_iteration_dispatch_count(), 1);
3492 }
3493
3494 #[test]
3499 fn build_crash_metadata_captures_tls() {
3500 clear_format_tls();
3501 set_current_test_name("crash_test");
3502 set_current_iteration(99);
3503 push_action_record("act_a", serde_json::json!({"p": 1}), true);
3504 push_action_record("act_b", serde_json::json!({}), false);
3505
3506 let meta = build_crash_metadata(Some(12345));
3507 assert_eq!(meta.test_name, "crash_test");
3508 assert_eq!(meta.iteration, 99);
3509 assert_eq!(meta.seed, Some(12345));
3510 assert_eq!(meta.actions.len(), 2);
3511 assert_eq!(meta.actions[0].name, "act_a");
3512 assert!(meta.actions[0].success);
3513 assert_eq!(meta.actions[1].name, "act_b");
3514 assert!(!meta.actions[1].success);
3515 }
3516
3517 #[test]
3518 fn build_crash_metadata_no_test_name() {
3519 clear_format_tls();
3520 CURRENT_TEST_NAME.with(|t| *t.borrow_mut() = None);
3521
3522 let meta = build_crash_metadata(None);
3523 assert_eq!(meta.test_name, "unknown");
3524 assert!(meta.seed.is_none());
3525 }
3526
3527 #[test]
3532 fn generic_builder_create_basic() {
3533 let mut ctx = TestContext::new();
3534 let pk = Pubkey::new_unique();
3535 let owner = Pubkey::new_unique();
3536
3537 let addr = ctx
3538 .create_account()
3539 .pubkey(pk)
3540 .owner(owner)
3541 .lamports(1_000_000)
3542 .size(128)
3543 .create()
3544 .unwrap();
3545
3546 assert_eq!(addr, pk);
3547 let acc = ctx.svm.get_account(&pk).unwrap();
3548 assert_eq!(acc.owner, owner);
3549 assert_eq!(acc.lamports, 1_000_000);
3550 assert_eq!(acc.data.len(), 128);
3551 assert!(!acc.executable);
3552 }
3553
3554 #[test]
3555 fn generic_builder_with_data() {
3556 let mut ctx = TestContext::new();
3557 let pk = Pubkey::new_unique();
3558 let data = vec![1, 2, 3, 4, 5];
3559
3560 ctx.create_account()
3561 .pubkey(pk)
3562 .lamports(1)
3563 .data(&data)
3564 .create()
3565 .unwrap();
3566
3567 let acc = ctx.svm.get_account(&pk).unwrap();
3568 assert_eq!(acc.data, data);
3569 }
3570
3571 #[test]
3572 fn generic_builder_default_address_errors() {
3573 let mut ctx = TestContext::new();
3574 let err = ctx.create_account().lamports(100).create().unwrap_err();
3575
3576 assert!(
3577 err.to_string().contains("Address must be set"),
3578 "expected address error: {}",
3579 err
3580 );
3581 }
3582
3583 #[test]
3584 fn generic_builder_tracks_account() {
3585 let mut ctx = TestContext::new();
3586 let pk = Pubkey::new_unique();
3587 let before = ctx.tracked_accounts_count();
3588
3589 ctx.create_account().pubkey(pk).create().unwrap();
3590
3591 assert_eq!(ctx.tracked_accounts_count(), before + 1);
3592 }
3593
3594 #[test]
3599 fn mint_builder_create_basic() {
3600 let mut ctx = TestContext::new();
3601 let mint_pk = Pubkey::new_unique();
3602 let authority = Pubkey::new_unique();
3603
3604 let addr = ctx
3605 .create_mint()
3606 .pubkey(mint_pk)
3607 .mint_authority(authority)
3608 .decimals(6)
3609 .supply(1_000_000)
3610 .create()
3611 .unwrap();
3612
3613 assert_eq!(addr, mint_pk);
3614
3615 let acc = ctx.svm.get_account(&mint_pk).unwrap();
3616 assert_eq!(acc.owner, spl_token::id());
3617 assert_eq!(acc.data.len(), spl_token::state::Mint::LEN);
3618
3619 let mint = spl_token::state::Mint::unpack(&acc.data).unwrap();
3621 assert_eq!(mint.decimals, 6);
3622 assert_eq!(mint.supply, 1_000_000);
3623 assert_eq!(mint.mint_authority, COption::Some(authority));
3624 assert!(mint.is_initialized);
3625 }
3626
3627 #[test]
3628 fn mint_builder_default_is_initialized() {
3629 let mut ctx = TestContext::new();
3630 let pk = Pubkey::new_unique();
3631
3632 ctx.create_mint().pubkey(pk).create().unwrap();
3633
3634 let acc = ctx.svm.get_account(&pk).unwrap();
3635 let mint = spl_token::state::Mint::unpack(&acc.data).unwrap();
3636 assert!(mint.is_initialized, "default mint should be initialized");
3637 }
3638
3639 #[test]
3640 fn mint_builder_freeze_authority() {
3641 let mut ctx = TestContext::new();
3642 let pk = Pubkey::new_unique();
3643 let freeze_auth = Pubkey::new_unique();
3644
3645 ctx.create_mint()
3646 .pubkey(pk)
3647 .freeze_authority(Some(freeze_auth))
3648 .create()
3649 .unwrap();
3650
3651 let acc = ctx.svm.get_account(&pk).unwrap();
3652 let mint = spl_token::state::Mint::unpack(&acc.data).unwrap();
3653 assert_eq!(mint.freeze_authority, COption::Some(freeze_auth));
3654 }
3655
3656 #[test]
3657 fn mint_builder_freeze_authority_none() {
3658 let mut ctx = TestContext::new();
3659 let pk = Pubkey::new_unique();
3660
3661 ctx.create_mint()
3662 .pubkey(pk)
3663 .freeze_authority(None)
3664 .create()
3665 .unwrap();
3666
3667 let acc = ctx.svm.get_account(&pk).unwrap();
3668 let mint = spl_token::state::Mint::unpack(&acc.data).unwrap();
3669 assert_eq!(mint.freeze_authority, COption::None);
3670 }
3671
3672 #[test]
3673 fn mint_builder_default_address_errors() {
3674 let mut ctx = TestContext::new();
3675 let err = ctx.create_mint().decimals(9).create().unwrap_err();
3676
3677 assert!(
3678 err.to_string().contains("Address must be set"),
3679 "expected address error: {}",
3680 err
3681 );
3682 }
3683
3684 #[test]
3685 fn mint_builder_has_rent_exempt_lamports() {
3686 let mut ctx = TestContext::new();
3687 let pk = Pubkey::new_unique();
3688
3689 ctx.create_mint().pubkey(pk).create().unwrap();
3690
3691 let acc = ctx.svm.get_account(&pk).unwrap();
3692 let rent = Rent::default();
3693 let min_lamports = rent.minimum_balance(spl_token::state::Mint::LEN);
3694 assert_eq!(
3695 acc.lamports, min_lamports,
3696 "mint should have rent-exempt lamports by default"
3697 );
3698 }
3699
3700 #[test]
3705 fn token_builder_create_basic() {
3706 let mut ctx = TestContext::new();
3707 let token_pk = Pubkey::new_unique();
3708 let mint_pk = Pubkey::new_unique();
3709 let owner_pk = Pubkey::new_unique();
3710
3711 let addr = ctx
3712 .create_token_account()
3713 .pubkey(token_pk)
3714 .mint(mint_pk)
3715 .token_owner(owner_pk)
3716 .amount(500)
3717 .create()
3718 .unwrap();
3719
3720 assert_eq!(addr, token_pk);
3721
3722 let acc = ctx.svm.get_account(&token_pk).unwrap();
3723 assert_eq!(acc.owner, spl_token::id());
3724 assert_eq!(acc.data.len(), spl_token::state::Account::LEN);
3725
3726 let token = spl_token::state::Account::unpack(&acc.data).unwrap();
3727 assert_eq!(token.mint, mint_pk);
3728 assert_eq!(token.owner, owner_pk);
3729 assert_eq!(token.amount, 500);
3730 assert_eq!(token.state, spl_token::state::AccountState::Initialized);
3731 }
3732
3733 #[test]
3734 fn token_builder_missing_mint_errors() {
3735 let mut ctx = TestContext::new();
3736 let pk = Pubkey::new_unique();
3737 let owner = Pubkey::new_unique();
3738
3739 let err = ctx
3740 .create_token_account()
3741 .pubkey(pk)
3742 .token_owner(owner)
3743 .create()
3744 .unwrap_err();
3745
3746 assert!(
3747 err.to_string().contains("Mint must be set"),
3748 "expected mint error: {}",
3749 err
3750 );
3751 }
3752
3753 #[test]
3754 fn token_builder_missing_owner_errors() {
3755 let mut ctx = TestContext::new();
3756 let pk = Pubkey::new_unique();
3757 let mint = Pubkey::new_unique();
3758
3759 let err = ctx
3760 .create_token_account()
3761 .pubkey(pk)
3762 .mint(mint)
3763 .create()
3764 .unwrap_err();
3765
3766 assert!(
3767 err.to_string().contains("Owner must be set"),
3768 "expected owner error: {}",
3769 err
3770 );
3771 }
3772
3773 #[test]
3774 fn token_builder_default_address_errors() {
3775 let mut ctx = TestContext::new();
3776 let mint = Pubkey::new_unique();
3777 let owner = Pubkey::new_unique();
3778
3779 let err = ctx
3780 .create_token_account()
3781 .mint(mint)
3782 .token_owner(owner)
3783 .create()
3784 .unwrap_err();
3785
3786 assert!(
3787 err.to_string().contains("Address must be set"),
3788 "expected address error: {}",
3789 err
3790 );
3791 }
3792
3793 #[test]
3794 fn token_builder_delegate() {
3795 let mut ctx = TestContext::new();
3796 let pk = Pubkey::new_unique();
3797 let mint = Pubkey::new_unique();
3798 let owner = Pubkey::new_unique();
3799 let delegate = Pubkey::new_unique();
3800
3801 ctx.create_token_account()
3802 .pubkey(pk)
3803 .mint(mint)
3804 .token_owner(owner)
3805 .delegate(Some(delegate))
3806 .delegated_amount(100)
3807 .create()
3808 .unwrap();
3809
3810 let acc = ctx.svm.get_account(&pk).unwrap();
3811 let token = spl_token::state::Account::unpack(&acc.data).unwrap();
3812 assert_eq!(token.delegate, COption::Some(delegate));
3813 assert_eq!(token.delegated_amount, 100);
3814 }
3815
3816 #[test]
3817 fn token_builder_close_authority() {
3818 let mut ctx = TestContext::new();
3819 let pk = Pubkey::new_unique();
3820 let mint = Pubkey::new_unique();
3821 let owner = Pubkey::new_unique();
3822 let close_auth = Pubkey::new_unique();
3823
3824 ctx.create_token_account()
3825 .pubkey(pk)
3826 .mint(mint)
3827 .token_owner(owner)
3828 .close_authority(Some(close_auth))
3829 .create()
3830 .unwrap();
3831
3832 let acc = ctx.svm.get_account(&pk).unwrap();
3833 let token = spl_token::state::Account::unpack(&acc.data).unwrap();
3834 assert_eq!(token.close_authority, COption::Some(close_auth));
3835 }
3836
3837 #[test]
3838 fn token_builder_is_native() {
3839 let mut ctx = TestContext::new();
3840 let pk = Pubkey::new_unique();
3841 let mint = Pubkey::new_unique();
3842 let owner = Pubkey::new_unique();
3843
3844 ctx.create_token_account()
3845 .pubkey(pk)
3846 .mint(mint)
3847 .token_owner(owner)
3848 .is_native(Some(1_000_000))
3849 .create()
3850 .unwrap();
3851
3852 let acc = ctx.svm.get_account(&pk).unwrap();
3853 let token = spl_token::state::Account::unpack(&acc.data).unwrap();
3854 assert_eq!(token.is_native, COption::Some(1_000_000));
3855 }
3856
3857 #[test]
3858 fn token_builder_has_rent_exempt_lamports() {
3859 let mut ctx = TestContext::new();
3860 let pk = Pubkey::new_unique();
3861 let mint = Pubkey::new_unique();
3862 let owner = Pubkey::new_unique();
3863
3864 ctx.create_token_account()
3865 .pubkey(pk)
3866 .mint(mint)
3867 .token_owner(owner)
3868 .create()
3869 .unwrap();
3870
3871 let acc = ctx.svm.get_account(&pk).unwrap();
3872 let rent = Rent::default();
3873 let min_lamports = rent.minimum_balance(spl_token::state::Account::LEN);
3874 assert_eq!(
3875 acc.lamports, min_lamports,
3876 "token account should have rent-exempt lamports by default"
3877 );
3878 }
3879
3880 #[test]
3885 fn builder_rent_epoch() {
3886 let mut ctx = TestContext::new();
3887 let pk = Pubkey::new_unique();
3888
3889 ctx.create_account()
3890 .pubkey(pk)
3891 .lamports(1)
3892 .rent_epoch(42)
3893 .create()
3894 .unwrap();
3895
3896 let acc = ctx.svm.get_account(&pk).unwrap();
3897 assert_eq!(acc.rent_epoch, 42);
3898 }
3899
3900 #[test]
3901 fn builder_lamports_override_on_mint() {
3902 let mut ctx = TestContext::new();
3903 let pk = Pubkey::new_unique();
3904
3905 ctx.create_mint().pubkey(pk).lamports(999).create().unwrap();
3907
3908 let acc = ctx.svm.get_account(&pk).unwrap();
3909 assert_eq!(acc.lamports, 999);
3910 }
3911
3912 #[test]
3917 fn generic_builder_overwrite_same_pubkey() {
3918 let mut ctx = TestContext::new();
3919 let pk = Pubkey::new_unique();
3920
3921 ctx.create_account()
3923 .pubkey(pk)
3924 .lamports(100)
3925 .data(&[1, 2, 3])
3926 .create()
3927 .unwrap();
3928 let acc1 = ctx.svm.get_account(&pk).unwrap();
3929 assert_eq!(acc1.data, vec![1, 2, 3]);
3930
3931 ctx.create_account()
3933 .pubkey(pk)
3934 .lamports(200)
3935 .data(&[4, 5])
3936 .create()
3937 .unwrap();
3938 let acc2 = ctx.svm.get_account(&pk).unwrap();
3939 assert_eq!(acc2.data, vec![4, 5]);
3940 assert_eq!(acc2.lamports, 200);
3941 }
3942
3943 #[test]
3944 fn mint_builder_zero_decimals() {
3945 let mut ctx = TestContext::new();
3947 let pk = Pubkey::new_unique();
3948
3949 ctx.create_mint().pubkey(pk).decimals(0).create().unwrap();
3950
3951 let acc = ctx.svm.get_account(&pk).unwrap();
3952 let mint = spl_token::state::Mint::unpack(&acc.data).unwrap();
3953 assert_eq!(mint.decimals, 0);
3954 }
3955
3956 #[test]
3957 fn token_builder_max_amount() {
3958 let mut ctx = TestContext::new();
3959 let pk = Pubkey::new_unique();
3960 let mint = Pubkey::new_unique();
3961 let owner = Pubkey::new_unique();
3962
3963 ctx.create_token_account()
3964 .pubkey(pk)
3965 .mint(mint)
3966 .token_owner(owner)
3967 .amount(u64::MAX)
3968 .create()
3969 .unwrap();
3970
3971 let acc = ctx.svm.get_account(&pk).unwrap();
3972 let token = spl_token::state::Account::unpack(&acc.data).unwrap();
3973 assert_eq!(token.amount, u64::MAX);
3974 }
3975
3976 #[test]
3977 fn token_builder_frozen_state() {
3978 let mut ctx = TestContext::new();
3979 let pk = Pubkey::new_unique();
3980 let mint = Pubkey::new_unique();
3981 let owner = Pubkey::new_unique();
3982
3983 ctx.create_token_account()
3984 .pubkey(pk)
3985 .mint(mint)
3986 .token_owner(owner)
3987 .state(spl_token::state::AccountState::Frozen)
3988 .create()
3989 .unwrap();
3990
3991 let acc = ctx.svm.get_account(&pk).unwrap();
3992 let token = spl_token::state::Account::unpack(&acc.data).unwrap();
3993 assert_eq!(token.state, spl_token::state::AccountState::Frozen);
3994 }
3995
3996 #[test]
3997 fn builder_chaining_order_independent() {
3998 let mut ctx = TestContext::new();
3999 let mint = Pubkey::new_unique();
4000 let owner = Pubkey::new_unique();
4001 let pk1 = Pubkey::new_unique();
4002 let pk2 = Pubkey::new_unique();
4003
4004 ctx.create_token_account()
4006 .mint(mint)
4007 .pubkey(pk1)
4008 .token_owner(owner)
4009 .amount(42)
4010 .create()
4011 .unwrap();
4012
4013 ctx.create_token_account()
4015 .amount(42)
4016 .token_owner(owner)
4017 .pubkey(pk2)
4018 .mint(mint)
4019 .create()
4020 .unwrap();
4021
4022 let acc1 = ctx.svm.get_account(&pk1).unwrap();
4023 let acc2 = ctx.svm.get_account(&pk2).unwrap();
4024 let token1 = spl_token::state::Account::unpack(&acc1.data).unwrap();
4025 let token2 = spl_token::state::Account::unpack(&acc2.data).unwrap();
4026
4027 assert_eq!(token1.mint, token2.mint);
4028 assert_eq!(token1.owner, token2.owner);
4029 assert_eq!(token1.amount, token2.amount);
4030 }
4031
4032 #[test]
4033 fn generic_builder_zero_length_data_with_lamports() {
4034 let mut ctx = TestContext::new();
4035 let pk = Pubkey::new_unique();
4036
4037 ctx.create_account()
4038 .pubkey(pk)
4039 .lamports(1_000_000)
4040 .size(0) .create()
4042 .unwrap();
4043
4044 let acc = ctx.svm.get_account(&pk).unwrap();
4045 assert_eq!(acc.lamports, 1_000_000);
4046 assert!(acc.data.is_empty());
4047 }
4048
4049 #[test]
4054 fn format_action_sequence_violation_at_index_0() {
4055 clear_format_tls();
4056 set_total_actions(2);
4057 push_action_record("action_first", serde_json::json!({}), false);
4058 push_action_record("action_second", serde_json::json!({}), true);
4059 set_violation_action_index(0);
4060
4061 let out = format_action_sequence();
4062 assert!(
4063 out.contains("action_first -> FAIL [VIOLATION]"),
4064 "violation should be on first: {out}"
4065 );
4066 assert!(
4067 !out.contains("action_second -> OK [VIOLATION]"),
4068 "second should not have marker: {out}"
4069 );
4070 }
4071
4072 #[test]
4073 fn format_action_sequence_deeply_nested_params() {
4074 clear_format_tls();
4075 set_total_actions(1);
4076 push_action_record(
4077 "action_nested",
4078 serde_json::json!({"a": {"b": {"c": 1}}}),
4079 true,
4080 );
4081
4082 let out = format_action_sequence();
4083 assert!(out.contains("a={"), "nested object: {out}");
4085 assert!(out.contains("c: 1"), "deeply nested value: {out}");
4086 }
4087
4088 #[test]
4089 fn format_action_sequence_total_0_with_actions() {
4090 clear_format_tls();
4092 push_action_record("orphan", serde_json::json!({}), true);
4094
4095 let out = format_action_sequence();
4096 assert!(
4098 out.contains("1 executed, 0 skipped"),
4099 "should handle mismatch: {out}"
4100 );
4101 }
4102
4103 #[test]
4104 fn format_action_sequence_many_actions() {
4105 clear_format_tls();
4106 set_total_actions(50);
4107 for i in 0..50 {
4108 push_action_record(
4109 &format!("action_{}", i),
4110 serde_json::json!({"i": i}),
4111 i % 3 != 0,
4112 );
4113 }
4114
4115 let out = format_action_sequence();
4116 assert!(out.contains("50 executed, 0 skipped"), "header: {out}");
4117 assert!(out.contains("1. action_0"), "first: {out}");
4118 assert!(out.contains("50. action_49"), "last: {out}");
4119 }
4120
4121 #[test]
4122 fn format_last_action_oneline_multiple_params_ordering() {
4123 clear_format_tls();
4124 push_action_record(
4126 "action_multi",
4127 serde_json::json!({"amount": 100, "user": 2}),
4128 true,
4129 );
4130 let out = format_last_action_oneline();
4131 assert!(out.contains("amount=100"), "amount: {out}");
4133 assert!(out.contains("user=2"), "user: {out}");
4134 assert!(out.contains("-> OK"), "status: {out}");
4135 }
4136
4137 #[test]
4143 fn action_record_includes_params_in_sequence() {
4144 clear_format_tls();
4145 set_total_actions(3);
4146 push_action_record(
4147 "delegate_stake",
4148 serde_json::json!({"authority": null, "stake_account": 1340788527u64, "vote_account": 640494879u64}),
4149 true,
4150 );
4151 push_action_record("advance_slots", serde_json::json!({"slots": 54177}), true);
4152 push_action_record(
4153 "withdraw",
4154 serde_json::json!({"stake_account": 3, "lamports": 1000, "leave_reserve": true}),
4155 false,
4156 );
4157
4158 let out = format_action_sequence();
4159 assert!(
4161 out.contains("authority=null"),
4162 "authority param missing: {out}"
4163 );
4164 assert!(
4165 out.contains("stake_account=1340788527"),
4166 "stake_account param missing: {out}"
4167 );
4168 assert!(
4169 out.contains("vote_account=640494879"),
4170 "vote_account param missing: {out}"
4171 );
4172 assert!(out.contains("slots=54177"), "slots param missing: {out}");
4173 assert!(
4174 out.contains("leave_reserve=true"),
4175 "leave_reserve param missing: {out}"
4176 );
4177 assert!(
4178 out.contains("lamports=1000"),
4179 "lamports param missing: {out}"
4180 );
4181 assert!(
4183 out.contains("delegate_stake(") && out.contains(") -> OK"),
4184 "delegate_stake format: {out}"
4185 );
4186 assert!(
4187 out.contains("withdraw(") && out.contains(") -> FAIL"),
4188 "withdraw format: {out}"
4189 );
4190 }
4191
4192 #[test]
4193 fn action_record_params_in_oneline() {
4194 clear_format_tls();
4195 push_action_record(
4196 "move_lamports",
4197 serde_json::json!({"dest_account": 42, "lamports": null, "stake_account": 99}),
4198 true,
4199 );
4200
4201 let out = format_last_action_oneline();
4202 assert!(out.contains("move_lamports("), "should have parens: {out}");
4203 assert!(out.contains("dest_account=42"), "dest_account: {out}");
4204 assert!(out.contains("lamports=null"), "lamports null: {out}");
4205 assert!(out.contains("stake_account=99"), "stake_account: {out}");
4206 assert!(out.contains("-> OK"), "status: {out}");
4207 }
4208
4209 #[test]
4210 fn action_record_lite_omits_params_regression() {
4211 clear_format_tls();
4214 set_total_actions(1);
4215 push_action_record_lite("delegate_stake", true);
4216
4217 let out = format_action_sequence();
4218 assert!(
4220 out.contains("delegate_stake -> OK"),
4221 "should have no params: {out}"
4222 );
4223 assert!(
4224 !out.contains("delegate_stake("),
4225 "should not have parens: {out}"
4226 );
4227 }
4228
4229 #[test]
4234 fn parse_error_code_anchor_custom_6000() {
4235 use solana_instruction::error::InstructionError;
4236 let err = TransactionError::InstructionError(0, InstructionError::Custom(6000));
4237 assert_eq!(parse_error_code(&err), Some(6000));
4238 }
4239
4240 #[test]
4241 fn parse_error_code_insufficient_funds() {
4242 use solana_instruction::error::InstructionError;
4243 let err = TransactionError::InstructionError(0, InstructionError::InsufficientFunds);
4244 assert_eq!(
4245 parse_error_code(&err),
4246 None,
4247 "InsufficientFunds has no Custom(N)"
4248 );
4249 }
4250
4251 #[test]
4252 fn parse_error_code_account_already_initialized() {
4253 use solana_instruction::error::InstructionError;
4254 let err =
4255 TransactionError::InstructionError(2, InstructionError::AccountAlreadyInitialized);
4256 assert_eq!(parse_error_code(&err), None);
4257 assert_eq!(parse_instruction_index(&err), Some(2));
4259 }
4260
4261 #[test]
4266 fn parse_error_code_duplicate_instruction() {
4267 let err = TransactionError::DuplicateInstruction(2);
4269 assert_eq!(parse_error_code(&err), None);
4270 assert_eq!(parse_instruction_index(&err), None);
4271 }
4272
4273 #[test]
4274 fn parse_instruction_index_with_non_custom_error() {
4275 use solana_instruction::error::InstructionError;
4276 let err =
4278 TransactionError::InstructionError(4, InstructionError::ComputationalBudgetExceeded);
4279 assert_eq!(parse_error_code(&err), None);
4280 assert_eq!(parse_instruction_index(&err), Some(4));
4281 }
4282
4283 #[test]
4284 fn parse_error_code_custom_one() {
4285 use solana_instruction::error::InstructionError;
4286 let err = TransactionError::InstructionError(0, InstructionError::Custom(1));
4288 assert_eq!(parse_error_code(&err), Some(1));
4289 }
4290
4291 #[test]
4292 fn parse_both_from_same_error() {
4293 use solana_instruction::error::InstructionError;
4294 let err = TransactionError::InstructionError(7, InstructionError::Custom(9999));
4296 assert_eq!(parse_error_code(&err), Some(9999));
4297 assert_eq!(parse_instruction_index(&err), Some(7));
4298 }
4299
4300 #[test]
4301 fn tx_result_to_outcome_success() {
4302 use litesvm::types::TransactionMetadata;
4303 let meta = TransactionMetadata {
4304 compute_units_consumed: 42,
4305 logs: vec!["log".to_string()],
4306 ..Default::default()
4307 };
4308 let outcome = tx_result_to_outcome(Ok(meta));
4309 assert!(outcome.is_success());
4310 assert_eq!(outcome.compute_units(), Some(42));
4311 assert_eq!(outcome.error_code(), None);
4312 }
4313
4314 #[test]
4315 fn tx_result_to_outcome_error_sets_tls() {
4316 use litesvm::types::{FailedTransactionMetadata, TransactionMetadata};
4317 use solana_instruction::error::InstructionError;
4318 let failed = FailedTransactionMetadata {
4319 err: TransactionError::InstructionError(1, InstructionError::Custom(6051)),
4320 meta: TransactionMetadata {
4321 logs: vec!["err log".to_string()],
4322 ..Default::default()
4323 },
4324 };
4325 let outcome = tx_result_to_outcome(Err(failed));
4326 assert!(outcome.is_error());
4327 assert_eq!(outcome.error_code(), Some(6051));
4328 let tls_code = take_last_error_code();
4330 assert_eq!(tls_code, Some(6051));
4331 }
4332
4333 #[test]
4338 fn test_snapshot_multiple_writes_same_account() {
4339 let mut ctx = TestContext::new();
4340 let pk = Pubkey::new_unique();
4341
4342 ctx.write_account(
4344 &pk,
4345 Account {
4346 lamports: 100,
4347 data: vec![1],
4348 owner: Pubkey::new_unique(),
4349 executable: false,
4350 rent_epoch: 0,
4351 },
4352 )
4353 .unwrap();
4354 ctx.dirty_tracker.mark_account_dirty(&pk);
4355
4356 ctx.take_snapshot();
4357
4358 ctx.write_account(
4360 &pk,
4361 Account {
4362 lamports: 200,
4363 data: vec![2],
4364 owner: Pubkey::new_unique(),
4365 executable: false,
4366 rent_epoch: 0,
4367 },
4368 )
4369 .unwrap();
4370 ctx.dirty_tracker.mark_account_dirty(&pk);
4371
4372 ctx.write_account(
4374 &pk,
4375 Account {
4376 lamports: 300,
4377 data: vec![3],
4378 owner: Pubkey::new_unique(),
4379 executable: false,
4380 rent_epoch: 0,
4381 },
4382 )
4383 .unwrap();
4384 ctx.dirty_tracker.mark_account_dirty(&pk);
4385
4386 ctx.restore_snapshot();
4388 let acc = ctx.svm.get_account(&pk).unwrap();
4389 assert_eq!(
4390 acc.lamports, 100,
4391 "restore should use snapshot value, not intermediate"
4392 );
4393 assert_eq!(acc.data, vec![1]);
4394 }
4395
4396 #[test]
4397 fn test_snapshot_large_account_data_integrity() {
4398 let mut ctx = TestContext::new();
4399 let pk = Pubkey::new_unique();
4400
4401 let original_data: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
4403 ctx.write_account(
4404 &pk,
4405 Account {
4406 lamports: 1_000_000,
4407 data: original_data.clone(),
4408 owner: Pubkey::new_unique(),
4409 executable: false,
4410 rent_epoch: 0,
4411 },
4412 )
4413 .unwrap();
4414 ctx.dirty_tracker.mark_account_dirty(&pk);
4415
4416 ctx.take_snapshot();
4417
4418 ctx.write_account(
4420 &pk,
4421 Account {
4422 lamports: 1_000_000,
4423 data: vec![0xFF; 10_000],
4424 owner: Pubkey::new_unique(),
4425 executable: false,
4426 rent_epoch: 0,
4427 },
4428 )
4429 .unwrap();
4430 ctx.dirty_tracker.mark_account_dirty(&pk);
4431
4432 ctx.restore_snapshot();
4433 let acc = ctx.svm.get_account(&pk).unwrap();
4434 assert_eq!(
4435 acc.data, original_data,
4436 "10KB data should be perfectly restored"
4437 );
4438 }
4439
4440 use std::sync::Mutex;
4446 static ENV_MUTEX: Mutex<()> = Mutex::new(());
4447
4448 fn find_test_so() -> String {
4450 std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
4451 .join("test-data/staking.so")
4452 .to_string_lossy()
4453 .into_owned()
4454 }
4455
4456 #[test]
4457 fn test_add_program_override_env_var() {
4458 let _lock = ENV_MUTEX.lock().unwrap();
4459 let so_path = find_test_so();
4460
4461 std::env::set_var("FUZZ_PROGRAM_SO", &so_path);
4463
4464 let mut ctx = TestContext::new();
4465 let program_id = Pubkey::new_unique();
4466
4467 let result = ctx.add_program(&program_id, "/nonexistent/bogus.so");
4469 std::env::remove_var("FUZZ_PROGRAM_SO");
4470
4471 assert!(
4472 result.is_ok(),
4473 "Override should load from FUZZ_PROGRAM_SO, not the bogus path"
4474 );
4475 }
4476
4477 #[test]
4478 fn test_add_program_normal_path() {
4479 let _lock = ENV_MUTEX.lock().unwrap();
4480 std::env::remove_var("FUZZ_PROGRAM_SO");
4481
4482 let so_path = find_test_so();
4483 let mut ctx = TestContext::new();
4484 let program_id = Pubkey::new_unique();
4485
4486 let result = ctx.add_program(&program_id, &so_path);
4487 assert!(
4488 result.is_ok(),
4489 "Normal add_program should work without override"
4490 );
4491 }
4492
4493 #[test]
4494 fn test_add_program_override_nonexistent_errors() {
4495 let _lock = ENV_MUTEX.lock().unwrap();
4496
4497 std::env::set_var("FUZZ_PROGRAM_SO", "/tmp/does_not_exist_xyz.so");
4499
4500 let mut ctx = TestContext::new();
4501 let program_id = Pubkey::new_unique();
4502
4503 let result = ctx.add_program(&program_id, "/also/bogus.so");
4504 std::env::remove_var("FUZZ_PROGRAM_SO");
4505
4506 assert!(
4507 result.is_err(),
4508 "Override pointing to nonexistent file should error"
4509 );
4510 }
4511
4512 #[test]
4517 fn test_program_builder_fee_payer() {
4518 let mut ctx = TestContext::new();
4519 let program_id = Pubkey::new_unique();
4520 let fee_payer = Keypair::new();
4521 let signer = Keypair::new();
4522
4523 let builder = ctx
4524 .program(program_id)
4525 .fee_payer(&fee_payer)
4526 .signers(&[&signer]);
4527
4528 assert_eq!(builder.fee_payer, Some(fee_payer.insecure_clone()));
4529
4530 let builder = ctx
4531 .program(program_id)
4532 .signers(&[&signer])
4533 .fee_payer(&fee_payer);
4534
4535 assert_eq!(builder.fee_payer, Some(fee_payer));
4536 }
4537
4538 #[test]
4544 fn tx_result_to_outcome_success_preserves_all_metadata() {
4545 use litesvm::types::TransactionMetadata;
4546 use solana_message::compiled_instruction::CompiledInstruction;
4547 use solana_message::inner_instruction::InnerInstruction;
4548
4549 let sig = Signature::from([1u8; 64]);
4550 let return_program = Pubkey::new_unique();
4551 let inner_ix = InnerInstruction {
4552 instruction: CompiledInstruction {
4553 program_id_index: 2,
4554 accounts: vec![0, 1],
4555 data: vec![0xAB, 0xCD],
4556 },
4557 stack_height: 2,
4558 };
4559
4560 let meta = TransactionMetadata {
4561 signature: sig,
4562 logs: vec!["Program invoked".to_string()],
4563 inner_instructions: vec![vec![inner_ix]],
4564 compute_units_consumed: 12345,
4565 return_data: TransactionReturnData {
4566 program_id: return_program,
4567 data: vec![1, 2, 3, 4],
4568 },
4569 fee: 5000,
4570 };
4571
4572 let outcome = tx_result_to_outcome(Ok(meta));
4573
4574 assert!(outcome.is_success());
4576 assert_eq!(outcome.compute_units(), Some(12345));
4577 assert_eq!(outcome.logs(), &["Program invoked"]);
4578 assert_eq!(*outcome.signature(), sig);
4579 assert_eq!(outcome.fee(), 5000);
4580 assert_eq!(outcome.return_data().program_id, return_program);
4581 assert_eq!(outcome.return_data().data, vec![1, 2, 3, 4]);
4582 assert_eq!(outcome.inner_instructions().len(), 1);
4583 assert_eq!(outcome.inner_instructions()[0].len(), 1);
4584 assert_eq!(outcome.inner_instructions()[0][0].stack_height, 2);
4585 assert_eq!(
4586 outcome.inner_instructions()[0][0].instruction.data,
4587 vec![0xAB, 0xCD]
4588 );
4589 }
4590
4591 #[test]
4592 fn tx_result_to_outcome_error_preserves_all_metadata() {
4593 use litesvm::types::{FailedTransactionMetadata, TransactionMetadata};
4594 use solana_instruction::error::InstructionError;
4595
4596 let sig = Signature::from([1u8; 64]);
4597 let failed = FailedTransactionMetadata {
4598 err: TransactionError::InstructionError(2, InstructionError::Custom(9999)),
4599 meta: TransactionMetadata {
4600 signature: sig,
4601 logs: vec!["Program failed".to_string()],
4602 inner_instructions: vec![vec![], vec![]],
4603 compute_units_consumed: 500,
4604 return_data: TransactionReturnData {
4605 program_id: Pubkey::new_unique(),
4606 data: vec![0xFF],
4607 },
4608 fee: 7500,
4609 },
4610 };
4611
4612 let outcome = tx_result_to_outcome(Err(failed));
4613
4614 assert!(outcome.is_error());
4615 assert_eq!(outcome.error_code(), Some(9999));
4616 assert_eq!(*outcome.signature(), sig);
4617 assert_eq!(outcome.fee(), 7500);
4618 assert_eq!(outcome.return_data().data, vec![0xFF]);
4619 assert_eq!(outcome.inner_instructions().len(), 2);
4620 assert_eq!(outcome.logs(), &["Program failed"]);
4621 }
4622
4623 #[test]
4624 fn tx_outcome_into_result_preserves_new_fields_in_tx_error() {
4625 let sig = Signature::from([1u8; 64]);
4626 let return_program = Pubkey::new_unique();
4627
4628 let error = TxOutcome::ProgramError {
4629 error: TransactionError::AccountInUse,
4630 error_code: Some(42),
4631 instruction_index: Some(3),
4632 logs: vec!["log".to_string()],
4633 signature: sig,
4634 inner_instructions: vec![vec![]],
4635 return_data: TransactionReturnData {
4636 program_id: return_program,
4637 data: vec![10, 20],
4638 },
4639 fee: 3000,
4640 };
4641
4642 let err = error.into_result().unwrap_err();
4643
4644 assert_eq!(err.error_code, Some(42));
4646 assert_eq!(err.instruction_index, Some(3));
4647 assert_eq!(err.signature, sig);
4648 assert_eq!(err.fee, 3000);
4649 assert_eq!(err.return_data.program_id, return_program);
4650 assert_eq!(err.return_data.data, vec![10, 20]);
4651 assert_eq!(err.inner_instructions.len(), 1);
4652 assert_eq!(err.logs, vec!["log"]);
4653 }
4654
4655 #[test]
4656 fn tx_outcome_accessors_agree_across_variants() {
4657 let sig = Signature::from([1u8; 64]);
4659 let rd = TransactionReturnData {
4660 program_id: Pubkey::new_unique(),
4661 data: vec![42],
4662 };
4663
4664 let success = TxOutcome::Success {
4665 compute_units: 0,
4666 logs: vec![],
4667 signature: sig,
4668 inner_instructions: vec![],
4669 return_data: rd.clone(),
4670 fee: 100,
4671 };
4672
4673 let error = TxOutcome::ProgramError {
4674 error: TransactionError::AccountInUse,
4675 error_code: None,
4676 instruction_index: None,
4677 logs: vec![],
4678 signature: sig,
4679 inner_instructions: vec![],
4680 return_data: rd.clone(),
4681 fee: 100,
4682 };
4683
4684 assert_eq!(*success.signature(), *error.signature());
4685 assert_eq!(success.fee(), error.fee());
4686 assert_eq!(success.return_data().data, error.return_data().data);
4687 assert_eq!(
4688 success.inner_instructions().len(),
4689 error.inner_instructions().len()
4690 );
4691 }
4692
4693 #[test]
4699 fn program_coverage_totals_shared_across_clones() {
4700 let _lock = ENV_MUTEX.lock().unwrap();
4701 std::env::remove_var("FUZZ_PROGRAM_SO");
4702
4703 let so_path = find_test_so();
4704 let mut ctx = TestContext::new();
4705 let program_id = Pubkey::new_unique();
4706 ctx.add_program(&program_id, &so_path).unwrap();
4707
4708 let totals = ctx.get_program_coverage_totals();
4710 assert!(
4711 totals.contains_key(&program_id),
4712 "program should have coverage totals"
4713 );
4714 let (edges, instrs) = totals[&program_id];
4715 assert!(edges > 0, "should have edges: {}", edges);
4716 assert!(instrs > 0, "should have instructions: {}", instrs);
4717
4718 let cloned = ctx.clone();
4720 let cloned_totals = cloned.get_program_coverage_totals();
4721 assert_eq!(
4722 cloned_totals[&program_id],
4723 (edges, instrs),
4724 "clone must have identical coverage totals"
4725 );
4726
4727 }
4731
4732 #[test]
4733 fn program_coverage_totals_empty_without_program() {
4734 let ctx = TestContext::new();
4735 assert!(
4736 ctx.get_program_coverage_totals().is_empty(),
4737 "fresh context should have no coverage totals"
4738 );
4739
4740 let cloned = ctx.clone();
4741 assert!(
4742 cloned.get_program_coverage_totals().is_empty(),
4743 "clone of fresh context should also be empty"
4744 );
4745 }
4746
4747 #[test]
4753 fn analyze_program_coverage_returns_nonzero_for_valid_binary() {
4754 let _lock = ENV_MUTEX.lock().unwrap();
4755 std::env::remove_var("FUZZ_PROGRAM_SO");
4756
4757 let so_path = find_test_so();
4758 let program_data = std::fs::read(&so_path).unwrap();
4759
4760 let result = TestContext::analyze_program_coverage(&program_data);
4761 assert!(
4762 result.is_some(),
4763 "valid SBF binary should produce coverage data"
4764 );
4765
4766 let (edges, instructions) = result.unwrap();
4767 assert!(edges > 0, "should have conditional edges: {}", edges);
4768 assert!(
4769 instructions > 0,
4770 "should have instructions: {}",
4771 instructions
4772 );
4773
4774 assert!(
4777 edges < instructions * 2,
4778 "edges ({}) should be < 2*instructions ({})",
4779 edges,
4780 instructions
4781 );
4782 }
4783
4784 #[test]
4785 fn analyze_program_coverage_reachable_subset_of_total() {
4786 use solana_sbpf::elf::Executable;
4790 use solana_sbpf::program::BuiltinProgram;
4791 use solana_sbpf::static_analysis::Analysis;
4792 use solana_sbpf::vm::ContextObject;
4793
4794 let _lock = ENV_MUTEX.lock().unwrap();
4795 std::env::remove_var("FUZZ_PROGRAM_SO");
4796
4797 let so_path = find_test_so();
4798 let program_data = std::fs::read(&so_path).unwrap();
4799
4800 let (bfs_edges, bfs_instructions) =
4802 TestContext::analyze_program_coverage(&program_data).unwrap();
4803
4804 struct DummyContext;
4806 impl ContextObject for DummyContext {
4807 fn consume(&mut self, _amount: u64) {}
4808 fn get_remaining(&self) -> u64 {
4809 0
4810 }
4811 }
4812 let loader = Arc::new(BuiltinProgram::<DummyContext>::new_mock());
4813 let executable = Executable::from_elf(&program_data, loader).unwrap();
4814 let analysis = Analysis::from_executable(&executable).unwrap();
4815 let total_all_instructions = analysis.instructions.len();
4816
4817 assert!(
4819 bfs_instructions <= total_all_instructions,
4820 "BFS instructions ({}) should be <= total instructions ({})",
4821 bfs_instructions,
4822 total_all_instructions
4823 );
4824
4825 eprintln!(
4829 "[TEST] BFS reachable: {} instructions, {} edges. Total in binary: {} instructions. \
4830 Excluded: {} instructions ({:.1}%)",
4831 bfs_instructions,
4832 bfs_edges,
4833 total_all_instructions,
4834 total_all_instructions - bfs_instructions,
4835 ((total_all_instructions - bfs_instructions) as f64 / total_all_instructions as f64)
4836 * 100.0
4837 );
4838 }
4839
4840 #[test]
4841 fn analyze_program_coverage_returns_none_for_garbage() {
4842 let garbage = vec![0u8; 64];
4843 assert!(
4844 TestContext::analyze_program_coverage(&garbage).is_none(),
4845 "garbage bytes should not parse as valid SBF"
4846 );
4847 }
4848
4849 #[test]
4850 fn analyze_program_coverage_returns_none_for_empty() {
4851 assert!(
4852 TestContext::analyze_program_coverage(&[]).is_none(),
4853 "empty bytes should not parse as valid SBF"
4854 );
4855 }
4856
4857 #[test]
4858 fn analyze_program_coverage_edges_only_from_conditional_jumps() {
4859 let _lock = ENV_MUTEX.lock().unwrap();
4860 std::env::remove_var("FUZZ_PROGRAM_SO");
4861
4862 let so_path = find_test_so();
4863 let program_data = std::fs::read(&so_path).unwrap();
4864
4865 let (edges, _) = TestContext::analyze_program_coverage(&program_data).unwrap();
4866
4867 assert_eq!(
4870 edges % 2,
4871 0,
4872 "edge count ({}) should be even (each conditional branch = 2 edges)",
4873 edges
4874 );
4875 }
4876
4877 fn fund_keypair(ctx: &mut TestContext) -> Keypair {
4883 let kp = Keypair::new();
4884 ctx.svm.airdrop(&kp.pubkey(), 10_000_000_000).unwrap();
4885 kp
4886 }
4887
4888 #[test]
4889 fn raw_call_with_signers_no_fee_payer() {
4890 let mut ctx = TestContext::new();
4892 let payer = fund_keypair(&mut ctx);
4893 let recipient = Pubkey::new_unique();
4894
4895 let ix = anchor_lang::solana_program::system_instruction::transfer(
4896 &payer.pubkey(),
4897 &recipient,
4898 1000,
4899 );
4900 let result = ctx.raw_call(ix).signers(&[&payer]).send();
4901 assert!(
4902 result.is_ok(),
4903 "raw_call with signers should succeed: {:?}",
4904 result.err()
4905 );
4906 assert!(result.unwrap().is_success());
4907 }
4908
4909 #[test]
4910 fn raw_call_with_explicit_fee_payer() {
4911 let mut ctx = TestContext::new();
4913 let payer = fund_keypair(&mut ctx);
4914 let recipient = Pubkey::new_unique();
4915
4916 let ix = anchor_lang::solana_program::system_instruction::transfer(
4917 &payer.pubkey(),
4918 &recipient,
4919 1000,
4920 );
4921 let result = ctx.raw_call(ix).fee_payer(&payer).send();
4922 assert!(
4923 result.is_ok(),
4924 "raw_call with fee_payer should succeed: {:?}",
4925 result.err()
4926 );
4927 assert!(result.unwrap().is_success());
4928 }
4929
4930 #[test]
4931 fn raw_call_fee_payer_prepended_to_signers() {
4932 let mut ctx = TestContext::new();
4934 let payer = fund_keypair(&mut ctx);
4935 let other_signer = fund_keypair(&mut ctx);
4936 let recipient = Pubkey::new_unique();
4937
4938 let ix = anchor_lang::solana_program::system_instruction::transfer(
4939 &payer.pubkey(),
4940 &recipient,
4941 1000,
4942 );
4943 let result = ctx
4944 .raw_call(ix)
4945 .fee_payer(&payer)
4946 .signers(&[&other_signer])
4947 .send();
4948 assert!(
4949 result.is_ok(),
4950 "fee_payer + signers should succeed: {:?}",
4951 result.err()
4952 );
4953 }
4954
4955 #[test]
4956 fn raw_call_no_signers_no_fee_payer_errors() {
4957 let mut ctx = TestContext::new();
4959 let ix = anchor_lang::solana_program::system_instruction::transfer(
4960 &Pubkey::new_unique(),
4961 &Pubkey::new_unique(),
4962 1000,
4963 );
4964 let result = ctx.raw_call(ix).send();
4965 assert!(result.is_err(), "should error with no signers");
4966 }
4967
4968 #[test]
4969 fn raw_call_duplicate_fee_payer_signer_no_double_sign() {
4970 let mut ctx = TestContext::new();
4972 let payer = fund_keypair(&mut ctx);
4973 let recipient = Pubkey::new_unique();
4974
4975 let ix = anchor_lang::solana_program::system_instruction::transfer(
4976 &payer.pubkey(),
4977 &recipient,
4978 1000,
4979 );
4980 let result = ctx.raw_call(ix).fee_payer(&payer).signers(&[&payer]).send();
4981 assert!(
4982 result.is_ok(),
4983 "duplicate fee_payer/signer should not cause double-sign error: {:?}",
4984 result.err()
4985 );
4986 assert!(result.unwrap().is_success());
4987 }
4988
4989 #[test]
4990 fn send_batch_with_signers() {
4991 let mut ctx = TestContext::new();
4993 let payer = fund_keypair(&mut ctx);
4994 let recipient = Pubkey::new_unique();
4995
4996 let ix = anchor_lang::solana_program::system_instruction::transfer(
4997 &payer.pubkey(),
4998 &recipient,
4999 2000,
5000 );
5001 ctx.pending_instructions.push(ix);
5002 ctx.pending_signers.push(payer.insecure_clone());
5003 let result = ctx.send_batch();
5004 assert!(
5005 result.is_ok(),
5006 "send_batch should succeed: {:?}",
5007 result.err()
5008 );
5009 let outcome = result.unwrap();
5010 assert!(outcome.is_some());
5011 assert!(outcome.unwrap().is_success());
5012 assert_eq!(ctx.svm.get_balance(&recipient).unwrap_or(0), 2000);
5013 }
5014
5015 #[test]
5020 fn sigverify_false_uses_dummy_signatures() {
5021 let mut ctx = TestContext::new();
5023 assert!(!ctx.sigverify, "default sigverify should be false");
5024 let payer = fund_keypair(&mut ctx);
5025 let recipient = Pubkey::new_unique();
5026
5027 let ix = anchor_lang::solana_program::system_instruction::transfer(
5028 &payer.pubkey(),
5029 &recipient,
5030 1000,
5031 );
5032 let result = ctx.raw_call(ix).signers(&[&payer]).send();
5033 assert!(result.is_ok());
5034 assert!(result.unwrap().is_success());
5035 }
5036
5037 #[test]
5038 fn sigverify_true_uses_real_signatures() {
5039 let mut ctx = TestContext::new();
5040 ctx.sigverify = true;
5041 ctx.svm = ctx.svm.with_sigverify(true);
5043 let payer = fund_keypair(&mut ctx);
5044 let recipient = Pubkey::new_unique();
5045
5046 let ix = anchor_lang::solana_program::system_instruction::transfer(
5047 &payer.pubkey(),
5048 &recipient,
5049 1000,
5050 );
5051 let result = ctx.raw_call(ix).signers(&[&payer]).send();
5052 assert!(
5053 result.is_ok(),
5054 "real signing should work: {:?}",
5055 result.err()
5056 );
5057 assert!(result.unwrap().is_success());
5058 }
5059
5060 #[test]
5061 fn multiple_transactions_with_dummy_signing() {
5062 let mut ctx = TestContext::new();
5064 let payer = fund_keypair(&mut ctx);
5065 let recipient = Pubkey::new_unique();
5066
5067 for i in 0..5 {
5068 let ix = anchor_lang::solana_program::system_instruction::transfer(
5069 &payer.pubkey(),
5070 &recipient,
5071 1000,
5072 );
5073 let result = ctx.raw_call(ix).signers(&[&payer]).send();
5074 assert!(
5075 result.is_ok(),
5076 "tx {} should succeed: {:?}",
5077 i,
5078 result.err()
5079 );
5080 assert!(
5081 result.unwrap().is_success(),
5082 "tx {} should be successful",
5083 i
5084 );
5085 }
5086
5087 let balance = ctx.svm.get_balance(&recipient).unwrap_or(0);
5089 assert_eq!(
5090 balance, 5000,
5091 "recipient should have 5000 lamports after 5 transfers"
5092 );
5093 }
5094
5095 #[test]
5100 fn sysvar_snapshot_excludes_large_sysvars() {
5101 use anchor_lang::prelude::sysvar::SysvarId;
5103 use anchor_lang::prelude::Clock;
5104
5105 let ctx = TestContext::new();
5106 let snapshot = snapshot::SvmSnapshot::take_all(&ctx.svm);
5107
5108 let sysvar_pubkeys: Vec<_> = snapshot.sysvars.iter().map(|(pk, _)| *pk).collect();
5109
5110 assert!(
5112 sysvar_pubkeys.contains(&Clock::id()),
5113 "Clock should be in snapshot"
5114 );
5115
5116 let slot_history_id =
5118 solana_pubkey::Pubkey::from_str_const("SysvarS1otHistory11111111111111111111111111");
5119 let slot_hashes_id =
5120 solana_pubkey::Pubkey::from_str_const("SysvarS1otHashes111111111111111111111111111");
5121 assert!(
5122 !sysvar_pubkeys.contains(&slot_history_id),
5123 "SlotHistory (131KB) should be excluded from snapshots"
5124 );
5125 assert!(
5126 !sysvar_pubkeys.contains(&slot_hashes_id),
5127 "SlotHashes (20KB) should be excluded from snapshots"
5128 );
5129 }
5130
5131 #[test]
5136 fn advance_slots_records_target_slot() {
5137 let mut ctx = TestContext::new();
5138 ctx.advance_slots(500);
5139 assert_eq!(
5140 ctx.dirty_tracker.clock_target_slot,
5141 Some(500 + ctx.slot() - 500)
5142 );
5143 assert!(ctx.dirty_tracker.is_clock_dirty());
5144 }
5145
5146 #[test]
5147 fn warp_to_slot_records_target_slot() {
5148 let mut ctx = TestContext::new();
5149 ctx.warp_to_slot(12345);
5150 assert_eq!(ctx.dirty_tracker.clock_target_slot, Some(12345));
5151 assert!(ctx.dirty_tracker.is_clock_dirty());
5152 }
5153
5154 #[test]
5155 fn dirty_tracker_clear_resets_clock_target() {
5156 let mut ctx = TestContext::new();
5157 ctx.advance_slots(100);
5158 assert!(ctx.dirty_tracker.clock_target_slot.is_some());
5159 ctx.dirty_tracker.clear();
5160 assert!(ctx.dirty_tracker.clock_target_slot.is_none());
5161 assert!(!ctx.dirty_tracker.is_clock_dirty());
5162 }
5163
5164 #[test]
5167 fn corpus_loading_flag_defaults_false() {
5168 assert!(!is_corpus_loading());
5169 }
5170
5171 #[test]
5172 fn corpus_loading_flag_roundtrip() {
5173 set_corpus_loading(true);
5174 assert!(is_corpus_loading());
5175 set_corpus_loading(false);
5176 assert!(!is_corpus_loading());
5177 }
5178}