use super::super::*;
use super::helpers::*;
use crate::{FastHashMap, FastHashSet};
use anchor_lang::solana_program::instruction::{AccountMeta, Instruction};
use litesvm::LiteSVM;
use solana_account::Account;
use solana_pubkey::Pubkey;
use std::collections::HashSet;
use std::sync::Arc;
#[test]
fn test_edge_account_recreated_after_deletion() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(100, &[1, 2, 3])).unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut a_accts = FastHashMap::default();
a_accts.insert(
pk,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
let delta_a = SvmSnapshot {
accounts: a_accts,
sysvars: initial.sysvars.clone(),
};
let mut b_accts = FastHashMap::default();
b_accts.insert(pk, Arc::new(make_account(500, &[0xDE, 0xAD])));
let delta_b = SvmSnapshot {
accounts: b_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta_a,
None,
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta_a.accounts().keys().copied());
assert!(
svm.get_account(&pk).is_none(),
"pk should be deleted in state A"
);
let count =
initial.restore_selective_from(&mut svm, &divergent, &delta_a, &delta_b, &prev_exec_dirty);
assert!(count > 0, "should have written pk");
assert_eq!(svm.get_account(&pk).unwrap().lamports, 500);
assert_eq!(svm.get_account(&pk).unwrap().data, vec![0xDE, 0xAD]);
divergent.clear();
divergent.extend(delta_b.accounts().keys().copied());
let count =
initial.restore_selective_from(&mut svm, &divergent, &delta_b, &delta_a, &prev_exec_dirty);
assert!(count > 0);
assert!(
svm.get_account(&pk).is_none(),
"pk should be deleted again in state A"
);
}
#[test]
fn test_edge_account_data_length_change() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(10, &[0; 8])).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(100, &[0xAA; 32])));
let delta_1 = SvmSnapshot {
accounts: s1_accts,
sysvars: initial.sysvars.clone(),
};
let mut s2_accts = FastHashMap::default();
s2_accts.insert(pk, Arc::new(make_account(200, &[0xBB; 256])));
let delta_2 = SvmSnapshot {
accounts: s2_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());
assert_eq!(svm.get_account(&pk).unwrap().data.len(), 32);
initial.restore_selective_from(&mut svm, &divergent, &delta_1, &delta_2, &prev_exec_dirty);
divergent.clear();
divergent.extend(delta_2.accounts().keys().copied());
assert_eq!(svm.get_account(&pk).unwrap().data.len(), 256);
assert_eq!(svm.get_account(&pk).unwrap().lamports, 200);
assert!(svm
.get_account(&pk)
.unwrap()
.data
.iter()
.all(|&b| b == 0xBB));
initial.restore_selective_from(&mut svm, &divergent, &delta_2, &delta_1, &prev_exec_dirty);
assert_eq!(svm.get_account(&pk).unwrap().data.len(), 32);
assert_eq!(svm.get_account(&pk).unwrap().lamports, 100);
assert!(svm
.get_account(&pk)
.unwrap()
.data
.iter()
.all(|&b| b == 0xAA));
divergent.clear();
divergent.extend(delta_1.accounts().keys().copied());
let empty = SvmSnapshot::empty(initial.clock().clone());
initial.restore_selective_from(&mut svm, &divergent, &delta_1, &empty, &prev_exec_dirty);
assert_eq!(svm.get_account(&pk).unwrap().data.len(), 8);
assert_eq!(svm.get_account(&pk).unwrap().lamports, 10);
}
#[test]
fn test_edge_account_owner_change() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
let owner_initial = Pubkey::new_unique();
svm.set_account(
pk,
Account {
lamports: 100,
data: vec![1],
owner: owner_initial,
executable: false,
rent_epoch: 0,
},
)
.unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let owner_1 = Pubkey::new_unique();
let owner_2 = Pubkey::new_unique();
let mut s1_accts = FastHashMap::default();
s1_accts.insert(
pk,
Arc::new(Account {
lamports: 100,
data: vec![1],
owner: owner_1,
executable: false,
rent_epoch: 0,
}),
);
let delta_1 = SvmSnapshot {
accounts: s1_accts,
sysvars: initial.sysvars.clone(),
};
let mut s2_accts = FastHashMap::default();
s2_accts.insert(
pk,
Arc::new(Account {
lamports: 100,
data: vec![1],
owner: owner_2,
executable: false,
rent_epoch: 0,
}),
);
let delta_2 = SvmSnapshot {
accounts: s2_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());
assert_eq!(svm.get_account(&pk).unwrap().owner, owner_1);
initial.restore_selective_from(&mut svm, &divergent, &delta_1, &delta_2, &prev_exec_dirty);
assert_eq!(svm.get_account(&pk).unwrap().owner, owner_2);
divergent.clear();
divergent.extend(delta_2.accounts().keys().copied());
let empty = SvmSnapshot::empty(initial.clock().clone());
initial.restore_selective(&mut svm, &divergent, &empty);
assert_eq!(svm.get_account(&pk).unwrap().owner, owner_initial);
}
#[test]
fn test_edge_tombstone_roundtrip() {
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 tombstone = Account {
lamports: 0,
..Default::default()
};
let mut a_accts = FastHashMap::default();
a_accts.insert(pk, 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, Arc::new(tombstone.clone()));
let delta_b = SvmSnapshot {
accounts: b_accts,
sysvars: initial.sysvars.clone(),
};
let mut c_accts = FastHashMap::default();
c_accts.insert(pk, Arc::new(make_account(300, &[0xCC; 16])));
let delta_c = SvmSnapshot {
accounts: c_accts,
sysvars: initial.sysvars.clone(),
};
let mut d_accts = FastHashMap::default();
d_accts.insert(pk, Arc::new(tombstone.clone()));
let delta_d = SvmSnapshot {
accounts: d_accts,
sysvars: initial.sysvars.clone(),
};
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
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, 200);
initial.restore_selective_from(&mut svm, &divergent, &delta_a, &delta_b, &prev_exec_dirty);
divergent.clear();
divergent.extend(delta_b.accounts().keys().copied());
assert!(svm.get_account(&pk).is_none());
initial.restore_selective_from(&mut svm, &divergent, &delta_b, &delta_c, &prev_exec_dirty);
divergent.clear();
divergent.extend(delta_c.accounts().keys().copied());
assert_eq!(svm.get_account(&pk).unwrap().lamports, 300);
assert_eq!(svm.get_account(&pk).unwrap().data.len(), 16);
initial.restore_selective_from(&mut svm, &divergent, &delta_c, &delta_d, &prev_exec_dirty);
assert!(svm.get_account(&pk).is_none());
}
#[test]
fn test_edge_cpi_account_persisted_in_delta() {
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 pk_cpi = Pubkey::new_unique();
let mut delta_accts = FastHashMap::default();
delta_accts.insert(pk_cpi, Arc::new(make_account(500, &[0xCC; 64])));
let delta_with_cpi = SvmSnapshot {
accounts: delta_accts,
sysvars: initial.sysvars.clone(),
};
let empty_delta = SvmSnapshot::empty(initial.clock().clone());
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta_with_cpi);
divergent.extend(delta_with_cpi.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_cpi).unwrap().lamports, 500);
initial.restore_selective_from(
&mut svm,
&divergent,
&delta_with_cpi,
&empty_delta,
&prev_exec_dirty,
);
assert!(
svm.get_account(&pk_cpi).is_none(),
"CPI account should be zeroed when switching to state without it"
);
assert_eq!(svm.get_account(&pk_base).unwrap().lamports, 100);
}
#[test]
fn test_edge_execution_modifies_to_initial_value() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
let initial_acct = make_account(10, &[1, 2]);
svm.set_account(pk, initial_acct.clone()).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 mut s2_accts = FastHashMap::default();
s2_accts.insert(pk, Arc::new(make_account(300, &[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();
initial.restore_selective(&mut svm, &divergent, &delta_1);
divergent.extend(delta_1.accounts().keys().copied());
svm.set_account(pk, initial_acct.clone()).unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk);
divergent.extend(prev_exec_dirty.iter().copied());
initial.restore_selective_from(&mut svm, &divergent, &delta_1, &delta_2, &prev_exec_dirty);
assert_eq!(
svm.get_account(&pk).unwrap().lamports,
300,
"pk must be 300 even though execution set it to initial value"
);
}
#[test]
fn test_edge_from_tombstone_to_live() {
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 prev_accts = FastHashMap::default();
prev_accts.insert(
pk,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
let prev_delta = SvmSnapshot {
accounts: prev_accts,
sysvars: initial.sysvars.clone(),
};
let mut next_accts = FastHashMap::default();
next_accts.insert(pk, Arc::new(make_account(500, &[0xFF; 32])));
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());
assert!(svm.get_account(&pk).is_none());
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!(count, 1);
assert_eq!(svm.get_account(&pk).unwrap().lamports, 500);
assert_eq!(svm.get_account(&pk).unwrap().data, vec![0xFF; 32]);
}
#[test]
fn test_edge_from_live_to_tombstone() {
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 prev_accts = FastHashMap::default();
prev_accts.insert(pk, Arc::new(make_account(500, &[0xAA])));
let prev_delta = SvmSnapshot {
accounts: prev_accts,
sysvars: initial.sysvars.clone(),
};
let mut next_accts = FastHashMap::default();
next_accts.insert(
pk,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
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());
assert_eq!(svm.get_account(&pk).unwrap().lamports, 500);
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective_from(
&mut svm,
&divergent,
&prev_delta,
&next_delta,
&prev_exec_dirty,
);
assert!(
svm.get_account(&pk).is_none(),
"pk should be deleted (tombstone)"
);
}
#[test]
fn test_edge_same_state_consecutive() {
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_a = Arc::new(make_account(100, &[0xAA]));
let shared_arc_b = Arc::new(make_account(200, &[0xBB]));
let mut delta_accts = FastHashMap::default();
delta_accts.insert(pk_a, shared_arc_a);
delta_accts.insert(pk_b, shared_arc_b);
let delta = SvmSnapshot {
accounts: delta_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);
divergent.extend(delta.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
let count =
initial.restore_selective_from(&mut svm, &divergent, &delta, &delta, &prev_exec_dirty);
assert_eq!(
count, 0,
"no writes needed when same state with no exec-dirty"
);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
}
#[test]
fn test_edge_exec_dirty_forces_write_despite_same_arc() {
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 accts = FastHashMap::default();
accts.insert(pk, shared_arc.clone());
let delta = SvmSnapshot {
accounts: accts,
sysvars: initial.sysvars.clone(),
};
assert!(Arc::ptr_eq(&delta.accounts()[&pk], &delta.accounts()[&pk]));
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta);
divergent.extend(delta.accounts().keys().copied());
svm.set_account(pk, make_account(99999, &[0xDE, 0xAD]))
.unwrap();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
prev_exec_dirty.insert(pk);
divergent.extend(prev_exec_dirty.iter().copied());
let count =
initial.restore_selective_from(&mut svm, &divergent, &delta, &delta, &prev_exec_dirty);
assert_eq!(
count, 1,
"pk must be written despite same Arc because exec-dirty"
);
assert_eq!(svm.get_account(&pk).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk).unwrap().data, vec![0xAA]);
}
#[test]
fn test_edge_from_mixed_scenario() {
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();
let pk_e = Pubkey::new_unique();
svm.set_account(pk_a, make_account(1, &[1])).unwrap();
svm.set_account(pk_b, make_account(2, &[2])).unwrap();
svm.set_account(pk_c, make_account(3, &[3])).unwrap();
svm.set_account(pk_d, make_account(4, &[4])).unwrap();
svm.set_account(pk_e, make_account(5, &[5])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b, pk_c, pk_d, pk_e].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let shared_arc_a = Arc::new(make_account(10, &[0xAA]));
let shared_arc_b = Arc::new(make_account(20, &[0xBB]));
let mut prev_accts = FastHashMap::default();
prev_accts.insert(pk_a, shared_arc_a.clone());
prev_accts.insert(pk_b, shared_arc_b.clone());
prev_accts.insert(pk_c, Arc::new(make_account(30, &[0xC1])));
prev_accts.insert(pk_e, Arc::new(make_account(50, &[0xEE])));
let prev_delta = SvmSnapshot {
accounts: prev_accts,
sysvars: initial.sysvars.clone(),
};
let mut next_accts = FastHashMap::default();
next_accts.insert(pk_a, shared_arc_a.clone()); next_accts.insert(pk_b, shared_arc_b.clone()); next_accts.insert(pk_c, Arc::new(make_account(31, &[0xC2]))); next_accts.insert(pk_d, Arc::new(make_account(40, &[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());
svm.set_account(pk_b, make_account(999, &[0xFF])).unwrap();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
prev_exec_dirty.insert(pk_b);
divergent.extend(prev_exec_dirty.iter().copied());
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, 31);
assert_eq!(svm.get_account(&pk_d).unwrap().lamports, 40);
assert_eq!(svm.get_account(&pk_e).unwrap().lamports, 5);
assert_eq!(count, 4);
}
#[test]
fn test_edge_deep_chain_5_levels() {
let mut svm = LiteSVM::new();
let pks: Vec<Pubkey> = (0..6).map(|_| Pubkey::new_unique()).collect();
let pk_base = Pubkey::new_unique();
svm.set_account(pk_base, make_account(1, &[0])).unwrap();
let tracked: HashSet<Pubkey> = [pk_base].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let root = SvmSnapshot::empty(initial.clock().clone());
let mut levels: Vec<SvmSnapshot> = Vec::new();
let mut parent = &root;
for i in 0..6 {
svm.set_account(pks[i], make_account((i as u64 + 1) * 100, &[i as u8]))
.unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pks[i]);
let delta = SvmSnapshot::take_delta(&svm, parent, &dirty);
levels.push(delta);
parent = &levels[i];
}
assert!(
Arc::ptr_eq(
&levels[0].accounts()[&pks[0]],
&levels[5].accounts()[&pks[0]],
),
"L5 should share Arc with L1 for pk[0]"
);
assert_eq!(levels[5].account_count(), 6);
for level_idx in 0..6 {
let delta = &levels[level_idx];
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
for &pk in &pks {
divergent.insert(pk);
}
initial.restore_selective(&mut svm, &divergent, delta);
for i in 0..=level_idx {
let got = svm.get_account(&pks[i]).map_or(0, |a| a.lamports);
assert_eq!(
got,
(i as u64 + 1) * 100,
"L{}: pk[{}] expected {} got {}",
level_idx,
i,
(i + 1) * 100,
got
);
}
for i in (level_idx + 1)..6 {
assert!(
svm.get_account(&pks[i]).is_none(),
"L{}: pk[{}] should not exist",
level_idx,
i
);
}
}
}
#[test]
fn test_edge_deep_chain_leaf_to_root() {
let mut svm = LiteSVM::new();
let pks: Vec<Pubkey> = (0..5).map(|_| Pubkey::new_unique()).collect();
let pk_base = Pubkey::new_unique();
svm.set_account(pk_base, make_account(1, &[0])).unwrap();
let tracked: HashSet<Pubkey> = [pk_base].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let root = SvmSnapshot::empty(initial.clock().clone());
let mut parent = &root;
let mut levels: Vec<SvmSnapshot> = Vec::new();
for i in 0..5 {
svm.set_account(pks[i], make_account((i as u64 + 1) * 100, &[i as u8]))
.unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pks[i]);
let delta = SvmSnapshot::take_delta(&svm, parent, &dirty);
levels.push(delta);
parent = &levels[i];
}
let l5 = &levels[4];
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, l5);
divergent.extend(l5.accounts().keys().copied());
for i in 0..5 {
assert!(
svm.get_account(&pks[i]).is_some(),
"pk[{}] should exist in L5",
i
);
}
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective_from(&mut svm, &divergent, l5, &root, &prev_exec_dirty);
for i in 0..5 {
assert!(
svm.get_account(&pks[i]).is_none(),
"pk[{}] should be zeroed after jumping to root",
i
);
}
assert_eq!(svm.get_account(&pk_base).unwrap().lamports, 1);
}
#[test]
fn test_edge_deep_chain_sibling_jump() {
let mut svm = LiteSVM::new();
let pk_shared = Pubkey::new_unique(); let pk_diverge = Pubkey::new_unique(); svm.set_account(pk_shared, make_account(10, &[1])).unwrap();
svm.set_account(pk_diverge, make_account(20, &[2])).unwrap();
let tracked: HashSet<Pubkey> = [pk_shared, pk_diverge].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let root = SvmSnapshot::empty(initial.clock().clone());
svm.set_account(pk_shared, make_account(100, &[0xAA]))
.unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_shared);
let l3 = SvmSnapshot::take_delta(&svm, &root, &dirty);
svm.set_account(pk_diverge, make_account(300, &[0xBB]))
.unwrap();
let mut dirty_a = DirtyTracker::new();
dirty_a.mark_account_dirty(&pk_diverge);
let l4a = SvmSnapshot::take_delta(&svm, &l3, &dirty_a);
svm.set_account(pk_diverge, make_account(400, &[0xCC]))
.unwrap();
let mut dirty_b = DirtyTracker::new();
dirty_b.mark_account_dirty(&pk_diverge);
let l4b = SvmSnapshot::take_delta(&svm, &l3, &dirty_b);
assert!(
Arc::ptr_eq(&l4a.accounts()[&pk_shared], &l4b.accounts()[&pk_shared],),
"siblings should share ancestor Arc for pk_shared"
);
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &l4a);
divergent.extend(l4a.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_shared).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_diverge).unwrap().lamports, 300);
let prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let count = initial.restore_selective_from(&mut svm, &divergent, &l4a, &l4b, &prev_exec_dirty);
assert_eq!(
count, 1,
"only pk_diverge should be written (pk_shared same Arc)"
);
assert_eq!(svm.get_account(&pk_shared).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_diverge).unwrap().lamports, 400);
}
#[test]
fn test_edge_take_delta_cpi_not_in_parent() {
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 parent_delta = SvmSnapshot::empty(_initial.clock().clone());
let pk_cpi = Pubkey::new_unique();
svm.set_account(pk_cpi, make_account(777, &[0xCC; 48]))
.unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_cpi);
let delta = SvmSnapshot::take_delta(&svm, &parent_delta, &dirty);
assert!(delta.accounts().contains_key(&pk_cpi));
assert_eq!(delta.accounts()[&pk_cpi].lamports, 777);
assert_eq!(delta.accounts()[&pk_cpi].data, vec![0xCC; 48]);
assert!(!delta.accounts().contains_key(&pk_base));
}
#[test]
fn test_edge_take_delta_dirty_but_unchanged() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(100, &[0xAA])).unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let _initial = SvmSnapshot::take(&svm, &tracked);
let mut parent_accts = FastHashMap::default();
let parent_arc = Arc::new(make_account(100, &[0xAA]));
parent_accts.insert(pk, parent_arc.clone());
let parent_delta = SvmSnapshot {
accounts: parent_accts,
sysvars: _initial.sysvars.clone(),
};
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk);
let child_delta = SvmSnapshot::take_delta(&svm, &parent_delta, &dirty);
assert_eq!(child_delta.accounts()[&pk].lamports, 100);
assert_eq!(child_delta.accounts()[&pk].data, vec![0xAA]);
assert!(
!Arc::ptr_eq(&parent_delta.accounts()[&pk], &child_delta.accounts()[&pk],),
"take_delta should create new Arc even if value unchanged"
);
}
#[test]
fn test_edge_take_delta_mixed_inherit_and_dirty() {
let mut svm = LiteSVM::new();
let pks: Vec<Pubkey> = (0..5).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 mut parent_accts = FastHashMap::default();
for (i, pk) in pks.iter().enumerate() {
parent_accts.insert(
*pk,
Arc::new(make_account((i as u64 + 1) * 100, &[i as u8 + 10])),
);
}
let parent_delta = SvmSnapshot {
accounts: parent_accts,
sysvars: _initial.sysvars.clone(),
};
for (pk, acct) in parent_delta.accounts() {
svm.set_account(*pk, (**acct).clone()).unwrap();
}
svm.set_account(pks[0], make_account(999, &[0xFF])).unwrap();
svm.set_account(
pks[1],
Account {
lamports: 0,
..Default::default()
},
)
.unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pks[0]);
dirty.mark_account_dirty(&pks[1]);
let child_delta = SvmSnapshot::take_delta(&svm, &parent_delta, &dirty);
assert_eq!(child_delta.accounts()[&pks[0]].lamports, 999);
assert!(!Arc::ptr_eq(
&parent_delta.accounts()[&pks[0]],
&child_delta.accounts()[&pks[0]],
));
assert_eq!(child_delta.accounts()[&pks[1]].lamports, 0);
assert!(!Arc::ptr_eq(
&parent_delta.accounts()[&pks[1]],
&child_delta.accounts()[&pks[1]],
));
for i in 2..5 {
assert!(
Arc::ptr_eq(
&parent_delta.accounts()[&pks[i]],
&child_delta.accounts()[&pks[i]],
),
"pk[{}] should be ptr_eq with parent (inherited)",
i
);
}
}
#[test]
fn test_edge_take_full_vs_take_delta_tombstone_behavior() {
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 base = SvmSnapshot::take(&svm, &tracked);
let parent_delta = SvmSnapshot::empty(base.clock().clone());
svm.set_account(
pk,
Account {
lamports: 0,
..Default::default()
},
)
.unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk);
let delta = SvmSnapshot::take_delta(&svm, &parent_delta, &dirty);
assert!(
delta.accounts().contains_key(&pk),
"take_delta should have tombstone entry"
);
assert_eq!(delta.accounts()[&pk].lamports, 0);
let full = SvmSnapshot::take_full(&svm, &base, &dirty);
assert!(
!full.accounts().contains_key(&pk),
"take_full should remove deleted account, not keep tombstone"
);
}
#[test]
fn test_edge_take_full_preserves_untracked() {
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(10, &[1])).unwrap();
svm.set_account(pk_b, make_account(20, &[2])).unwrap();
svm.set_account(pk_c, make_account(30, &[3])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b, pk_c].into_iter().collect();
let base = SvmSnapshot::take(&svm, &tracked);
svm.set_account(pk_b, make_account(200, &[0xBB])).unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_b);
let full = SvmSnapshot::take_full(&svm, &base, &dirty);
assert!(
Arc::ptr_eq(&base.accounts()[&pk_a], &full.accounts()[&pk_a],),
"A should be inherited Arc"
);
assert!(
Arc::ptr_eq(&base.accounts()[&pk_c], &full.accounts()[&pk_c],),
"C should be inherited Arc"
);
assert_eq!(full.accounts()[&pk_b].lamports, 200);
assert!(
!Arc::ptr_eq(&base.accounts()[&pk_b], &full.accounts()[&pk_b],),
"B should be new Arc"
);
}
#[test]
fn test_edge_delete_restore_delete_cycle() {
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 dead_accts = FastHashMap::default();
dead_accts.insert(
pk,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
let state_dead = SvmSnapshot {
accounts: dead_accts,
sysvars: initial.sysvars.clone(),
};
let mut alive_accts = FastHashMap::default();
alive_accts.insert(pk, Arc::new(make_account(500, &[0xFF])));
let state_alive = SvmSnapshot {
accounts: alive_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_restore(
&initial,
&mut svm,
&divergent,
&state_dead,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(state_dead.accounts().keys().copied());
assert!(svm.get_account(&pk).is_none(), "iter 1: pk should be dead");
prev_exec_dirty.clear();
prev_delta = Some(state_dead.clone());
simulate_restore(
&initial,
&mut svm,
&divergent,
&state_alive,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(state_alive.accounts().keys().copied());
assert_eq!(
svm.get_account(&pk).unwrap().lamports,
500,
"iter 2: pk should be alive"
);
prev_exec_dirty.clear();
prev_delta = Some(state_alive.clone());
simulate_restore(
&initial,
&mut svm,
&divergent,
&state_dead,
prev_delta.as_ref(),
&prev_exec_dirty,
);
assert!(
svm.get_account(&pk).is_none(),
"iter 3: pk should be dead again"
);
}
#[test]
fn test_edge_cpi_account_lifecycle_across_iterations() {
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 s1_accts = FastHashMap::default();
s1_accts.insert(pk_base, 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 mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_delta: Option<SvmSnapshot> = None;
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta_1,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta_1.accounts().keys().copied());
let pk_cpi1 = Pubkey::new_unique();
svm.set_account(pk_cpi1, make_account(777, &[0xCC]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk_base);
prev_exec_dirty.insert(pk_cpi1);
divergent.extend(prev_exec_dirty.iter().copied());
prev_delta = Some(delta_1.clone());
simulate_restore(
&initial,
&mut svm,
&divergent,
&empty_delta,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
assert!(
svm.get_account(&pk_cpi1).is_none(),
"pk_cpi1 should be cleaned up"
);
assert_eq!(svm.get_account(&pk_base).unwrap().lamports, 100);
prev_exec_dirty.clear();
prev_delta = Some(empty_delta.clone());
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta_1,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta_1.accounts().keys().copied());
let pk_cpi2 = Pubkey::new_unique();
svm.set_account(pk_cpi2, make_account(888, &[0xDD]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk_base);
prev_exec_dirty.insert(pk_cpi2);
divergent.extend(prev_exec_dirty.iter().copied());
prev_delta = Some(delta_1.clone());
simulate_restore(
&initial,
&mut svm,
&divergent,
&empty_delta,
prev_delta.as_ref(),
&prev_exec_dirty,
);
assert!(
svm.get_account(&pk_cpi1).is_none(),
"pk_cpi1 should still be gone"
);
assert!(
svm.get_account(&pk_cpi2).is_none(),
"pk_cpi2 should be cleaned up"
);
assert_eq!(svm.get_account(&pk_base).unwrap().lamports, 100);
}
#[test]
fn test_edge_many_accounts_stress() {
let mut svm = LiteSVM::new();
let pks: Vec<Pubkey> = (0..50).map(|_| Pubkey::new_unique()).collect();
for (i, pk) in pks.iter().enumerate() {
svm.set_account(*pk, make_account(i as u64 + 1, &[i as u8]))
.unwrap();
}
let tracked: HashSet<Pubkey> = pks.iter().copied().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut states: Vec<SvmSnapshot> = Vec::new();
for i in 0..10 {
let mut accts = FastHashMap::default();
for j in 0..5 {
let idx = i * 5 + j;
accts.insert(
pks[idx],
Arc::new(make_account(
(i as u64 + 1) * 1000 + j as u64,
&[(i * 5 + j) as u8 + 100],
)),
);
}
states.push(SvmSnapshot {
accounts: 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;
let sequence = [0, 5, 3, 8, 1, 9, 2, 7, 4, 6, 0, 3, 9, 1, 5, 8, 2, 6, 7, 4];
for (iter, &state_idx) in sequence.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());
for (k, pk) in pks.iter().enumerate() {
let expected_lamports = if k >= state_idx * 5 && k < (state_idx + 1) * 5 {
(state_idx as u64 + 1) * 1000 + (k - state_idx * 5) as u64
} else {
k as u64 + 1
};
let got = svm.get_account(pk).map_or(0, |a| a.lamports);
assert_eq!(
got, expected_lamports,
"iter {} state {}: pk[{}] expected {} got {}",
iter, state_idx, k, expected_lamports, got
);
}
let dirty_idx = iter % 50;
svm.set_account(pks[dirty_idx], make_account(99999, &[0xDE]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pks[dirty_idx]);
divergent.extend(prev_exec_dirty.iter().copied());
prev_delta = Some(delta.clone());
}
}
#[test]
fn test_edge_failed_action_no_prev_delta_arc() {
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 mut s1_accts = FastHashMap::default();
s1_accts.insert(pk, Arc::new(make_account(100, &[0xAA])));
let delta_1 = SvmSnapshot {
accounts: s1_accts,
sysvars: initial.sysvars.clone(),
};
let mut s2_accts = FastHashMap::default();
s2_accts.insert(pk, 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();
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta_1,
None,
&prev_exec_dirty,
);
divergent.extend(delta_1.accounts().keys().copied());
assert_eq!(svm.get_account(&pk).unwrap().lamports, 100);
svm.set_account(pk, make_account(55555, &[0xFF; 100]))
.unwrap();
prev_exec_dirty.insert(pk);
divergent.extend(prev_exec_dirty.iter().copied());
let prev_delta: Option<SvmSnapshot> = None;
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta_2,
prev_delta.as_ref(),
&prev_exec_dirty,
);
assert_eq!(
svm.get_account(&pk).unwrap().lamports,
200,
"must restore correctly even without prev_delta"
);
}
#[test]
fn test_edge_alternating_success_failure() {
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 make_state = |val: u64| -> SvmSnapshot {
let mut accts = FastHashMap::default();
accts.insert(pk, Arc::new(make_account(val, &[val as u8])));
SvmSnapshot {
accounts: accts,
sysvars: initial.sysvars.clone(),
}
};
let states: Vec<SvmSnapshot> = (1..=6).map(|i| make_state(i * 100)).collect();
let successes = [true, false, true, false, true, false];
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, (&success, delta)) in successes.iter().zip(states.iter()).enumerate() {
simulate_restore(
&initial,
&mut svm,
&divergent,
delta,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta.accounts().keys().copied());
let expected = (iter as u64 + 1) * 100;
assert_eq!(
svm.get_account(&pk).unwrap().lamports,
expected,
"iter {}: expected {}",
iter,
expected
);
svm.set_account(pk, make_account(99999, &[0xEE])).unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk);
divergent.extend(prev_exec_dirty.iter().copied());
if success {
prev_delta = Some(delta.clone());
} else {
prev_delta = None; }
}
}
#[test]
fn test_edge_dirty_tracker_clear_is_complete() {
let mut tracker = DirtyTracker::new();
let fee_payer = Pubkey::new_unique();
let program = Pubkey::new_unique();
for _tx in 0..3 {
let mut accounts = Vec::new();
for _ in 0..3 {
accounts.push(AccountMeta::new(Pubkey::new_unique(), false));
}
let ix = Instruction::new_with_bytes(program, &[], accounts);
tracker.record_tx(&[ix], &fee_payer);
}
tracker.mark_clock_dirty(100);
assert!(tracker.dirty_count() > 0);
assert!(!tracker.dirty_accounts().is_empty());
assert!(!tracker.read_accounts().is_empty());
assert!(tracker.is_clock_dirty());
tracker.clear();
assert_eq!(tracker.dirty_count(), 0);
assert!(tracker.dirty_accounts().is_empty());
assert!(tracker.read_accounts().is_empty());
assert!(!tracker.is_clock_dirty());
let pk_new_1 = Pubkey::new_unique();
let pk_new_2 = Pubkey::new_unique();
tracker.mark_account_dirty(&pk_new_1);
tracker.mark_account_dirty(&pk_new_2);
assert_eq!(tracker.dirty_count(), 2);
assert!(tracker.dirty_accounts().contains(&pk_new_1));
assert!(tracker.dirty_accounts().contains(&pk_new_2));
}
#[test]
fn test_edge_dirty_tracker_duplicate_writable() {
let mut tracker = DirtyTracker::new();
let fee_payer = Pubkey::new_unique();
let program = Pubkey::new_unique();
let pk_shared = Pubkey::new_unique();
let ix1 = Instruction::new_with_bytes(
program,
&[],
vec![
AccountMeta::new(pk_shared, false),
AccountMeta::new(Pubkey::new_unique(), false),
],
);
let ix2 = Instruction::new_with_bytes(program, &[], vec![AccountMeta::new(pk_shared, false)]);
tracker.record_tx(&[ix1, ix2], &fee_payer);
let ix3 = Instruction::new_with_bytes(program, &[], vec![AccountMeta::new(pk_shared, false)]);
tracker.record_tx(&[ix3], &fee_payer);
let count = tracker
.dirty_accounts()
.iter()
.filter(|&&pk| pk == pk_shared)
.count();
assert_eq!(count, 1, "pk_shared should be deduplicated in dirty set");
assert!(
tracker.dirty_count() >= 2,
"should have at least fee_payer and pk_shared"
);
}
#[test]
fn test_edge_dirty_tracker_clone_is_fresh() {
let mut tracker = DirtyTracker::new();
let fee_payer = Pubkey::new_unique();
let program = Pubkey::new_unique();
for _ in 0..5 {
let ix = Instruction::new_with_bytes(
program,
&[],
vec![AccountMeta::new(Pubkey::new_unique(), false)],
);
tracker.record_tx(&[ix], &fee_payer);
}
tracker.mark_clock_dirty(100);
assert!(tracker.dirty_count() > 0);
assert!(tracker.is_clock_dirty());
let cloned = tracker.clone();
assert_eq!(cloned.dirty_count(), 0, "cloned tracker should be empty");
assert!(cloned.dirty_accounts().is_empty());
assert!(cloned.read_accounts().is_empty());
assert!(
!cloned.is_clock_dirty(),
"cloned tracker should not have clock dirty"
);
}