use super::super::*;
use super::helpers::*;
use crate::{FastHashMap, FastHashSet};
use anchor_lang::prelude::Clock;
use litesvm::LiteSVM;
use solana_account::Account;
use solana_pubkey::Pubkey;
use std::collections::HashSet;
use std::sync::Arc;
#[test]
fn test_adversarial_cpi_account_collides_with_next_delta() {
let mut svm = LiteSVM::new();
let pk_base = Pubkey::new_unique();
let pk_collide = Pubkey::new_unique();
svm.set_account(pk_base, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk_base].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut a_accts = FastHashMap::default();
a_accts.insert(pk_base, Arc::new(make_account(200, &[0xAA])));
let delta_a = SvmSnapshot {
accounts: a_accts,
sysvars: initial.sysvars.clone(),
};
let mut b_accts = FastHashMap::default();
b_accts.insert(pk_collide, Arc::new(make_account(999, &[0xBB; 128])));
let delta_b = SvmSnapshot {
accounts: b_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_delta: Option<SvmSnapshot> = None;
simulate_fuzzer_iteration(
&initial,
&mut svm,
&mut divergent,
&mut prev_delta,
&mut prev_exec_dirty,
&delta_a,
&[(pk_collide, Some(make_account(777, &[0xFF; 64])))], true,
);
assert_eq!(svm.get_account(&pk_collide).unwrap().lamports, 777);
simulate_fuzzer_iteration(
&initial,
&mut svm,
&mut divergent,
&mut prev_delta,
&mut prev_exec_dirty,
&delta_b,
&[], true,
);
assert_eq!(
svm.get_account(&pk_collide).unwrap().lamports,
999,
"BUG: CPI remnant leaked — delta value should override"
);
assert_eq!(
svm.get_account(&pk_collide).unwrap().data,
vec![0xBB; 128],
"BUG: CPI data leaked — delta data should override"
);
}
#[test]
fn test_adversarial_phantom_account_not_in_any_tracking() {
let mut svm = LiteSVM::new();
let pk_tracked = Pubkey::new_unique();
let pk_phantom = Pubkey::new_unique();
svm.set_account(pk_tracked, make_account(100, &[1]))
.unwrap();
svm.set_account(pk_phantom, make_account(42, &[0x42]))
.unwrap();
let tracked: HashSet<Pubkey> = [pk_tracked].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut s1_accts = FastHashMap::default();
s1_accts.insert(pk_tracked, Arc::new(make_account(200, &[0xAA])));
let delta_1 = SvmSnapshot {
accounts: s1_accts,
sysvars: initial.sysvars.clone(),
};
let empty_delta = SvmSnapshot::empty(initial.clock().clone());
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta_1);
divergent.extend(delta_1.accounts().keys().copied());
assert_eq!(
svm.get_account(&pk_phantom).unwrap().lamports,
42,
"phantom should survive restore — not in divergent_keys"
);
initial.restore_selective_from(
&mut svm,
&divergent,
&delta_1,
&empty_delta,
&prev_exec_dirty,
);
assert_eq!(
svm.get_account(&pk_phantom).unwrap().lamports,
42,
"phantom survives across restores because it's never in divergent_keys"
);
divergent.insert(pk_phantom);
initial.restore_selective(&mut svm, &divergent, &empty_delta);
assert!(
svm.get_account(&pk_phantom).is_none(),
"phantom should be zeroed once it enters divergent_keys — not in initial!"
);
}
#[test]
fn test_adversarial_account_in_initial_but_never_in_divergent() {
let mut svm = LiteSVM::new();
let pk_normal = Pubkey::new_unique();
let pk_hidden = Pubkey::new_unique();
svm.set_account(pk_normal, make_account(100, &[1])).unwrap();
svm.set_account(pk_hidden, make_account(50, &[0x50]))
.unwrap();
let tracked: HashSet<Pubkey> = [pk_normal, pk_hidden].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut s1_accts = FastHashMap::default();
s1_accts.insert(pk_normal, Arc::new(make_account(200, &[0xAA])));
let delta_1 = SvmSnapshot {
accounts: s1_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta_1);
divergent.extend(delta_1.accounts().keys().copied());
svm.set_account(pk_hidden, make_account(0, &[])).unwrap();
initial.restore_selective_from(&mut svm, &divergent, &delta_1, &delta_1, &prev_exec_dirty);
let hidden_val = svm.get_account(&pk_hidden).map_or(0, |a| a.lamports);
assert_eq!(
hidden_val, 0,
"pk_hidden stays corrupted — restore can't fix what it doesn't know about"
);
divergent.insert(pk_hidden);
initial.restore_selective(&mut svm, &divergent, &delta_1);
assert_eq!(
svm.get_account(&pk_hidden).unwrap().lamports,
50,
"pk_hidden should be restored to initial when properly tracked"
);
}
#[test]
fn test_adversarial_arc_optimization_unsound_without_exec_dirty() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(10, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let shared_arc = Arc::new(make_account(100, &[0xAA]));
let mut a_accts = FastHashMap::default();
a_accts.insert(pk, shared_arc.clone());
let delta_a = SvmSnapshot {
accounts: a_accts,
sysvars: initial.sysvars.clone(),
};
let mut b_accts = FastHashMap::default();
b_accts.insert(pk, shared_arc.clone());
let delta_b = SvmSnapshot {
accounts: b_accts,
sysvars: initial.sysvars.clone(),
};
assert!(Arc::ptr_eq(
&delta_a.accounts()[&pk],
&delta_b.accounts()[&pk]
));
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta_a);
divergent.extend(delta_a.accounts().keys().copied());
assert_eq!(svm.get_account(&pk).unwrap().lamports, 100);
svm.set_account(pk, make_account(66666, &[0xDE, 0xAD, 0xBE, 0xEF]))
.unwrap();
let empty_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let count_without =
initial.restore_selective_from(&mut svm, &divergent, &delta_a, &delta_b, &empty_exec_dirty);
assert_eq!(
count_without, 0,
"without exec_dirty, optimization skips the write"
);
assert_eq!(
svm.get_account(&pk).unwrap().lamports,
66666,
"BUG DEMO: without exec_dirty, corruption persists!"
);
let mut proper_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
proper_exec_dirty.insert(pk);
let count_with = initial.restore_selective_from(
&mut svm,
&divergent,
&delta_a,
&delta_b,
&proper_exec_dirty,
);
assert_eq!(count_with, 1, "with exec_dirty, pk is written");
assert_eq!(
svm.get_account(&pk).unwrap().lamports,
100,
"with exec_dirty, corruption is fixed"
);
}
#[test]
fn test_adversarial_dual_svm_divergent_tracking() {
let mut fast_svm = LiteSVM::new();
let mut traced_svm = LiteSVM::new();
let pk_a = Pubkey::new_unique();
let pk_b = Pubkey::new_unique();
for svm in [&mut fast_svm, &mut traced_svm] {
svm.set_account(pk_a, make_account(10, &[1])).unwrap();
svm.set_account(pk_b, make_account(20, &[2])).unwrap();
}
let tracked: HashSet<Pubkey> = [pk_a, pk_b].into_iter().collect();
let initial = SvmSnapshot::take(&fast_svm, &tracked);
let states: Vec<SvmSnapshot> = (1..=4)
.map(|i| {
let mut accts = FastHashMap::default();
accts.insert(pk_a, Arc::new(make_account(i * 100, &[i as u8 * 10])));
accts.insert(pk_b, Arc::new(make_account(i * 200, &[i as u8 * 20])));
SvmSnapshot {
accounts: accts,
sysvars: initial.sysvars.clone(),
}
})
.collect();
let mut divergent_keys: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_delta_arc: Option<SvmSnapshot> = None;
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut traced_divergent: FastHashSet<Pubkey> = FastHashSet::default();
let trace_interval = 3;
let sequence = [0, 1, 2, 3, 0, 2, 1, 3, 2, 0, 3, 1];
for (iter, &state_idx) in sequence.iter().enumerate() {
let is_traced = trace_interval > 0 && iter % trace_interval == 0;
let delta = &states[state_idx];
if is_traced {
initial.restore_selective(&mut traced_svm, &traced_divergent, delta);
traced_divergent.clear();
traced_divergent.extend(delta.accounts().keys().copied());
let dirty_pk = if iter % 2 == 0 { pk_a } else { pk_b };
traced_svm
.set_account(dirty_pk, make_account(99999, &[0xEE]))
.unwrap();
traced_divergent.insert(dirty_pk);
} else {
if let Some(ref prev) = prev_delta_arc {
initial.restore_selective_from(
&mut fast_svm,
&divergent_keys,
prev,
delta,
&prev_exec_dirty,
);
} else {
initial.restore_selective(&mut fast_svm, &divergent_keys, delta);
}
divergent_keys.clear();
divergent_keys.extend(delta.accounts().keys().copied());
let ea = states[state_idx].accounts()[&pk_a].lamports;
let eb = states[state_idx].accounts()[&pk_b].lamports;
assert_eq!(
fast_svm.get_account(&pk_a).unwrap().lamports,
ea,
"iter {} (fast): pk_a expected {}",
iter,
ea
);
assert_eq!(
fast_svm.get_account(&pk_b).unwrap().lamports,
eb,
"iter {} (fast): pk_b expected {}",
iter,
eb
);
let dirty_pk = if iter % 2 == 0 { pk_a } else { pk_b };
fast_svm
.set_account(dirty_pk, make_account(88888, &[0xDD]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(dirty_pk);
divergent_keys.extend(prev_exec_dirty.iter().copied());
prev_delta_arc = Some(delta.clone());
}
}
}
#[test]
fn test_adversarial_clock_divergence_across_states() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let clock_100 = Clock {
slot: 100,
unix_timestamp: 1000,
..Default::default()
};
let clock_200 = Clock {
slot: 200,
unix_timestamp: 2000,
..Default::default()
};
let clock_300 = Clock {
slot: 300,
unix_timestamp: 3000,
..Default::default()
};
let mut s1_accts = FastHashMap::default();
s1_accts.insert(pk, Arc::new(make_account(200, &[0xAA])));
let delta_1 = SvmSnapshot {
accounts: s1_accts,
sysvars: clock_to_sysvars(&clock_100),
};
let mut s2_accts = FastHashMap::default();
s2_accts.insert(pk, Arc::new(make_account(300, &[0xBB])));
let delta_2 = SvmSnapshot {
accounts: s2_accts,
sysvars: clock_to_sysvars(&clock_200),
};
let delta_3 = SvmSnapshot {
accounts: FastHashMap::default(), sysvars: clock_to_sysvars(&clock_300),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta_1);
divergent.extend(delta_1.accounts().keys().copied());
let clock: Clock = svm.get_sysvar();
assert_eq!(clock.slot, 100);
assert_eq!(clock.unix_timestamp, 1000);
initial.restore_selective_from(&mut svm, &divergent, &delta_1, &delta_2, &prev_exec_dirty);
divergent.clear();
divergent.extend(delta_2.accounts().keys().copied());
let clock: Clock = svm.get_sysvar();
assert_eq!(clock.slot, 200, "clock should advance to state 2");
assert_eq!(clock.unix_timestamp, 2000);
initial.restore_selective_from(&mut svm, &divergent, &delta_2, &delta_3, &prev_exec_dirty);
let clock: Clock = svm.get_sysvar();
assert_eq!(
clock.slot, 300,
"clock should advance even with empty delta"
);
divergent.clear();
initial.restore_selective(&mut svm, &divergent, &delta_1);
let clock: Clock = svm.get_sysvar();
assert_eq!(
clock.slot, 100,
"clock must go backwards when restoring older state"
);
}
#[test]
fn test_adversarial_fee_payer_drift_across_chain() {
let mut svm = LiteSVM::new();
let fee_payer = Pubkey::new_unique();
let pk_data = Pubkey::new_unique();
svm.set_account(fee_payer, make_account(10_000_000, &[0; 0]))
.unwrap();
svm.set_account(pk_data, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [fee_payer, pk_data].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let root = SvmSnapshot::empty(initial.clock().clone());
let mut l1_accts = FastHashMap::default();
l1_accts.insert(fee_payer, Arc::new(make_account(9_995_000, &[]))); l1_accts.insert(pk_data, Arc::new(make_account(200, &[2])));
let l1 = SvmSnapshot {
accounts: l1_accts,
sysvars: initial.sysvars.clone(),
};
let mut l2_accts = FastHashMap::default();
l2_accts.insert(fee_payer, Arc::new(make_account(9_990_000, &[]))); l2_accts.insert(pk_data, l1.accounts()[&pk_data].clone()); let l2 = SvmSnapshot {
accounts: l2_accts,
sysvars: initial.sysvars.clone(),
};
let mut l3_accts = FastHashMap::default();
l3_accts.insert(fee_payer, Arc::new(make_account(9_985_000, &[]))); l3_accts.insert(pk_data, Arc::new(make_account(300, &[3])));
let l3 = SvmSnapshot {
accounts: l3_accts,
sysvars: initial.sysvars.clone(),
};
assert!(!Arc::ptr_eq(
&l1.accounts()[&fee_payer],
&l2.accounts()[&fee_payer]
));
assert!(!Arc::ptr_eq(
&l2.accounts()[&fee_payer],
&l3.accounts()[&fee_payer]
));
assert!(Arc::ptr_eq(
&l1.accounts()[&pk_data],
&l2.accounts()[&pk_data]
));
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &l3);
divergent.extend(l3.accounts().keys().copied());
assert_eq!(svm.get_account(&fee_payer).unwrap().lamports, 9_985_000);
initial.restore_selective_from(&mut svm, &divergent, &l3, &l1, &prev_exec_dirty);
assert_eq!(
svm.get_account(&fee_payer).unwrap().lamports,
9_995_000,
"fee_payer must be restored to L1 value, not L3"
);
divergent.clear();
divergent.extend(l1.accounts().keys().copied());
initial.restore_selective_from(&mut svm, &divergent, &l1, &root, &prev_exec_dirty);
assert_eq!(
svm.get_account(&fee_payer).unwrap().lamports,
10_000_000,
"fee_payer must return to initial when jumping to root"
);
}
#[test]
fn test_adversarial_divergent_keys_must_include_exec_dirty_for_cpi_cleanup() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut s1_accts = FastHashMap::default();
s1_accts.insert(pk, Arc::new(make_account(200, &[0xAA])));
let delta_1 = SvmSnapshot {
accounts: s1_accts,
sysvars: initial.sysvars.clone(),
};
let empty = SvmSnapshot::empty(initial.clock().clone());
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta_1);
divergent.clear();
divergent.extend(delta_1.accounts().keys().copied());
let pk_cpi = Pubkey::new_unique();
svm.set_account(pk_cpi, make_account(777, &[0xCC; 32]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk);
prev_exec_dirty.insert(pk_cpi);
divergent.extend(prev_exec_dirty.iter().copied());
let prev_delta = Some(delta_1.clone());
if let Some(ref prev) = prev_delta {
initial.restore_selective_from(&mut svm, &divergent, prev, &empty, &prev_exec_dirty);
}
assert!(
svm.get_account(&pk_cpi).is_none(),
"CRITICAL BUG: CPI account leaked across iterations!"
);
svm.set_account(pk, make_account(100, &[1])).unwrap();
let mut bad_divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &bad_divergent, &delta_1);
bad_divergent.clear();
bad_divergent.extend(delta_1.accounts().keys().copied());
svm.set_account(pk_cpi, make_account(777, &[0xCC; 32]))
.unwrap();
initial.restore_selective_from(&mut svm, &bad_divergent, &delta_1, &empty, &prev_exec_dirty);
assert_eq!(
svm.get_account(&pk_cpi).unwrap().lamports,
777,
"Without divergent_keys.extend(exec_dirty), CPI account leaks!"
);
}
#[test]
fn test_adversarial_10_iteration_full_state_verification() {
let mut svm = LiteSVM::new();
let pks: Vec<Pubkey> = (0..10).map(|_| Pubkey::new_unique()).collect();
for (i, pk) in pks.iter().enumerate() {
svm.set_account(*pk, make_account((i as u64 + 1) * 10, &[i as u8]))
.unwrap();
}
let tracked: HashSet<Pubkey> = pks.iter().copied().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let states: Vec<SvmSnapshot> = vec![
SvmSnapshot::empty(initial.clock().clone()), {
let mut m = FastHashMap::default();
m.insert(pks[0], Arc::new(make_account(100, &[0x10])));
m.insert(pks[1], Arc::new(make_account(200, &[0x20])));
m.insert(pks[2], Arc::new(make_account(300, &[0x30])));
SvmSnapshot {
accounts: m,
sysvars: initial.sysvars.clone(),
}
},
{
let mut m = FastHashMap::default();
m.insert(
pks[0],
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
m.insert(pks[4], Arc::new(make_account(400, &[0x40])));
m.insert(pks[5], Arc::new(make_account(500, &[0x50])));
m.insert(pks[6], Arc::new(make_account(600, &[0x60])));
SvmSnapshot {
accounts: m,
sysvars: initial.sysvars.clone(),
}
},
{
let mut m = FastHashMap::default();
let pk_cpi = Pubkey::new_unique();
m.insert(pk_cpi, Arc::new(make_account(999, &[0xCC])));
m.insert(pks[9], Arc::new(make_account(900, &[0x90])));
SvmSnapshot {
accounts: m,
sysvars: initial.sysvars.clone(),
}
},
{
let mut m = FastHashMap::default();
m.insert(pks[7], Arc::new(make_account(700, &[0x70])));
m.insert(pks[8], Arc::new(make_account(800, &[0x80])));
m.insert(pks[9], Arc::new(make_account(901, &[0x91])));
SvmSnapshot {
accounts: m,
sysvars: initial.sysvars.clone(),
}
},
];
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_delta: Option<SvmSnapshot> = None;
let mut all_cpi_ever: Vec<Pubkey> = Vec::new();
let sequence = [1, 2, 0, 3, 4, 2, 1, 0, 4, 3];
let successes = [
true, true, false, true, true, false, true, true, true, false,
];
for (iter, (&state_idx, &success)) in sequence.iter().zip(successes.iter()).enumerate() {
let delta = &states[state_idx];
simulate_restore(
&initial,
&mut svm,
&divergent,
delta,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta.accounts().keys().copied());
verify_full_state(
&svm,
&initial,
delta,
&all_cpi_ever,
&format!("iter {} state {}", iter, state_idx),
);
let exec_pk = pks[iter % 10];
svm.set_account(exec_pk, make_account(77777, &[0xFF]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(exec_pk);
divergent.extend(prev_exec_dirty.iter().copied());
if iter % 3 == 1 {
let cpi_pk = Pubkey::new_unique();
svm.set_account(cpi_pk, make_account(42, &[0x42])).unwrap();
prev_exec_dirty.insert(cpi_pk);
divergent.insert(cpi_pk);
all_cpi_ever.push(cpi_pk);
}
if success {
prev_delta = Some(delta.clone());
} else {
prev_delta = None;
}
}
}
#[test]
fn test_adversarial_take_delta_chain_fidelity() {
let mut svm = LiteSVM::new();
let pk_a = Pubkey::new_unique();
let pk_b = Pubkey::new_unique();
let pk_c = Pubkey::new_unique();
svm.set_account(pk_a, make_account(1, &[0x01])).unwrap();
svm.set_account(pk_b, make_account(2, &[0x02])).unwrap();
svm.set_account(pk_c, make_account(3, &[0x03])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b, pk_c].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let root = SvmSnapshot::empty(initial.clock().clone());
svm.set_account(pk_a, make_account(10, &[0x10])).unwrap();
let mut d1 = DirtyTracker::new();
d1.mark_account_dirty(&pk_a);
let l1 = SvmSnapshot::take_delta(&svm, &root, &d1);
assert_eq!(l1.accounts()[&pk_a].lamports, 10);
assert!(!l1.accounts().contains_key(&pk_b));
assert!(!l1.accounts().contains_key(&pk_c));
svm.set_account(pk_b, make_account(20, &[0x20])).unwrap();
let mut d2 = DirtyTracker::new();
d2.mark_account_dirty(&pk_b);
let l2 = SvmSnapshot::take_delta(&svm, &l1, &d2);
assert_eq!(l2.accounts()[&pk_a].lamports, 10);
assert_eq!(l2.accounts()[&pk_b].lamports, 20);
assert!(
Arc::ptr_eq(&l1.accounts()[&pk_a], &l2.accounts()[&pk_a]),
"A should be inherited Arc"
);
svm.set_account(pk_a, make_account(30, &[0x30])).unwrap();
let mut d3 = DirtyTracker::new();
d3.mark_account_dirty(&pk_a);
let l3 = SvmSnapshot::take_delta(&svm, &l2, &d3);
assert_eq!(l3.accounts()[&pk_a].lamports, 30);
assert_eq!(l3.accounts()[&pk_b].lamports, 20);
assert!(
!Arc::ptr_eq(&l2.accounts()[&pk_a], &l3.accounts()[&pk_a]),
"A should be new Arc (overridden)"
);
assert!(
Arc::ptr_eq(&l2.accounts()[&pk_b], &l3.accounts()[&pk_b]),
"B should be inherited Arc"
);
svm.set_account(
pk_a,
Account {
lamports: 0,
..Default::default()
},
)
.unwrap();
svm.set_account(pk_c, make_account(40, &[0x40])).unwrap();
let mut d4 = DirtyTracker::new();
d4.mark_account_dirty(&pk_a);
d4.mark_account_dirty(&pk_c);
let l4 = SvmSnapshot::take_delta(&svm, &l3, &d4);
assert_eq!(l4.accounts()[&pk_a].lamports, 0); assert_eq!(l4.accounts()[&pk_b].lamports, 20);
assert_eq!(l4.accounts()[&pk_c].lamports, 40);
let all_keys: FastHashSet<Pubkey> = [pk_a, pk_b, pk_c].into_iter().collect();
initial.restore_selective(&mut svm, &all_keys, &l1);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 10);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 2);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 3);
initial.restore_selective(&mut svm, &all_keys, &l4);
assert!(
svm.get_account(&pk_a).is_none(),
"A should be tombstoned in L4"
);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 20);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 40);
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective_from(&mut svm, &all_keys, &l4, &l1, &prev_exec_dirty);
assert_eq!(
svm.get_account(&pk_a).unwrap().lamports,
10,
"A must resurrect"
);
assert_eq!(
svm.get_account(&pk_b).unwrap().lamports,
2,
"B must return to initial"
);
assert_eq!(
svm.get_account(&pk_c).unwrap().lamports,
3,
"C must return to initial"
);
}
#[test]
fn test_adversarial_multiple_cpi_accounts_tracked_and_cleaned() {
let mut svm = LiteSVM::new();
let pk_base = Pubkey::new_unique();
svm.set_account(pk_base, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk_base].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut s_accts = FastHashMap::default();
s_accts.insert(pk_base, Arc::new(make_account(200, &[0xAA])));
let delta = SvmSnapshot {
accounts: s_accts,
sysvars: initial.sysvars.clone(),
};
let empty = SvmSnapshot::empty(initial.clock().clone());
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_delta: Option<SvmSnapshot> = None;
let mut all_cpi: Vec<Pubkey> = Vec::new();
for iter in 0..5 {
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta.accounts().keys().copied());
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk_base);
for _ in 0..3 {
let cpi = Pubkey::new_unique();
svm.set_account(cpi, make_account(42, &[iter as u8]))
.unwrap();
prev_exec_dirty.insert(cpi);
all_cpi.push(cpi);
}
divergent.extend(prev_exec_dirty.iter().copied());
prev_delta = Some(delta.clone());
}
simulate_restore(
&initial,
&mut svm,
&divergent,
&empty,
prev_delta.as_ref(),
&prev_exec_dirty,
);
assert_eq!(
svm.get_account(&pk_base).unwrap().lamports,
100,
"base must return to initial"
);
for (i, cpi) in all_cpi.iter().enumerate() {
if i >= all_cpi.len() - 3 {
assert!(
svm.get_account(cpi).is_none(),
"CPI {} from last iter should be cleaned up",
i
);
}
}
}
#[test]
fn test_adversarial_stale_cpi_from_old_iteration_leaks() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut s_accts = FastHashMap::default();
s_accts.insert(pk, Arc::new(make_account(200, &[0xAA])));
let delta = SvmSnapshot {
accounts: s_accts,
sysvars: initial.sysvars.clone(),
};
let empty = SvmSnapshot::empty(initial.clock().clone());
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_delta: Option<SvmSnapshot> = None;
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta.accounts().keys().copied());
let pk_cpi_old = Pubkey::new_unique();
svm.set_account(pk_cpi_old, make_account(777, &[0xFF]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk);
prev_exec_dirty.insert(pk_cpi_old);
divergent.extend(prev_exec_dirty.iter().copied());
prev_delta = Some(delta.clone());
assert!(divergent.contains(&pk_cpi_old));
simulate_restore(
&initial,
&mut svm,
&divergent,
&empty,
prev_delta.as_ref(),
&prev_exec_dirty,
);
assert!(
svm.get_account(&pk_cpi_old).is_none(),
"pk_cpi_old must be cleaned in iter 2 restore"
);
divergent.clear();
let pk_cpi_new = Pubkey::new_unique();
svm.set_account(pk_cpi_new, make_account(888, &[0xEE]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk_cpi_new);
divergent.extend(prev_exec_dirty.iter().copied());
prev_delta = Some(empty.clone());
assert!(!divergent.contains(&pk_cpi_old));
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta,
prev_delta.as_ref(),
&prev_exec_dirty,
);
assert!(
svm.get_account(&pk_cpi_old).is_none(),
"pk_cpi_old should still be gone (was cleaned in iter 2)"
);
assert!(
svm.get_account(&pk_cpi_new).is_none(),
"pk_cpi_new should be cleaned in iter 3"
);
assert_eq!(svm.get_account(&pk).unwrap().lamports, 200);
}
#[test]
fn test_adversarial_exec_creates_account_at_initial_address() {
let mut svm = LiteSVM::new();
let pk_reinit = Pubkey::new_unique();
let owner_initial = Pubkey::new_unique();
svm.set_account(
pk_reinit,
Account {
lamports: 100,
data: vec![1, 2, 3, 4],
owner: owner_initial,
executable: false,
rent_epoch: 0,
},
)
.unwrap();
let tracked: HashSet<Pubkey> = [pk_reinit].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let empty = SvmSnapshot::empty(initial.clock().clone());
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &empty);
let owner_new = Pubkey::new_unique();
svm.set_account(
pk_reinit,
Account {
lamports: 500,
data: vec![0xDE; 64],
owner: owner_new,
executable: false,
rent_epoch: 0,
},
)
.unwrap();
prev_exec_dirty.insert(pk_reinit);
divergent.extend(prev_exec_dirty.iter().copied());
let prev_delta = Some(empty.clone());
simulate_restore(
&initial,
&mut svm,
&divergent,
&empty,
prev_delta.as_ref(),
&prev_exec_dirty,
);
let restored = svm.get_account(&pk_reinit).unwrap();
assert_eq!(restored.lamports, 100, "lamports must return to initial");
assert_eq!(
restored.data,
vec![1, 2, 3, 4],
"data must return to initial"
);
assert_eq!(
restored.owner, owner_initial,
"owner must return to initial"
);
}
#[test]
fn test_adversarial_divergent_keys_growth_bounded() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut s_accts = FastHashMap::default();
s_accts.insert(pk, Arc::new(make_account(200, &[0xAA])));
let delta = SvmSnapshot {
accounts: s_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_delta: Option<SvmSnapshot> = None;
for iter in 0..20 {
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta.accounts().keys().copied());
let cpi = Pubkey::new_unique();
svm.set_account(cpi, make_account(42, &[iter as u8]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk);
prev_exec_dirty.insert(cpi);
divergent.extend(prev_exec_dirty.iter().copied());
prev_delta = Some(delta.clone());
assert!(
divergent.len() <= 3,
"iter {}: divergent has {} keys, expected ≤3 (should not accumulate)",
iter,
divergent.len()
);
}
}
#[test]
fn test_adversarial_prev_delta_none_after_failure_forces_full_restore() {
let mut svm = LiteSVM::new();
let pk_a = Pubkey::new_unique();
let pk_b = Pubkey::new_unique();
svm.set_account(pk_a, make_account(10, &[1])).unwrap();
svm.set_account(pk_b, make_account(20, &[2])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let shared_arc = Arc::new(make_account(100, &[0xAA]));
let mut s1_accts = FastHashMap::default();
s1_accts.insert(pk_a, shared_arc.clone());
let delta_1 = SvmSnapshot {
accounts: s1_accts,
sysvars: initial.sysvars.clone(),
};
let mut s2_accts = FastHashMap::default();
s2_accts.insert(pk_a, shared_arc.clone()); s2_accts.insert(pk_b, Arc::new(make_account(200, &[0xBB])));
let delta_2 = SvmSnapshot {
accounts: s2_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_delta: Option<SvmSnapshot> = None;
simulate_fuzzer_iteration(
&initial,
&mut svm,
&mut divergent,
&mut prev_delta,
&mut prev_exec_dirty,
&delta_1,
&[(pk_a, Some(make_account(55555, &[0xFF])))],
true, );
assert!(prev_delta.is_some());
simulate_fuzzer_iteration(
&initial,
&mut svm,
&mut divergent,
&mut prev_delta,
&mut prev_exec_dirty,
&delta_1,
&[(pk_a, Some(make_account(77777, &[0xEE])))],
false, );
assert!(prev_delta.is_none(), "failure should clear prev_delta");
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta_2,
prev_delta.as_ref(),
&prev_exec_dirty,
);
assert_eq!(
svm.get_account(&pk_a).unwrap().lamports,
100,
"pk_a must be correctly restored even after failure"
);
assert_eq!(
svm.get_account(&pk_b).unwrap().lamports,
200,
"pk_b must be from state 2"
);
}
#[test]
fn test_adversarial_take_delta_with_empty_dirty_tracker() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let _initial = SvmSnapshot::take(&svm, &tracked);
let mut parent_accts = FastHashMap::default();
parent_accts.insert(pk, Arc::new(make_account(200, &[0xBB])));
let parent = SvmSnapshot {
accounts: parent_accts,
sysvars: _initial.sysvars.clone(),
};
let empty_dirty = DirtyTracker::new();
let child = SvmSnapshot::take_delta(&svm, &parent, &empty_dirty);
assert_eq!(child.account_count(), parent.account_count());
assert!(
Arc::ptr_eq(&parent.accounts()[&pk], &child.accounts()[&pk]),
"empty dirty → child should be exact clone of parent (ptr_eq)"
);
}
#[test]
fn test_adversarial_restore_selective_empty_divergent_nonempty_delta() {
let mut svm = LiteSVM::new();
let pk_a = Pubkey::new_unique();
let pk_b = Pubkey::new_unique();
svm.set_account(pk_a, make_account(10, &[1])).unwrap();
svm.set_account(pk_b, make_account(20, &[2])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
svm.set_account(pk_b, make_account(99999, &[0xFF])).unwrap();
let mut delta_accts = FastHashMap::default();
delta_accts.insert(pk_a, Arc::new(make_account(100, &[0xAA])));
let delta = SvmSnapshot {
accounts: delta_accts,
sysvars: initial.sysvars.clone(),
};
let divergent: FastHashSet<Pubkey> = FastHashSet::default(); let count = initial.restore_selective(&mut svm, &divergent, &delta);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(
svm.get_account(&pk_b).unwrap().lamports,
99999,
"pk_b stays corrupted because it's not in divergent_keys — \
this is correct behavior (caller must track divergence)"
);
assert_eq!(count, 1, "only delta account written");
}
#[test]
fn test_adversarial_restore_all_account_fields() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
let owner_1 = Pubkey::new_unique();
svm.set_account(
pk,
Account {
lamports: 100,
data: vec![1, 2, 3],
owner: owner_1,
executable: false,
rent_epoch: 42,
},
)
.unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let owner_2 = Pubkey::new_unique();
let mut delta_accts = FastHashMap::default();
delta_accts.insert(
pk,
Arc::new(Account {
lamports: 200,
data: vec![4, 5, 6, 7, 8],
owner: owner_2,
executable: false,
rent_epoch: 99,
}),
);
let delta = SvmSnapshot {
accounts: delta_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.insert(pk);
initial.restore_selective(&mut svm, &divergent, &delta);
let acct = svm.get_account(&pk).unwrap();
assert_eq!(acct.lamports, 200);
assert_eq!(acct.data, vec![4, 5, 6, 7, 8]);
assert_eq!(acct.owner, owner_2);
assert_eq!(acct.rent_epoch, 99);
let empty = SvmSnapshot::empty(initial.clock().clone());
initial.restore_selective(&mut svm, &divergent, &empty);
let acct = svm.get_account(&pk).unwrap();
assert_eq!(acct.lamports, 100);
assert_eq!(acct.data, vec![1, 2, 3]);
assert_eq!(acct.owner, owner_1);
assert_eq!(acct.rent_epoch, 42);
}
#[test]
fn test_adversarial_disjoint_delta_key_sets() {
let mut svm = LiteSVM::new();
let pk_a = Pubkey::new_unique();
let pk_b = Pubkey::new_unique();
let pk_c = Pubkey::new_unique();
let pk_d = Pubkey::new_unique();
for (i, pk) in [pk_a, pk_b, pk_c, pk_d].iter().enumerate() {
svm.set_account(*pk, make_account((i as u64 + 1) * 10, &[i as u8]))
.unwrap();
}
let tracked: HashSet<Pubkey> = [pk_a, pk_b, pk_c, pk_d].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut prev_accts = FastHashMap::default();
prev_accts.insert(pk_a, Arc::new(make_account(100, &[0xAA])));
prev_accts.insert(pk_b, Arc::new(make_account(200, &[0xBB])));
let prev_delta = SvmSnapshot {
accounts: prev_accts,
sysvars: initial.sysvars.clone(),
};
let mut next_accts = FastHashMap::default();
next_accts.insert(pk_c, Arc::new(make_account(300, &[0xCC])));
next_accts.insert(pk_d, Arc::new(make_account(400, &[0xDD])));
let next_delta = SvmSnapshot {
accounts: next_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &prev_delta);
divergent.extend(prev_delta.accounts().keys().copied());
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let count = initial.restore_selective_from(
&mut svm,
&divergent,
&prev_delta,
&next_delta,
&prev_exec_dirty,
);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 10);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 20);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300);
assert_eq!(svm.get_account(&pk_d).unwrap().lamports, 400);
assert_eq!(count, 4);
}
#[test]
fn test_adversarial_large_data_mutation_integrity() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
let data_initial: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
svm.set_account(pk, make_account(100, &data_initial))
.unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let data_modified: Vec<u8> = (0..10_000).map(|i| ((i * 7 + 13) % 256) as u8).collect();
let mut delta_accts = FastHashMap::default();
delta_accts.insert(pk, Arc::new(make_account(200, &data_modified)));
let delta = SvmSnapshot {
accounts: delta_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta);
divergent.extend(delta.accounts().keys().copied());
let acct = svm.get_account(&pk).unwrap();
assert_eq!(acct.data.len(), 10_000);
assert_eq!(acct.data, data_modified, "10KB data should match exactly");
let empty = SvmSnapshot::empty(initial.clock().clone());
initial.restore_selective(&mut svm, &divergent, &empty);
let acct = svm.get_account(&pk).unwrap();
assert_eq!(acct.data.len(), 10_000);
assert_eq!(
acct.data, data_initial,
"10KB initial data should be restored exactly"
);
}
#[test]
fn test_adversarial_empty_data_and_zero_lamports_distinction() {
let mut svm = LiteSVM::new();
let pk_has_data = Pubkey::new_unique();
let pk_no_data = Pubkey::new_unique();
svm.set_account(pk_has_data, make_account(100, &[1, 2, 3]))
.unwrap();
svm.set_account(pk_no_data, make_account(50, &[])).unwrap();
let tracked: HashSet<Pubkey> = [pk_has_data, pk_no_data].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut delta_accts = FastHashMap::default();
delta_accts.insert(
pk_has_data,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
let delta = SvmSnapshot {
accounts: delta_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta);
divergent.extend(delta.accounts().keys().copied());
assert!(svm.get_account(&pk_has_data).is_none());
let acct = svm.get_account(&pk_no_data).unwrap();
assert_eq!(acct.lamports, 50);
assert_eq!(acct.data.len(), 0);
let empty = SvmSnapshot::empty(initial.clock().clone());
initial.restore_selective(&mut svm, &divergent, &empty);
let acct = svm.get_account(&pk_has_data).unwrap();
assert_eq!(acct.lamports, 100);
assert_eq!(acct.data, vec![1, 2, 3]);
}