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_multi_iteration_dirty_overlap() {
let mut svm = LiteSVM::new();
let pk_a = Pubkey::new_unique();
let pk_b = Pubkey::new_unique();
let pk_c = Pubkey::new_unique();
let acct_a = make_account(100, &[1]);
let acct_b = make_account(200, &[2]);
let acct_c = make_account(300, &[3]);
svm.set_account(pk_a, acct_a.clone()).unwrap();
svm.set_account(pk_b, acct_b.clone()).unwrap();
svm.set_account(pk_c, acct_c.clone()).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b, pk_c].into_iter().collect();
let snap = SvmSnapshot::take(&svm, &tracked);
let mut dirty = DirtyTracker::new();
dirty.clear();
svm.set_account(pk_a, make_account(999, &[0xAA])).unwrap();
svm.set_account(pk_b, make_account(888, &[0xBB])).unwrap();
dirty.mark_account_dirty(&pk_a);
dirty.mark_account_dirty(&pk_b);
snap.restore(&mut svm, &dirty);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300);
dirty.clear();
svm.set_account(pk_b, make_account(777, &[0xBB])).unwrap();
svm.set_account(pk_c, make_account(666, &[0xCC])).unwrap();
dirty.mark_account_dirty(&pk_b);
dirty.mark_account_dirty(&pk_c);
snap.restore(&mut svm, &dirty);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300);
dirty.clear();
svm.set_account(pk_a, make_account(555, &[0xAA])).unwrap();
svm.set_account(pk_c, make_account(444, &[0xCC])).unwrap();
dirty.mark_account_dirty(&pk_a);
dirty.mark_account_dirty(&pk_c);
snap.restore(&mut svm, &dirty);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300);
}
#[test]
fn test_delta_chain_restore_full_at_each_level() {
let mut svm = LiteSVM::new();
let pk_x = Pubkey::new_unique();
let pk_y = Pubkey::new_unique();
let pk_z = Pubkey::new_unique();
svm.set_account(pk_x, make_account(10, &[1])).unwrap();
svm.set_account(pk_y, make_account(20, &[2])).unwrap();
svm.set_account(pk_z, make_account(30, &[3])).unwrap();
let delta_root = SvmSnapshot::empty(svm.get_sysvar::<Clock>());
svm.set_account(pk_x, make_account(100, &[0xA1])).unwrap();
let mut dirty_a = DirtyTracker::new();
dirty_a.mark_account_dirty(&pk_x);
let delta_a = SvmSnapshot::take_delta(&svm, &delta_root, &dirty_a);
svm.set_account(pk_y, make_account(200, &[0xB2])).unwrap();
let mut dirty_b = DirtyTracker::new();
dirty_b.mark_account_dirty(&pk_y);
let delta_b = SvmSnapshot::take_delta(&svm, &delta_a, &dirty_b);
svm.set_account(pk_z, make_account(300, &[0xC3])).unwrap();
let mut dirty_c = DirtyTracker::new();
dirty_c.mark_account_dirty(&pk_z);
let delta_c = SvmSnapshot::take_delta(&svm, &delta_b, &dirty_c);
svm.set_account(pk_x, make_account(1, &[0])).unwrap();
svm.set_account(pk_y, make_account(1, &[0])).unwrap();
svm.set_account(pk_z, make_account(1, &[0])).unwrap();
delta_a.restore_full(&mut svm);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100);
svm.set_account(pk_x, make_account(1, &[0])).unwrap();
svm.set_account(pk_y, make_account(1, &[0])).unwrap();
svm.set_account(pk_z, make_account(1, &[0])).unwrap();
delta_b.restore_full(&mut svm);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_y).unwrap().lamports, 200);
svm.set_account(pk_x, make_account(1, &[0])).unwrap();
svm.set_account(pk_y, make_account(1, &[0])).unwrap();
svm.set_account(pk_z, make_account(1, &[0])).unwrap();
delta_c.restore_full(&mut svm);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_y).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_z).unwrap().lamports, 300);
}
#[test]
fn test_restore_selective_to_from_transition() {
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(100, &[1])).unwrap();
svm.set_account(pk_b, make_account(200, &[2])).unwrap();
svm.set_account(pk_c, make_account(300, &[3])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b, pk_c].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
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 _delta_0 = SvmSnapshot::empty(initial.clock().clone());
let mut delta_1_accounts = FastHashMap::default();
delta_1_accounts.insert(pk_a, Arc::new(make_account(500, &[5])));
let delta_1 = SvmSnapshot {
accounts: delta_1_accounts,
sysvars: make_test_sysvars(10),
};
let mut delta_2_accounts = FastHashMap::default();
delta_2_accounts.insert(pk_a, delta_1.accounts()[&pk_a].clone()); delta_2_accounts.insert(pk_b, Arc::new(make_account(600, &[6])));
let delta_2 = SvmSnapshot {
accounts: delta_2_accounts,
sysvars: make_test_sysvars(20),
};
assert!(prev_delta_arc.is_none());
initial.restore_selective(&mut svm, &divergent_keys, &delta_1);
divergent_keys.clear();
divergent_keys.extend(delta_1.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 500);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200); assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300);
svm.set_account(pk_a, make_account(550, &[0xAA])).unwrap();
svm.set_account(pk_c, make_account(350, &[0xCC])).unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_a);
dirty.mark_account_dirty(&pk_c);
let action_succeeded = true;
prev_exec_dirty.clear();
if action_succeeded {
prev_exec_dirty.extend(dirty.dirty_accounts().iter().copied());
divergent_keys.extend(prev_exec_dirty.iter().copied());
}
prev_delta_arc = Some(delta_1.clone());
assert!(divergent_keys.contains(&pk_a));
assert!(divergent_keys.contains(&pk_c));
assert!(!divergent_keys.contains(&pk_b));
assert!(prev_delta_arc.is_some());
let prev = prev_delta_arc.as_ref().unwrap();
initial.restore_selective_from(&mut svm, &divergent_keys, prev, &delta_2, &prev_exec_dirty);
divergent_keys.clear();
divergent_keys.extend(delta_2.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 500);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 600);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300);
assert_eq!(svm.get_sysvar::<Clock>().slot, 20);
}
#[test]
fn test_divergent_keys_union_exec_dirty_outside_delta() {
let mut svm = LiteSVM::new();
let pk_a = Pubkey::new_unique();
let pk_cpi = Pubkey::new_unique();
svm.set_account(pk_a, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut divergent_keys: FastHashSet<Pubkey> = FastHashSet::default();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
let mut delta_accounts = FastHashMap::default();
delta_accounts.insert(pk_a, Arc::new(make_account(500, &[5])));
let delta = SvmSnapshot {
accounts: delta_accounts,
sysvars: initial.sysvars.clone(),
};
initial.restore_selective(&mut svm, &divergent_keys, &delta);
divergent_keys.clear();
divergent_keys.extend(delta.accounts().keys().copied());
svm.set_account(pk_a, make_account(550, &[0xAA])).unwrap();
svm.set_account(pk_cpi, make_account(999, &[9, 9])).unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk_a);
prev_exec_dirty.insert(pk_cpi);
divergent_keys.extend(prev_exec_dirty.iter().copied());
assert!(divergent_keys.contains(&pk_a));
assert!(divergent_keys.contains(&pk_cpi));
let empty_delta = SvmSnapshot::empty(initial.clock().clone());
let prev_delta = delta.clone();
initial.restore_selective_from(
&mut svm,
&divergent_keys,
&prev_delta,
&empty_delta,
&prev_exec_dirty,
);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert!(svm.get_account(&pk_cpi).is_none());
}
#[test]
fn test_divergent_keys_prev_exec_dirty_account_in_initial() {
let mut svm = LiteSVM::new();
let pk_target = Pubkey::new_unique();
let pk_delta = Pubkey::new_unique();
svm.set_account(pk_target, make_account(100, &[1])).unwrap();
svm.set_account(pk_delta, make_account(200, &[2])).unwrap();
let tracked: HashSet<Pubkey> = [pk_target, pk_delta].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut prev_accounts = FastHashMap::default();
prev_accounts.insert(pk_delta, Arc::new(make_account(500, &[5])));
let prev_delta = SvmSnapshot {
accounts: prev_accounts,
sysvars: initial.sysvars.clone(),
};
let next_delta = prev_delta.clone();
let mut divergent_keys: FastHashSet<Pubkey> = FastHashSet::default();
divergent_keys.insert(pk_delta);
divergent_keys.insert(pk_target);
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
prev_exec_dirty.insert(pk_target);
svm.set_account(pk_delta, make_account(500, &[5])).unwrap();
svm.set_account(pk_target, make_account(999, &[9])).unwrap();
initial.restore_selective_from(
&mut svm,
&divergent_keys,
&prev_delta,
&next_delta,
&prev_exec_dirty,
);
assert_eq!(svm.get_account(&pk_target).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_delta).unwrap().lamports, 500);
}
#[test]
fn test_same_state_picked_consecutively() {
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(100, &[1])).unwrap();
svm.set_account(pk_b, make_account(200, &[2])).unwrap();
svm.set_account(pk_c, make_account(300, &[3])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b, pk_c].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut delta_accounts = FastHashMap::default();
delta_accounts.insert(pk_a, Arc::new(make_account(500, &[5])));
delta_accounts.insert(pk_b, Arc::new(make_account(600, &[6])));
let delta = SvmSnapshot {
accounts: delta_accounts,
sysvars: make_test_sysvars(50),
};
let mut divergent_keys: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent_keys, &delta);
divergent_keys.clear();
divergent_keys.extend(delta.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 500);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 600);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300);
svm.set_account(pk_a, make_account(555, &[0xAA])).unwrap();
svm.set_account(pk_c, make_account(333, &[0xCC])).unwrap();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
prev_exec_dirty.insert(pk_a);
prev_exec_dirty.insert(pk_c);
divergent_keys.extend(prev_exec_dirty.iter().copied());
let count =
initial.restore_selective_from(&mut svm, &divergent_keys, &delta, &delta, &prev_exec_dirty);
assert_eq!(count, 2);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 500); assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 600); assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300); }
#[test]
fn test_restore_selective_tombstone_overlay() {
let mut svm = LiteSVM::new();
let pk_live = Pubkey::new_unique();
let pk_tombstoned = Pubkey::new_unique();
svm.set_account(pk_live, make_account(100, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk_live].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut delta_accounts = FastHashMap::default();
delta_accounts.insert(
pk_tombstoned,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
let delta = SvmSnapshot {
accounts: delta_accounts,
sysvars: initial.sysvars.clone(),
};
svm.set_account(pk_tombstoned, make_account(999, &[9, 9]))
.unwrap();
assert!(svm.get_account(&pk_tombstoned).is_some());
let divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta);
assert!(svm.get_account(&pk_tombstoned).is_none());
}
#[test]
fn test_restore_selective_from_tombstone_overlay() {
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_accounts = FastHashMap::default();
prev_accounts.insert(pk, Arc::new(make_account(500, &[5])));
let prev_delta = SvmSnapshot {
accounts: prev_accounts,
sysvars: initial.sysvars.clone(),
};
let mut next_accounts = FastHashMap::default();
next_accounts.insert(
pk,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
let next_delta = SvmSnapshot {
accounts: next_accounts,
sysvars: initial.sysvars.clone(),
};
svm.set_account(pk, make_account(500, &[5])).unwrap();
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.insert(pk);
let prev_exec_dirty = FastHashSet::default();
initial.restore_selective_from(
&mut svm,
&divergent,
&prev_delta,
&next_delta,
&prev_exec_dirty,
);
assert!(svm.get_account(&pk).is_none());
}
#[test]
fn test_failed_action_empty_prev_exec_dirty() {
let mut svm = LiteSVM::new();
let pk_a = Pubkey::new_unique();
let pk_b = Pubkey::new_unique();
svm.set_account(pk_a, make_account(100, &[1])).unwrap();
svm.set_account(pk_b, make_account(200, &[2])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut delta_accounts = FastHashMap::default();
delta_accounts.insert(pk_a, Arc::new(make_account(500, &[5])));
let delta = SvmSnapshot {
accounts: delta_accounts,
sysvars: make_test_sysvars(10),
};
let mut divergent_keys: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent_keys, &delta);
divergent_keys.clear();
divergent_keys.extend(delta.accounts().keys().copied());
svm.set_account(pk_a, make_account(550, &[0xAA])).unwrap();
svm.set_account(pk_b, make_account(250, &[0xBB])).unwrap();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
prev_exec_dirty.insert(pk_a);
prev_exec_dirty.insert(pk_b);
divergent_keys.extend(prev_exec_dirty.iter().copied());
let prev_delta = delta.clone();
initial.restore_selective_from(
&mut svm,
&divergent_keys,
&prev_delta,
&delta,
&prev_exec_dirty,
);
divergent_keys.clear();
divergent_keys.extend(delta.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 500);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
svm.set_account(pk_a, make_account(777, &[0xFF])).unwrap();
svm.set_account(pk_b, make_account(888, &[0xEE])).unwrap();
prev_exec_dirty.clear();
let prev_delta_2 = delta.clone();
let count = initial.restore_selective_from(
&mut svm,
&divergent_keys,
&prev_delta_2,
&delta,
&prev_exec_dirty,
);
assert_eq!(count, 0); assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 777); assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 888); }
#[test]
fn test_deep_chain_five_levels() {
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();
let pks = [pk_a, pk_b, pk_c, pk_d, pk_e];
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 delta_root = SvmSnapshot::empty(svm.get_sysvar::<Clock>());
svm.set_account(pk_a, make_account(100, &[0x01])).unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_a);
let delta_1 = SvmSnapshot::take_delta(&svm, &delta_root, &dirty);
assert_eq!(delta_1.account_count(), 1);
svm.set_account(pk_b, make_account(200, &[0x02])).unwrap();
dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_b);
let delta_2 = SvmSnapshot::take_delta(&svm, &delta_1, &dirty);
assert_eq!(delta_2.account_count(), 2);
assert!(Arc::ptr_eq(
&delta_2.accounts()[&pk_a],
&delta_1.accounts()[&pk_a]
));
svm.set_account(pk_c, make_account(300, &[0x03])).unwrap();
dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_c);
let delta_3 = SvmSnapshot::take_delta(&svm, &delta_2, &dirty);
assert_eq!(delta_3.account_count(), 3);
assert!(Arc::ptr_eq(
&delta_3.accounts()[&pk_a],
&delta_1.accounts()[&pk_a]
));
assert!(Arc::ptr_eq(
&delta_3.accounts()[&pk_b],
&delta_2.accounts()[&pk_b]
));
svm.set_account(pk_a, make_account(400, &[0x04])).unwrap();
dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_a);
let delta_4 = SvmSnapshot::take_delta(&svm, &delta_3, &dirty);
assert_eq!(delta_4.account_count(), 3);
assert!(!Arc::ptr_eq(
&delta_4.accounts()[&pk_a],
&delta_3.accounts()[&pk_a]
));
assert!(Arc::ptr_eq(
&delta_4.accounts()[&pk_b],
&delta_2.accounts()[&pk_b]
));
assert!(Arc::ptr_eq(
&delta_4.accounts()[&pk_c],
&delta_3.accounts()[&pk_c]
));
svm.set_account(pk_d, make_account(500, &[0x05])).unwrap();
svm.set_account(pk_e, make_account(600, &[0x06])).unwrap();
dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_d);
dirty.mark_account_dirty(&pk_e);
let delta_5 = SvmSnapshot::take_delta(&svm, &delta_4, &dirty);
assert_eq!(delta_5.account_count(), 5);
for pk in &pks {
svm.set_account(*pk, make_account(1, &[0])).unwrap();
}
delta_2.restore_full(&mut svm);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
delta_5.restore_full(&mut svm);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 400); assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300);
assert_eq!(svm.get_account(&pk_d).unwrap().lamports, 500);
assert_eq!(svm.get_account(&pk_e).unwrap().lamports, 600);
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
for pk in delta_5.accounts().keys() {
divergent.insert(*pk);
}
initial.restore_selective(&mut svm, &divergent, &delta_2);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 30); assert_eq!(svm.get_account(&pk_d).unwrap().lamports, 40); assert_eq!(svm.get_account(&pk_e).unwrap().lamports, 50); }
#[test]
fn test_deep_chain_selective_from_between_levels() {
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();
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();
svm.set_account(pk_d, make_account(40, &[4])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b, pk_c, pk_d].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let delta_root = SvmSnapshot::empty(svm.get_sysvar::<Clock>());
svm.set_account(pk_a, make_account(100, &[0x11])).unwrap();
let mut d = DirtyTracker::new();
d.mark_account_dirty(&pk_a);
let delta_1 = SvmSnapshot::take_delta(&svm, &delta_root, &d);
svm.set_account(pk_b, make_account(200, &[0x22])).unwrap();
d = DirtyTracker::new();
d.mark_account_dirty(&pk_b);
let delta_2 = SvmSnapshot::take_delta(&svm, &delta_1, &d);
svm.set_account(pk_c, make_account(300, &[0x33])).unwrap();
d = DirtyTracker::new();
d.mark_account_dirty(&pk_c);
let delta_3 = SvmSnapshot::take_delta(&svm, &delta_2, &d);
svm.set_account(pk_a, make_account(400, &[0x44])).unwrap();
d = DirtyTracker::new();
d.mark_account_dirty(&pk_a);
let delta_4 = SvmSnapshot::take_delta(&svm, &delta_3, &d);
svm.set_account(pk_d, make_account(500, &[0x55])).unwrap();
d = DirtyTracker::new();
d.mark_account_dirty(&pk_d);
let delta_5 = SvmSnapshot::take_delta(&svm, &delta_4, &d);
initial.restore_full(&mut svm);
let mut divergent_keys: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent_keys, &delta_3);
divergent_keys.clear();
divergent_keys.extend(delta_3.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300);
assert_eq!(svm.get_account(&pk_d).unwrap().lamports, 40);
svm.set_account(pk_a, make_account(150, &[0xAA])).unwrap();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
prev_exec_dirty.insert(pk_a);
divergent_keys.extend(prev_exec_dirty.iter().copied());
let count = initial.restore_selective_from(
&mut svm,
&divergent_keys,
&delta_3,
&delta_5,
&prev_exec_dirty,
);
assert_eq!(count, 2);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 400); assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200); assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 300); assert_eq!(svm.get_account(&pk_d).unwrap().lamports, 500); }
#[test]
fn test_full_mini_fuzzing_loop() {
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 initial = SvmSnapshot::take(&svm, &tracked);
let state_0 = SvmSnapshot::empty(initial.clock().clone());
let mut s1_accounts = FastHashMap::default();
s1_accounts.insert(pk_a, Arc::new(make_account(100, &[0x11])));
let state_1 = SvmSnapshot {
accounts: s1_accounts,
sysvars: make_test_sysvars(10),
};
let mut s2_accounts = FastHashMap::default();
s2_accounts.insert(pk_a, state_1.accounts()[&pk_a].clone()); s2_accounts.insert(pk_b, Arc::new(make_account(200, &[0x22])));
let state_2 = SvmSnapshot {
accounts: s2_accounts,
sysvars: make_test_sysvars(20),
};
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 delta = &state_1;
assert!(prev_delta_arc.is_none());
initial.restore_selective(&mut svm, &divergent_keys, delta);
divergent_keys.clear();
divergent_keys.extend(delta.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 20);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 30);
svm.set_account(pk_a, make_account(150, &[0xAA])).unwrap();
let action_succeeded = true;
prev_exec_dirty.clear();
if action_succeeded {
prev_exec_dirty.insert(pk_a);
divergent_keys.extend(prev_exec_dirty.iter().copied());
}
prev_delta_arc = Some(delta.clone());
}
{
let delta = &state_1;
let prev = prev_delta_arc.as_ref().unwrap();
let count = initial.restore_selective_from(
&mut svm,
&divergent_keys,
prev,
delta,
&prev_exec_dirty,
);
divergent_keys.clear();
divergent_keys.extend(delta.accounts().keys().copied());
assert_eq!(count, 1); assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 20);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 30);
svm.set_account(pk_b, make_account(25, &[0xBB])).unwrap();
svm.set_account(pk_c, make_account(35, &[0xCC])).unwrap();
let action_succeeded = true;
prev_exec_dirty.clear();
if action_succeeded {
prev_exec_dirty.insert(pk_b);
prev_exec_dirty.insert(pk_c);
divergent_keys.extend(prev_exec_dirty.iter().copied());
}
prev_delta_arc = Some(delta.clone());
}
{
let delta = &state_2;
let prev = prev_delta_arc.as_ref().unwrap();
initial.restore_selective_from(&mut svm, &divergent_keys, prev, delta, &prev_exec_dirty);
divergent_keys.clear();
divergent_keys.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);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 30);
svm.set_account(pk_a, make_account(999, &[0xFF])).unwrap();
let action_succeeded = false;
prev_exec_dirty.clear();
if action_succeeded {
unreachable!();
}
prev_delta_arc = Some(delta.clone());
}
{
let delta = &state_0;
let prev = prev_delta_arc.as_ref().unwrap();
initial.restore_selective_from(&mut svm, &divergent_keys, prev, delta, &prev_exec_dirty);
divergent_keys.clear();
divergent_keys.extend(delta.accounts().keys().copied());
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, 30); }
}
#[test]
fn test_stateful_setup_captures_initial_state() {
let mut svm = LiteSVM::new();
let user = Pubkey::new_unique();
let vault = Pubkey::new_unique();
let mint = Pubkey::new_unique();
let reserve = Pubkey::new_unique();
svm.set_account(user, make_account(5_000_000, &[0; 165]))
.unwrap();
svm.set_account(vault, make_account(1_000, &[0; 64]))
.unwrap();
svm.set_account(mint, make_account(1_461_600, &[0; 82]))
.unwrap();
svm.set_account(reserve, make_account(2_000_000, &[0xAB; 256]))
.unwrap();
let tracked: HashSet<Pubkey> = [user, vault, mint, reserve].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
assert_eq!(initial.account_count(), 4);
assert_eq!(initial.accounts()[&user].lamports, 5_000_000);
assert_eq!(initial.accounts()[&user].data.len(), 165);
assert_eq!(initial.accounts()[&vault].lamports, 1_000);
assert_eq!(initial.accounts()[&vault].data.len(), 64);
assert_eq!(initial.accounts()[&mint].lamports, 1_461_600);
assert_eq!(initial.accounts()[&mint].data.len(), 82);
assert_eq!(initial.accounts()[&reserve].lamports, 2_000_000);
assert_eq!(initial.accounts()[&reserve].data.len(), 256);
}
#[test]
fn test_stateful_setup_empty_delta_represents_initial() {
let mut svm = LiteSVM::new();
let pk_a = Pubkey::new_unique();
let pk_b = Pubkey::new_unique();
svm.set_account(pk_a, make_account(100, &[1, 2, 3]))
.unwrap();
svm.set_account(pk_b, make_account(200, &[4, 5])).unwrap();
let tracked: HashSet<Pubkey> = [pk_a, pk_b].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
svm.set_account(pk_a, make_account(999, &[0xFF])).unwrap();
svm.set_account(pk_b, make_account(0, &[])).unwrap();
let empty_delta = SvmSnapshot::empty(initial.clock().clone());
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.insert(pk_a);
divergent.insert(pk_b);
initial.restore_selective(&mut svm, &divergent, &empty_delta);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_a).unwrap().data, vec![1, 2, 3]);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_b).unwrap().data, vec![4, 5]);
}
#[test]
fn test_stateful_pick_restores_delta_accounts() {
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 initial = SvmSnapshot::take(&svm, &tracked);
let mut delta_accts = FastHashMap::default();
delta_accts.insert(pk_a, Arc::new(make_account(100, &[0xAA])));
delta_accts.insert(pk_b, Arc::new(make_account(200, &[0xBB])));
let delta = SvmSnapshot {
accounts: delta_accts,
sysvars: initial.sysvars.clone(),
};
svm.set_account(pk_a, make_account(0, &[])).unwrap();
svm.set_account(pk_b, make_account(0, &[])).unwrap();
svm.set_account(pk_c, make_account(0, &[])).unwrap();
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.insert(pk_a);
divergent.insert(pk_b);
divergent.insert(pk_c);
initial.restore_selective(&mut svm, &divergent, &delta);
assert_eq!(svm.get_account(&pk_a).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_a).unwrap().data, vec![0xAA]);
assert_eq!(svm.get_account(&pk_b).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_b).unwrap().data, vec![0xBB]);
assert_eq!(svm.get_account(&pk_c).unwrap().lamports, 30);
assert_eq!(svm.get_account(&pk_c).unwrap().data, vec![3]);
}
#[test]
fn test_stateful_pick_restores_delta_with_tombstone() {
let mut svm = LiteSVM::new();
let pk_alive = Pubkey::new_unique();
let pk_deleted = Pubkey::new_unique();
svm.set_account(pk_alive, make_account(100, &[1])).unwrap();
svm.set_account(pk_deleted, make_account(200, &[2]))
.unwrap();
let tracked: HashSet<Pubkey> = [pk_alive, pk_deleted].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut delta_accts = FastHashMap::default();
delta_accts.insert(pk_alive, Arc::new(make_account(150, &[0xAA])));
delta_accts.insert(
pk_deleted,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
let delta = SvmSnapshot {
accounts: delta_accts,
sysvars: initial.sysvars.clone(),
};
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.insert(pk_alive);
divergent.insert(pk_deleted);
initial.restore_selective(&mut svm, &divergent, &delta);
assert_eq!(svm.get_account(&pk_alive).unwrap().lamports, 150);
assert!(svm.get_account(&pk_deleted).is_none());
}
#[test]
fn test_stateful_dirty_tracking_created_account() {
let mut svm = LiteSVM::new();
let pk_initial = Pubkey::new_unique();
svm.set_account(pk_initial, make_account(100, &[1]))
.unwrap();
let tracked: HashSet<Pubkey> = [pk_initial].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let pk_created = Pubkey::new_unique();
svm.set_account(pk_created, make_account(500, &[9, 9, 9]))
.unwrap();
assert!(svm.get_account(&pk_created).is_some());
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_created);
let parent_delta = SvmSnapshot::empty(initial.clock().clone());
let delta = SvmSnapshot::take_delta(&svm, &parent_delta, &dirty);
assert!(delta.accounts().contains_key(&pk_created));
assert_eq!(delta.accounts()[&pk_created].lamports, 500);
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.extend(delta.accounts().keys().copied());
let empty_delta = SvmSnapshot::empty(initial.clock().clone());
initial.restore_selective(&mut svm, &divergent, &empty_delta);
assert!(svm.get_account(&pk_created).is_none());
assert_eq!(svm.get_account(&pk_initial).unwrap().lamports, 100);
}
#[test]
fn test_stateful_dirty_tracking_deleted_account() {
let mut svm = LiteSVM::new();
let pk_will_delete = Pubkey::new_unique();
let pk_survives = Pubkey::new_unique();
svm.set_account(pk_will_delete, make_account(100, &[1, 2]))
.unwrap();
svm.set_account(pk_survives, make_account(200, &[3, 4]))
.unwrap();
let tracked: HashSet<Pubkey> = [pk_will_delete, pk_survives].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
svm.set_account(
pk_will_delete,
Account {
lamports: 0,
..Default::default()
},
)
.unwrap();
assert!(svm.get_account(&pk_will_delete).is_none());
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_will_delete);
let parent_delta = SvmSnapshot::empty(initial.clock().clone());
let delta = SvmSnapshot::take_delta(&svm, &parent_delta, &dirty);
assert!(delta.accounts().contains_key(&pk_will_delete));
assert_eq!(delta.accounts()[&pk_will_delete].lamports, 0);
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.extend(delta.accounts().keys().copied());
let empty_delta = SvmSnapshot::empty(initial.clock().clone());
initial.restore_selective(&mut svm, &divergent, &empty_delta);
let restored = svm.get_account(&pk_will_delete).unwrap();
assert_eq!(restored.lamports, 100);
assert_eq!(restored.data, vec![1, 2]);
}
#[test]
fn test_stateful_dirty_tracking_modified_account() {
let mut svm = LiteSVM::new();
let pk = Pubkey::new_unique();
svm.set_account(pk, make_account(100, &[0; 32])).unwrap();
let tracked: HashSet<Pubkey> = [pk].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
svm.set_account(pk, make_account(999, &[0xFF; 32])).unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk);
let parent_delta = SvmSnapshot::empty(initial.clock().clone());
let delta = SvmSnapshot::take_delta(&svm, &parent_delta, &dirty);
assert_eq!(delta.accounts()[&pk].lamports, 999);
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.insert(pk);
let empty_delta = SvmSnapshot::empty(initial.clock().clone());
initial.restore_selective(&mut svm, &divergent, &empty_delta);
assert_eq!(svm.get_account(&pk).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk).unwrap().data, vec![0; 32]);
}
#[test]
fn test_stateful_dirty_tracking_create_and_delete_same_iteration() {
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_ephemeral = Pubkey::new_unique();
svm.set_account(pk_ephemeral, make_account(500, &[9]))
.unwrap();
svm.set_account(
pk_ephemeral,
Account {
lamports: 0,
..Default::default()
},
)
.unwrap();
let mut dirty = DirtyTracker::new();
dirty.mark_account_dirty(&pk_ephemeral);
let parent_delta = SvmSnapshot::empty(initial.clock().clone());
let delta = SvmSnapshot::take_delta(&svm, &parent_delta, &dirty);
assert_eq!(delta.accounts()[&pk_ephemeral].lamports, 0);
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.insert(pk_ephemeral);
let empty_delta = SvmSnapshot::empty(initial.clock().clone());
initial.restore_selective(&mut svm, &divergent, &empty_delta);
assert!(svm.get_account(&pk_ephemeral).is_none());
}
#[test]
fn test_stateful_tree_branching_all_nodes_reachable() {
let mut svm = LiteSVM::new();
let pk_x = Pubkey::new_unique();
let pk_y = Pubkey::new_unique();
svm.set_account(pk_x, make_account(10, &[1])).unwrap();
svm.set_account(pk_y, make_account(20, &[2])).unwrap();
let tracked: HashSet<Pubkey> = [pk_x, pk_y].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let delta_a = SvmSnapshot::empty(initial.clock().clone());
let pk_new = Pubkey::new_unique();
svm.set_account(pk_x, make_account(100, &[0xAA])).unwrap();
svm.set_account(pk_new, make_account(500, &[0xBB])).unwrap();
let mut dirty_b = DirtyTracker::new();
dirty_b.mark_account_dirty(&pk_x);
dirty_b.mark_account_dirty(&pk_new);
let delta_b = SvmSnapshot::take_delta(&svm, &delta_a, &dirty_b);
svm.set_account(pk_x, make_account(200, &[0xCC])).unwrap();
svm.set_account(pk_y, make_account(300, &[0xDD])).unwrap();
let mut dirty_c = DirtyTracker::new();
dirty_c.mark_account_dirty(&pk_x);
dirty_c.mark_account_dirty(&pk_y);
let delta_c = SvmSnapshot::take_delta(&svm, &delta_b, &dirty_c);
initial.restore_full(&mut svm);
for (pk, acct) in delta_b.accounts() {
let _ = svm.set_account(*pk, (**acct).clone());
}
svm.set_account(
pk_new,
Account {
lamports: 0,
..Default::default()
},
)
.unwrap();
svm.set_account(pk_y, make_account(400, &[0xEE])).unwrap();
let mut dirty_d = DirtyTracker::new();
dirty_d.mark_account_dirty(&pk_new);
dirty_d.mark_account_dirty(&pk_y);
let delta_d = SvmSnapshot::take_delta(&svm, &delta_b, &dirty_d);
initial.restore_full(&mut svm);
assert_eq!(delta_b.accounts()[&pk_x].lamports, 100);
assert_eq!(delta_b.accounts()[&pk_new].lamports, 500);
assert!(!delta_b.accounts().contains_key(&pk_y));
assert_eq!(delta_c.accounts()[&pk_x].lamports, 200);
assert_eq!(delta_c.accounts()[&pk_new].lamports, 500); assert_eq!(delta_c.accounts()[&pk_y].lamports, 300);
assert_eq!(delta_d.accounts()[&pk_x].lamports, 100); assert_eq!(delta_d.accounts()[&pk_new].lamports, 0); assert_eq!(delta_d.accounts()[&pk_y].lamports, 400);
let all_keys: Vec<Pubkey> = vec![pk_x, pk_y, pk_new];
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
divergent.clear();
for &pk in &all_keys {
divergent.insert(pk);
}
initial.restore_selective(&mut svm, &divergent, &delta_a);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 10);
assert_eq!(svm.get_account(&pk_y).unwrap().lamports, 20);
assert!(svm.get_account(&pk_new).is_none());
divergent.clear();
for &pk in &all_keys {
divergent.insert(pk);
}
initial.restore_selective(&mut svm, &divergent, &delta_b);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_y).unwrap().lamports, 20); assert_eq!(svm.get_account(&pk_new).unwrap().lamports, 500);
divergent.clear();
for &pk in &all_keys {
divergent.insert(pk);
}
initial.restore_selective(&mut svm, &divergent, &delta_c);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 200);
assert_eq!(svm.get_account(&pk_y).unwrap().lamports, 300);
assert_eq!(svm.get_account(&pk_new).unwrap().lamports, 500);
divergent.clear();
for &pk in &all_keys {
divergent.insert(pk);
}
initial.restore_selective(&mut svm, &divergent, &delta_d);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100); assert_eq!(svm.get_account(&pk_y).unwrap().lamports, 400);
assert!(svm.get_account(&pk_new).is_none()); }
#[test]
fn test_stateful_tree_arc_sharing_between_siblings() {
let mut svm = LiteSVM::new();
let pk_x = Pubkey::new_unique();
let pk_y = Pubkey::new_unique();
svm.set_account(pk_x, make_account(10, &[1])).unwrap();
svm.set_account(pk_y, make_account(20, &[2])).unwrap();
let tracked: HashSet<Pubkey> = [pk_x, pk_y].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let delta_root = SvmSnapshot::empty(initial.clock().clone());
svm.set_account(pk_x, make_account(100, &[0xAA])).unwrap();
let mut dirty_b = DirtyTracker::new();
dirty_b.mark_account_dirty(&pk_x);
let delta_b = SvmSnapshot::take_delta(&svm, &delta_root, &dirty_b);
svm.set_account(pk_y, make_account(300, &[0xCC])).unwrap();
let mut dirty_c = DirtyTracker::new();
dirty_c.mark_account_dirty(&pk_y);
let delta_c = SvmSnapshot::take_delta(&svm, &delta_b, &dirty_c);
svm.set_account(pk_y, make_account(400, &[0xDD])).unwrap();
let mut dirty_d = DirtyTracker::new();
dirty_d.mark_account_dirty(&pk_y);
let delta_d = SvmSnapshot::take_delta(&svm, &delta_b, &dirty_d);
assert!(Arc::ptr_eq(
&delta_c.accounts()[&pk_x],
&delta_d.accounts()[&pk_x],
));
initial.restore_full(&mut svm);
let mut divergent: FastHashSet<Pubkey> = FastHashSet::default();
initial.restore_selective(&mut svm, &divergent, &delta_c);
divergent.extend(delta_c.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100);
assert_eq!(svm.get_account(&pk_y).unwrap().lamports, 300);
svm.set_account(pk_y, make_account(350, &[0x99])).unwrap();
let mut prev_exec_dirty: FastHashSet<Pubkey> = FastHashSet::default();
prev_exec_dirty.insert(pk_y);
divergent.extend(prev_exec_dirty.iter().copied());
let count =
initial.restore_selective_from(&mut svm, &divergent, &delta_c, &delta_d, &prev_exec_dirty);
assert_eq!(count, 1); assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100); assert_eq!(svm.get_account(&pk_y).unwrap().lamports, 400); }
#[test]
fn test_stateful_no_carryover_created_account() {
let mut svm = LiteSVM::new();
let pk_x = Pubkey::new_unique();
svm.set_account(pk_x, make_account(10, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk_x].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let _delta_a = SvmSnapshot::empty(initial.clock().clone());
let mut b_accounts = FastHashMap::default();
b_accounts.insert(pk_x, Arc::new(make_account(100, &[0xBB])));
let delta_b = SvmSnapshot {
accounts: b_accounts,
sysvars: initial.sysvars.clone(),
};
let mut d_accounts = FastHashMap::default();
d_accounts.insert(pk_x, delta_b.accounts()[&pk_x].clone());
let delta_d = SvmSnapshot {
accounts: d_accounts,
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_d,
None,
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta_d.accounts().keys().copied());
let pk_new = Pubkey::new_unique();
svm.set_account(pk_new, make_account(999, &[0xFF; 64]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk_x); prev_exec_dirty.insert(pk_new); divergent.extend(prev_exec_dirty.iter().copied());
let prev_delta = Some(delta_d.clone());
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta_b,
prev_delta.as_ref(),
&prev_exec_dirty,
);
assert!(
svm.get_account(&pk_new).is_none(),
"CARRYOVER BUG: pk_new from iteration 1 leaked into iteration 2"
);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100);
}
#[test]
fn test_stateful_no_carryover_deleted_account() {
let mut svm = LiteSVM::new();
let pk_x = Pubkey::new_unique();
let pk_y = Pubkey::new_unique();
svm.set_account(pk_x, make_account(10, &[1])).unwrap();
svm.set_account(pk_y, make_account(20, &[2])).unwrap();
let tracked: HashSet<Pubkey> = [pk_x, pk_y].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut b_accounts = FastHashMap::default();
b_accounts.insert(pk_x, Arc::new(make_account(100, &[0xAA])));
b_accounts.insert(pk_y, Arc::new(make_account(200, &[0xBB])));
let delta_b = SvmSnapshot {
accounts: b_accounts,
sysvars: initial.sysvars.clone(),
};
let mut c_accounts = FastHashMap::default();
c_accounts.insert(pk_x, delta_b.accounts()[&pk_x].clone());
c_accounts.insert(
pk_y,
Arc::new(Account {
lamports: 0,
..Default::default()
}),
);
let delta_c = SvmSnapshot {
accounts: c_accounts,
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_c,
None,
&prev_exec_dirty,
);
divergent.extend(delta_c.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100);
assert!(svm.get_account(&pk_y).is_none());
svm.set_account(pk_x, make_account(150, &[0xCC])).unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk_x);
divergent.extend(prev_exec_dirty.iter().copied());
let prev_delta = delta_c.clone();
initial.restore_selective_from(
&mut svm,
&divergent,
&prev_delta,
&delta_b,
&prev_exec_dirty,
);
assert_eq!(
svm.get_account(&pk_y).unwrap().lamports,
200,
"CARRYOVER BUG: pk_y deletion from C leaked into B's restoration"
);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100);
}
#[test]
fn test_stateful_no_carryover_modified_account() {
let mut svm = LiteSVM::new();
let pk_x = Pubkey::new_unique();
svm.set_account(pk_x, make_account(10, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk_x].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut s1_accts = FastHashMap::default();
s1_accts.insert(pk_x, 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_x, 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();
initial.restore_selective(&mut svm, &divergent, &delta_1);
divergent.extend(delta_1.accounts().keys().copied());
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 100);
svm.set_account(pk_x, make_account(77777, &[0xFF; 100]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk_x);
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_x).unwrap().lamports,
200,
"CARRYOVER BUG: execution-modified pk_x leaked into next iteration"
);
assert_eq!(svm.get_account(&pk_x).unwrap().data, vec![0xBB]);
}
#[test]
fn test_stateful_no_carryover_failed_action_with_side_effects() {
let mut svm = LiteSVM::new();
let pk_x = Pubkey::new_unique();
svm.set_account(pk_x, make_account(10, &[1])).unwrap();
let tracked: HashSet<Pubkey> = [pk_x].into_iter().collect();
let initial = SvmSnapshot::take(&svm, &tracked);
let mut s1_accts = FastHashMap::default();
s1_accts.insert(pk_x, Arc::new(make_account(100, &[0xAA])));
let delta_1 = SvmSnapshot {
accounts: s1_accts,
sysvars: initial.sysvars.clone(),
};
let delta_initial = 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.extend(delta_1.accounts().keys().copied());
let pk_new = Pubkey::new_unique();
svm.set_account(pk_new, make_account(999, &[0xBB; 32]))
.unwrap();
let _action_succeeded = false;
prev_exec_dirty.clear();
prev_exec_dirty.insert(pk_x);
prev_exec_dirty.insert(pk_new);
divergent.extend(prev_exec_dirty.iter().copied());
let prev_delta: Option<&SvmSnapshot> = None;
simulate_restore(
&initial,
&mut svm,
&divergent,
&delta_initial,
prev_delta,
&prev_exec_dirty,
);
assert!(
svm.get_account(&pk_new).is_none(),
"CARRYOVER BUG: pk_new from failed action leaked into next iteration"
);
assert_eq!(svm.get_account(&pk_x).unwrap().lamports, 10); }
#[test]
fn test_stateful_no_carryover_rapid_state_switching() {
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 states: Vec<SvmSnapshot> = vec![
SvmSnapshot::empty(initial.clock().clone()), SvmSnapshot {
accounts: {
let mut m = FastHashMap::default();
m.insert(pk_a, Arc::new(make_account(10, &[0x10])));
m.insert(pk_b, Arc::new(make_account(20, &[0x20])));
m
},
sysvars: initial.sysvars.clone(),
},
SvmSnapshot {
accounts: {
let mut m = FastHashMap::default();
m.insert(pk_b, Arc::new(make_account(30, &[0x30])));
m.insert(pk_c, Arc::new(make_account(40, &[0x40])));
m
},
sysvars: initial.sysvars.clone(),
},
SvmSnapshot {
accounts: {
let mut m = FastHashMap::default();
m.insert(pk_a, Arc::new(make_account(50, &[0x50])));
m.insert(pk_c, Arc::new(make_account(60, &[0x60])));
m
},
sysvars: initial.sysvars.clone(),
},
];
let expected: Vec<(u64, u64, u64)> = vec![
(1, 2, 3), (10, 20, 3), (1, 30, 40), (50, 2, 60), ];
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 = [1, 3, 0, 2, 1, 3, 2, 0, 3, 1];
for (iter, &state_idx) in sequence.iter().enumerate() {
let delta = &states[state_idx];
let (ea, eb, ec) = expected[state_idx];
simulate_restore(
&initial,
&mut svm,
&divergent,
delta,
prev_delta.as_ref(),
&prev_exec_dirty,
);
divergent.clear();
divergent.extend(delta.accounts().keys().copied());
let got_a = svm.get_account(&pk_a).map_or(0, |a| a.lamports);
let got_b = svm.get_account(&pk_b).map_or(0, |a| a.lamports);
let got_c = svm.get_account(&pk_c).map_or(0, |a| a.lamports);
assert_eq!(
got_a, ea,
"iter {} state {}: pk_a expected {} got {}",
iter, state_idx, ea, got_a
);
assert_eq!(
got_b, eb,
"iter {} state {}: pk_b expected {} got {}",
iter, state_idx, eb, got_b
);
assert_eq!(
got_c, ec,
"iter {} state {}: pk_c expected {} got {}",
iter, state_idx, ec, got_c
);
let dirty_pk = [pk_a, pk_b, pk_c][iter % 3];
svm.set_account(dirty_pk, make_account(99999, &[0xDE, 0xAD]))
.unwrap();
prev_exec_dirty.clear();
prev_exec_dirty.insert(dirty_pk);
divergent.extend(prev_exec_dirty.iter().copied());
prev_delta = Some(delta.clone());
}
}