use alloc::{
collections::{BTreeMap, BTreeSet},
vec::Vec,
};
use proptest::prelude::*;
use super::MemoryStorage;
use crate::{
EMPTY_WORD, Felt, ONE, Word, ZERO,
merkle::smt::{LargeSmt, LeafIndex, SMT_DEPTH},
};
fn arb_felt() -> impl Strategy<Value = Felt> {
prop_oneof![any::<u64>().prop_map(Felt::new_unchecked), Just(ZERO), Just(ONE),]
}
fn arb_word() -> impl Strategy<Value = Word> {
prop::array::uniform4(arb_felt()).prop_map(Word::new)
}
fn arb_entries(min_size: usize, max_size: usize) -> impl Strategy<Value = Vec<(Word, Word)>> {
prop::collection::vec((arb_word(), arb_word()), min_size..=max_size).prop_map(move |entries| {
let mut used_indices = BTreeSet::new();
let mut used_keys = BTreeSet::new();
let mut result = Vec::new();
for (key, value) in entries {
let leaf_index = LeafIndex::<SMT_DEPTH>::from(key).position();
if used_indices.insert(leaf_index) && used_keys.insert(key) {
result.push((key, value));
}
}
result
})
}
fn arb_updates(
existing_entries: Vec<(Word, Word)>,
min_updates: usize,
max_updates: usize,
) -> impl Strategy<Value = Vec<(Word, Word)>> {
let existing_keys: Vec<Word> = existing_entries.iter().map(|(k, _)| *k).collect();
let has_existing = !existing_keys.is_empty();
prop::collection::vec(
(any::<bool>(), any::<bool>(), any::<usize>(), arb_word(), arb_word()),
min_updates..=max_updates,
)
.prop_map(move |raw_updates| {
let mut updates = BTreeMap::new();
for (is_new_key, is_deletion, idx_seed, rand_key, rand_val) in raw_updates {
let key = if has_existing && !is_new_key {
existing_keys[idx_seed % existing_keys.len()]
} else {
rand_key
};
let value = if is_deletion { EMPTY_WORD } else { rand_val };
updates.insert(key, value);
}
updates.into_iter().collect()
})
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn prop_insert_batch_matches_compute_apply(
(initial_entries, updates) in arb_entries(1, 100)
.prop_flat_map(|entries| {
let updates = arb_updates(entries.clone(), 1, 50);
(Just(entries), updates)
})
) {
run_insert_batch_matches_compute_apply(initial_entries, updates)?;
}
}
fn run_insert_batch_matches_compute_apply(
initial_entries: Vec<(Word, Word)>,
updates: Vec<(Word, Word)>,
) -> Result<(), TestCaseError> {
let storage1 = MemoryStorage::new();
let storage2 = MemoryStorage::new();
let mut tree1 = LargeSmt::with_entries(storage1, initial_entries.clone()).unwrap();
let mut tree2 = LargeSmt::with_entries(storage2, initial_entries.clone()).unwrap();
let root1 = tree1.root();
let root2 = tree2.root();
let mutations = tree1.compute_mutations(updates.clone()).unwrap();
tree1.apply_mutations(mutations).unwrap();
let new_root1 = tree1.root();
let new_root2 = tree2.insert_batch(updates.clone()).unwrap();
prop_assert_eq!(root1, root2, "Initial roots should match");
prop_assert_eq!(new_root1, new_root2, "Final roots should match");
for (key, _) in updates {
let val1 = tree1.get_value(&key);
let val2 = tree2.get_value(&key);
prop_assert_eq!(val1, val2, "Values should match for key {:?}", key);
}
for (key, _) in initial_entries {
let val1 = tree1.get_value(&key);
let val2 = tree2.get_value(&key);
prop_assert_eq!(val1, val2, "Values for initial keys should match");
}
prop_assert_eq!(tree1.num_leaves(), tree2.num_leaves());
prop_assert_eq!(tree1.num_entries(), tree2.num_entries());
Ok(())
}