use proptest::prelude::*;
use ubt::{
get_basic_data_key, get_code_chunk_key, get_storage_slot_key, Address, Blake3Hasher, Hasher,
Stem, StreamingTreeBuilder, TreeKey, UnifiedBinaryTree, B256,
};
fn arb_stem() -> impl Strategy<Value = Stem> {
prop::array::uniform31(any::<u8>()).prop_map(Stem::new)
}
fn arb_subindex() -> impl Strategy<Value = u8> {
any::<u8>()
}
fn arb_tree_key() -> impl Strategy<Value = TreeKey> {
(arb_stem(), arb_subindex()).prop_map(|(stem, subindex)| TreeKey::new(stem, subindex))
}
fn arb_value() -> impl Strategy<Value = B256> {
prop::array::uniform32(any::<u8>()).prop_map(B256::from)
}
fn arb_nonzero_value() -> impl Strategy<Value = B256> {
arb_value().prop_filter("value must be nonzero", |v| *v != B256::ZERO)
}
fn arb_key_value() -> impl Strategy<Value = (TreeKey, B256)> {
(arb_tree_key(), arb_nonzero_value())
}
fn arb_key_value_list(max_len: usize) -> impl Strategy<Value = Vec<(TreeKey, B256)>> {
prop::collection::vec(arb_key_value(), 0..max_len)
}
proptest! {
#[test]
fn prop_get_insert_same(key in arb_tree_key(), value in arb_nonzero_value()) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(key, value);
prop_assert_eq!(tree.get(&key), Some(value));
}
#[test]
fn prop_get_insert_other(
k1 in arb_tree_key(),
k2 in arb_tree_key(),
v1 in arb_nonzero_value(),
v2 in arb_nonzero_value()
) {
prop_assume!(k1 != k2);
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(k1, v1);
tree.insert(k2, v2);
prop_assert_eq!(tree.get(&k1), Some(v1));
prop_assert_eq!(tree.get(&k2), Some(v2));
}
#[test]
fn prop_insert_delete(key in arb_tree_key(), value in arb_nonzero_value()) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(key, value);
tree.delete(&key);
prop_assert_eq!(tree.get(&key), None);
}
#[test]
fn prop_insert_idempotent(key in arb_tree_key(), value in arb_nonzero_value()) {
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree1.insert(key, value);
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree2.insert(key, value);
tree2.insert(key, value);
prop_assert_eq!(tree1.root_hash().unwrap(), tree2.root_hash().unwrap());
}
#[test]
fn prop_empty_get(key in arb_tree_key()) {
let tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
prop_assert_eq!(tree.get(&key), None);
}
}
proptest! {
#[test]
fn prop_insert_preserves_other_stems(
k1 in arb_tree_key(),
k2 in arb_tree_key(),
v1 in arb_nonzero_value(),
v2 in arb_nonzero_value()
) {
prop_assume!(k1.stem != k2.stem);
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(k1, v1);
tree.insert(k2, v2);
prop_assert_eq!(tree.get(&k1), Some(v1));
}
#[test]
fn prop_stem_colocation(
stem in arb_stem(),
idx1 in arb_subindex(),
idx2 in arb_subindex(),
v1 in arb_nonzero_value(),
v2 in arb_nonzero_value()
) {
prop_assume!(idx1 != idx2);
let k1 = TreeKey::new(stem, idx1);
let k2 = TreeKey::new(stem, idx2);
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(k1, v1);
tree.insert(k2, v2);
prop_assert_eq!(tree.get(&k1), Some(v1));
prop_assert_eq!(tree.get(&k2), Some(v2));
prop_assert_eq!(tree.stem_count(), 1);
}
#[test]
fn prop_subindex_independence(
stem in arb_stem(),
idx1 in arb_subindex(),
idx2 in arb_subindex(),
v in arb_nonzero_value()
) {
prop_assume!(idx1 != idx2);
let k1 = TreeKey::new(stem, idx1);
let k2 = TreeKey::new(stem, idx2);
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(k1, v);
prop_assert_eq!(tree.get(&k2), None);
}
}
proptest! {
#[test]
fn prop_delete_idempotent(key in arb_tree_key(), value in arb_nonzero_value()) {
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree1.insert(key, value);
tree1.delete(&key);
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree2.insert(key, value);
tree2.delete(&key);
tree2.delete(&key);
prop_assert_eq!(tree1.root_hash().unwrap(), tree2.root_hash().unwrap());
}
#[test]
fn prop_zero_insert_is_delete(key in arb_tree_key(), value in arb_nonzero_value()) {
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree1.insert(key, value);
tree1.delete(&key);
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree2.insert(key, value);
tree2.insert(key, B256::ZERO);
prop_assert_eq!(tree1.get(&key), None);
prop_assert_eq!(tree2.get(&key), None);
}
#[test]
fn prop_delete_nonexistent_noop(
k1 in arb_tree_key(),
k2 in arb_tree_key(),
v in arb_nonzero_value()
) {
prop_assume!(k1 != k2);
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree1.insert(k1, v);
let hash1 = tree1.root_hash().unwrap();
tree1.delete(&k2); let hash2 = tree1.root_hash().unwrap();
prop_assert_eq!(hash1, hash2);
}
#[test]
fn prop_last_insert_wins(
key in arb_tree_key(),
v1 in arb_nonzero_value(),
v2 in arb_nonzero_value()
) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(key, v1);
tree.insert(key, v2);
prop_assert_eq!(tree.get(&key), Some(v2));
}
}
proptest! {
#[test]
fn prop_root_hash_deterministic(entries in arb_key_value_list(20)) {
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &entries {
tree1.insert(*k, *v);
tree2.insert(*k, *v);
}
prop_assert_eq!(tree1.root_hash().unwrap(), tree2.root_hash().unwrap());
}
#[test]
fn prop_empty_tree_zero_hash(_dummy: u8) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
prop_assert_eq!(tree.root_hash().unwrap(), B256::ZERO);
}
#[test]
fn prop_different_values_different_hashes(
key in arb_tree_key(),
v1 in arb_nonzero_value(),
v2 in arb_nonzero_value()
) {
prop_assume!(v1 != v2);
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree1.insert(key, v1);
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree2.insert(key, v2);
prop_assert_ne!(tree1.root_hash().unwrap(), tree2.root_hash().unwrap());
}
}
proptest! {
#[test]
fn prop_batch_operations_commute(
k1 in arb_tree_key(),
k2 in arb_tree_key(),
v1 in arb_nonzero_value(),
v2 in arb_nonzero_value()
) {
prop_assume!(k1 != k2);
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree1.insert(k1, v1);
tree1.insert(k2, v2);
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree2.insert(k2, v2);
tree2.insert(k1, v1);
prop_assert_eq!(tree1.root_hash().unwrap(), tree2.root_hash().unwrap());
}
#[test]
fn prop_insert_order_independence(entries in arb_key_value_list(10)) {
let mut deduped = std::collections::HashMap::new();
for (k, v) in &entries {
deduped.insert(*k, *v);
}
let unique: Vec<_> = deduped.into_iter().collect();
if unique.len() < 2 {
return Ok(());
}
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &unique {
tree1.insert(*k, *v);
}
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in unique.iter().rev() {
tree2.insert(*k, *v);
}
prop_assert_eq!(tree1.root_hash().unwrap(), tree2.root_hash().unwrap());
}
}
proptest! {
#[test]
fn prop_batch_equals_sequential(entries in arb_key_value_list(20)) {
let mut deduped = std::collections::HashMap::new();
for (k, v) in &entries {
deduped.insert(*k, *v);
}
let unique: Vec<_> = deduped.into_iter().collect();
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &unique {
tree1.insert(*k, *v);
}
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree2.insert_batch(unique.clone()).unwrap();
prop_assert_eq!(tree1.root_hash().unwrap(), tree2.root_hash().unwrap());
}
#[test]
fn prop_batch_then_individual(
batch_entries in arb_key_value_list(10),
k in arb_tree_key(),
v in arb_nonzero_value()
) {
let batch_keys: std::collections::HashSet<_> = batch_entries.iter().map(|(k, _)| k).collect();
prop_assume!(!batch_keys.contains(&k));
let mut deduped = std::collections::HashMap::new();
for (k, v) in &batch_entries {
deduped.insert(*k, *v);
}
let unique_batch: Vec<_> = deduped.into_iter().collect();
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree1.insert_batch(unique_batch.clone()).unwrap();
tree1.insert(k, v);
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (bk, bv) in &unique_batch {
tree2.insert(*bk, *bv);
}
tree2.insert(k, v);
prop_assert_eq!(tree1.root_hash().unwrap(), tree2.root_hash().unwrap());
}
}
proptest! {
#[test]
fn prop_streaming_equals_tree(entries in arb_key_value_list(20)) {
let mut deduped = std::collections::HashMap::new();
for (k, v) in &entries {
if *v != B256::ZERO {
deduped.insert(*k, *v);
}
}
let mut sorted: Vec<_> = deduped.into_iter().collect();
sorted.sort_by(|a, b| (a.0.stem, a.0.subindex).cmp(&(b.0.stem, b.0.subindex)));
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &sorted {
tree.insert(*k, *v);
}
let tree_hash = tree.root_hash().unwrap();
let builder: StreamingTreeBuilder<Blake3Hasher> = StreamingTreeBuilder::new();
let streaming_hash = builder.build_root_hash(sorted).unwrap();
prop_assert_eq!(tree_hash, streaming_hash);
}
#[test]
#[cfg(feature = "parallel")]
fn prop_streaming_parallel_equals_serial(entries in arb_key_value_list(20)) {
let mut deduped = std::collections::HashMap::new();
for (k, v) in &entries {
if *v != B256::ZERO {
deduped.insert(*k, *v);
}
}
let mut sorted: Vec<_> = deduped.into_iter().collect();
sorted.sort_by(|a, b| (a.0.stem, a.0.subindex).cmp(&(b.0.stem, b.0.subindex)));
let builder: StreamingTreeBuilder<Blake3Hasher> = StreamingTreeBuilder::new();
let serial_hash = builder.build_root_hash(sorted.clone()).unwrap();
let parallel_hash = builder.build_root_hash_parallel(sorted).unwrap();
prop_assert_eq!(serial_hash, parallel_hash);
}
}
proptest! {
#[test]
fn prop_incremental_equals_full(
initial in arb_key_value_list(10),
update_key in arb_tree_key(),
update_value in arb_nonzero_value()
) {
let initial_keys: std::collections::HashSet<_> = initial.iter().map(|(k, _)| k).collect();
prop_assume!(!initial_keys.contains(&update_key));
let mut deduped = std::collections::HashMap::new();
for (k, v) in &initial {
deduped.insert(*k, *v);
}
let unique_initial: Vec<_> = deduped.into_iter().collect();
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &unique_initial {
tree1.insert(*k, *v);
}
tree1.root_hash().unwrap();
tree1.insert(update_key, update_value);
let full_hash = tree1.root_hash().unwrap();
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &unique_initial {
tree2.insert(*k, *v);
}
tree2.root_hash().unwrap();
tree2.enable_incremental_mode();
tree2.root_hash().unwrap(); tree2.insert(update_key, update_value);
let incremental_hash = tree2.root_hash().unwrap();
prop_assert_eq!(full_hash, incremental_hash);
}
#[test]
fn prop_multiple_incremental_updates(
initial in arb_key_value_list(5),
updates in arb_key_value_list(5)
) {
let mut deduped = std::collections::HashMap::new();
for (k, v) in &initial {
deduped.insert(*k, *v);
}
let unique_initial: Vec<_> = deduped.into_iter().collect();
let mut tree1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &unique_initial {
tree1.insert(*k, *v);
}
for (k, v) in &updates {
tree1.insert(*k, *v);
}
let full_hash = tree1.root_hash().unwrap();
let mut tree2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &unique_initial {
tree2.insert(*k, *v);
}
tree2.root_hash().unwrap();
tree2.enable_incremental_mode();
tree2.root_hash().unwrap();
for (k, v) in &updates {
tree2.insert(*k, *v);
}
let incremental_hash = tree2.root_hash().unwrap();
prop_assert_eq!(full_hash, incremental_hash);
}
}
proptest! {
#[test]
fn prop_max_subindex_works(stem in arb_stem(), value in arb_nonzero_value()) {
let key = TreeKey::new(stem, 255);
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(key, value);
prop_assert_eq!(tree.get(&key), Some(value));
}
#[test]
fn prop_min_subindex_works(stem in arb_stem(), value in arb_nonzero_value()) {
let key = TreeKey::new(stem, 0);
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(key, value);
prop_assert_eq!(tree.get(&key), Some(value));
}
#[test]
fn prop_full_stem_256_values(stem in arb_stem()) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for idx in 0u8..=255 {
let key = TreeKey::new(stem, idx);
let mut value_bytes = [idx; 32];
value_bytes[31] = 0xFF; let value = B256::from(value_bytes);
tree.insert(key, value);
}
for idx in 0u8..=255 {
let key = TreeKey::new(stem, idx);
let mut expected_bytes = [idx; 32];
expected_bytes[31] = 0xFF;
let expected = B256::from(expected_bytes);
prop_assert_eq!(tree.get(&key), Some(expected));
}
prop_assert_eq!(tree.stem_count(), 1);
}
}
proptest! {
#[test]
fn prop_tree_key_roundtrip(key in arb_tree_key()) {
let bytes = key.to_bytes();
let recovered = TreeKey::from_bytes(bytes);
prop_assert_eq!(key, recovered);
}
#[test]
fn prop_b256_key_roundtrip(b256_key in prop::array::uniform32(any::<u8>()).prop_map(B256::from)) {
let tree_key = TreeKey::from_bytes(b256_key);
let recovered = tree_key.to_bytes();
prop_assert_eq!(b256_key, recovered);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn prop_large_tree_many_stems(entries in arb_key_value_list(100)) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &entries {
tree.insert(*k, *v);
}
for (k, v) in &entries {
let got = tree.get(k);
if *v != B256::ZERO {
prop_assert!(got.is_some());
}
}
let hash1 = tree.root_hash().unwrap();
let hash2 = tree.root_hash().unwrap();
prop_assert_eq!(hash1, hash2);
}
#[test]
fn prop_alternating_insert_delete(
entries in arb_key_value_list(20),
delete_indices in prop::collection::vec(0usize..20, 0..10)
) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &entries {
tree.insert(*k, *v);
}
for idx in &delete_indices {
if *idx < entries.len() {
tree.delete(&entries[*idx].0);
}
}
for (i, (k, v)) in entries.iter().enumerate() {
if delete_indices.contains(&i) {
prop_assert_eq!(tree.get(k), None);
} else if *v != B256::ZERO {
let _ = tree.get(k);
}
}
let h1 = tree.root_hash().unwrap();
let h2 = tree.root_hash().unwrap();
prop_assert_eq!(h1, h2);
}
}
fn arb_updates(max_len: usize) -> impl Strategy<Value = Vec<(TreeKey, B256)>> {
prop::collection::vec((arb_tree_key(), arb_value()), 0..max_len)
}
proptest! {
#[test]
fn prop_len_and_stem_count_match_logical_state(updates in arb_updates(50)) {
use std::collections::{HashMap, HashSet};
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let mut map: HashMap<TreeKey, B256> = HashMap::new();
for (k, v) in &updates {
tree.insert(*k, *v);
if *v == B256::ZERO {
map.remove(k);
} else {
map.insert(*k, *v);
}
}
prop_assert_eq!(tree.len(), map.len());
let logical_stems: HashSet<_> = map.keys().map(|k| k.stem).collect();
prop_assert_eq!(tree.stem_count(), logical_stems.len());
for (k, v) in &map {
prop_assert_eq!(tree.get(k), Some(*v));
}
}
#[test]
fn prop_delete_last_value_removes_stem(
stem in arb_stem(),
idx in arb_subindex(),
value in arb_nonzero_value()
) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let key = TreeKey::new(stem, idx);
tree.insert(key, value);
prop_assert_eq!(tree.stem_count(), 1);
prop_assert!(!tree.is_empty());
tree.delete(&key);
prop_assert_eq!(tree.get(&key), None);
prop_assert_eq!(tree.stem_count(), 0);
prop_assert!(tree.is_empty());
prop_assert_eq!(tree.root_hash().unwrap(), B256::ZERO);
}
#[test]
fn prop_b256_apis_equivalent(key_bytes in arb_value(), value in arb_value()) {
let key = TreeKey::from_bytes(key_bytes);
let mut t1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
t1.insert(key, value);
let v1 = t1.get(&key);
let mut t2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
t2.insert_b256(key_bytes, value);
let v2 = t2.get_by_b256(&key_bytes);
prop_assert_eq!(v1, v2);
prop_assert_eq!(t1.root_hash().unwrap(), t2.root_hash().unwrap());
}
#[test]
fn prop_insert_batch_with_zeros(entries in arb_updates(50)) {
let mut t1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &entries {
t1.insert(*k, *v);
}
let h1 = t1.root_hash().unwrap();
let mut t2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
t2.insert_batch(entries.clone()).unwrap();
let h2 = t2.root_hash().unwrap();
prop_assert_eq!(h1, h2);
}
#[test]
fn prop_root_hash_stable_without_mutation(entries in arb_key_value_list(50)) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &entries {
tree.insert(*k, *v);
}
let h1 = tree.root_hash().unwrap();
let h2 = tree.root_hash().unwrap();
let h3 = tree.root_hash().unwrap();
prop_assert_eq!(h1, h2);
prop_assert_eq!(h2, h3);
}
}
#[derive(Clone, Debug)]
enum Op {
Insert(TreeKey, B256),
Delete(TreeKey),
}
fn arb_ops(max_len: usize) -> impl Strategy<Value = Vec<Op>> {
prop::collection::vec(
prop_oneof![
(arb_tree_key(), arb_value()).prop_map(|(k, v)| Op::Insert(k, v)),
arb_tree_key().prop_map(Op::Delete),
],
0..max_len,
)
}
proptest! {
#[test]
fn prop_incremental_mode_arbitrary_ops(ops in arb_ops(30)) {
let mut full: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let mut inc: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let _ = full.root_hash().unwrap();
let _ = inc.root_hash().unwrap();
inc.enable_incremental_mode();
let _ = inc.root_hash().unwrap();
for op in ops {
match op {
Op::Insert(k, v) => {
full.insert(k, v);
inc.insert(k, v);
}
Op::Delete(k) => {
full.delete(&k);
inc.delete(&k);
}
}
}
prop_assert_eq!(full.root_hash().unwrap(), inc.root_hash().unwrap());
}
#[test]
fn prop_deep_colliding_stems(
base in prop::array::uniform31(any::<u8>()),
v1 in arb_nonzero_value(),
v2 in arb_nonzero_value()
) {
let s1 = Stem::new(base);
let mut other = base;
other[30] ^= 0x01; let s2 = Stem::new(other);
if s1 == s2 {
return Ok(());
}
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
tree.insert(TreeKey::new(s1, 0), v1);
tree.insert(TreeKey::new(s2, 0), v2);
let tree_root = tree.root_hash().unwrap();
let mut entries = vec![
(TreeKey::new(s1, 0), v1),
(TreeKey::new(s2, 0), v2),
];
entries.sort_by(|a, b| (a.0.stem, a.0.subindex).cmp(&(b.0.stem, b.0.subindex)));
let builder: StreamingTreeBuilder<Blake3Hasher> = StreamingTreeBuilder::new();
let streaming_root = builder.build_root_hash(entries).unwrap();
prop_assert_eq!(tree_root, streaming_root);
}
}
use ubt::{generate_stem_proof, Proof, ProofNode, StemNode};
proptest! {
#[test]
fn prop_stem_proof_reconstructs_subtree(
stem in arb_stem(),
values in prop::collection::btree_map(arb_subindex(), arb_nonzero_value(), 1..20),
query_idx in arb_subindex()
) {
let hasher = Blake3Hasher;
let mut node = StemNode::new(stem);
for (idx, v) in &values {
node.set_value(*idx, *v);
}
let (value, siblings) = generate_stem_proof(&node, query_idx, &hasher);
let mut current = match value {
Some(v) => hasher.hash_32(&v),
None => B256::ZERO,
};
let mut idx = query_idx as usize;
for sibling in siblings.iter() {
let is_right = (idx & 1) == 1;
current = if is_right {
hasher.hash_64(sibling, ¤t)
} else {
hasher.hash_64(¤t, sibling)
};
idx /= 2;
}
let computed_stem_hash = hasher.hash_stem_node(stem.as_bytes(), ¤t);
let expected_stem_hash = node.hash(&hasher);
prop_assert_eq!(computed_stem_hash, expected_stem_hash);
}
#[test]
fn prop_stem_proof_value_correct(
stem in arb_stem(),
idx in arb_subindex(),
value in arb_nonzero_value()
) {
let hasher = Blake3Hasher;
let mut node = StemNode::new(stem);
node.set_value(idx, value);
let (proof_val, _siblings) = generate_stem_proof(&node, idx, &hasher);
prop_assert_eq!(proof_val, Some(value));
let other_idx = idx.wrapping_add(1);
if node.get_value(other_idx).is_none() {
let (proof_val2, _) = generate_stem_proof(&node, other_idx, &hasher);
prop_assert_eq!(proof_val2, None);
}
}
#[test]
fn prop_extension_matching_stem_invalid(key in arb_tree_key()) {
let hasher = Blake3Hasher;
let bogus_hash = B256::repeat_byte(0xAA);
let proof = Proof::new(
key,
None,
vec![ProofNode::Extension {
stem: key.stem,
stem_hash: bogus_hash,
}],
);
let result = proof.compute_root(&hasher);
prop_assert!(result.is_err());
}
}
use ubt::{chunkify_code, dechunkify_code, BasicDataLeaf, CodeChunk};
proptest! {
#[test]
fn prop_basic_data_leaf_roundtrip(
nonce in any::<u64>(),
balance in any::<u128>(),
code_size in 0u32..0x00FF_FFFF ) {
let leaf = BasicDataLeaf::new(nonce, balance, code_size);
let decoded = BasicDataLeaf::decode(leaf.encode());
prop_assert_eq!(leaf, decoded);
}
#[test]
fn prop_code_chunking_consistent(code in prop::collection::vec(any::<u8>(), 0..500)) {
let chunks1 = chunkify_code(&code);
let chunks2 = chunkify_code(&code);
prop_assert_eq!(chunks1.len(), chunks2.len());
for (c1, c2) in chunks1.iter().zip(chunks2.iter()) {
prop_assert_eq!(c1.encode(), c2.encode());
}
}
}
proptest! {
#[test]
fn prop_code_chunk_roundtrip(code in prop::collection::vec(any::<u8>(), 0..1024)) {
let chunks = chunkify_code(&code);
let recovered = dechunkify_code(&chunks, code.len());
prop_assert_eq!(code, recovered);
}
#[test]
fn prop_code_chunk_encode_decode(
leading in 0u8..31,
data in prop::array::uniform31(any::<u8>())
) {
let chunk = CodeChunk::new(leading, data);
let decoded = CodeChunk::decode(chunk.encode());
prop_assert_eq!(chunk.leading_pushdata, decoded.leading_pushdata);
prop_assert_eq!(chunk.data, decoded.data);
}
#[test]
fn prop_incremental_enable_midstream(ops in arb_ops(40)) {
let mut full: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let mut inc: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let mid = ops.len() / 2;
for op in &ops[..mid] {
match op {
Op::Insert(k, v) => { full.insert(*k, *v); inc.insert(*k, *v); }
Op::Delete(k) => { full.delete(k); inc.delete(k); }
}
}
let _ = full.root_hash().unwrap();
let _ = inc.root_hash().unwrap();
inc.enable_incremental_mode();
let _ = inc.root_hash().unwrap();
for op in &ops[mid..] {
match op {
Op::Insert(k, v) => { full.insert(*k, *v); inc.insert(*k, *v); }
Op::Delete(k) => { full.delete(k); inc.delete(k); }
}
}
prop_assert_eq!(full.root_hash().unwrap(), inc.root_hash().unwrap());
}
#[test]
fn prop_incremental_with_batch(
initial in arb_key_value_list(20),
batch in arb_key_value_list(20)
) {
let mut full: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let mut inc: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &initial {
full.insert(*k, *v);
inc.insert(*k, *v);
}
let _ = full.root_hash().unwrap();
let _ = inc.root_hash().unwrap();
inc.enable_incremental_mode();
let _ = inc.root_hash().unwrap();
for (k, v) in &batch {
full.insert(*k, *v);
}
inc.insert_batch(batch.clone()).unwrap();
prop_assert_eq!(full.root_hash().unwrap(), inc.root_hash().unwrap());
}
#[test]
fn prop_incremental_root_hash_stable(entries in arb_key_value_list(50)) {
let mut tree: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &entries {
tree.insert(*k, *v);
}
tree.enable_incremental_mode();
let h1 = tree.root_hash().unwrap();
let h2 = tree.root_hash().unwrap();
let h3 = tree.root_hash().unwrap();
prop_assert_eq!(h1, h2);
prop_assert_eq!(h2, h3);
}
#[test]
fn prop_constructors_equivalent(entries in arb_key_value_list(100)) {
let mut t1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let mut t2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::with_capacity(1024);
let hasher = Blake3Hasher;
let mut t3: UnifiedBinaryTree<Blake3Hasher> =
UnifiedBinaryTree::with_hasher_and_capacity(hasher, 1024);
for (k, v) in &entries {
t1.insert(*k, *v);
t2.insert(*k, *v);
t3.insert(*k, *v);
}
prop_assert_eq!(t1.root_hash().unwrap(), t2.root_hash().unwrap());
prop_assert_eq!(t1.root_hash().unwrap(), t3.root_hash().unwrap());
}
#[test]
fn prop_insert_batch_with_progress_equivalent(entries in arb_key_value_list(100)) {
let mut t1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
t1.insert_batch(entries.clone()).unwrap();
let h1 = t1.root_hash().unwrap();
let mut t2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let mut progress_count = 0usize;
t2.insert_batch_with_progress(entries.clone(), |n| progress_count = n)
.unwrap();
let h2 = t2.root_hash().unwrap();
prop_assert_eq!(h1, h2);
prop_assert_eq!(progress_count, entries.len());
}
#[test]
fn prop_b256_apis_equivalent_many(entries in arb_key_value_list(100)) {
let mut t1: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
let mut t2: UnifiedBinaryTree<Blake3Hasher> = UnifiedBinaryTree::new();
for (k, v) in &entries {
t1.insert(*k, *v);
t2.insert_b256(k.to_bytes(), *v);
}
prop_assert_eq!(t1.root_hash().unwrap(), t2.root_hash().unwrap());
for (k, _) in &entries {
prop_assert_eq!(t1.get(k), t2.get_by_b256(&k.to_bytes()));
}
}
}
fn arb_address() -> impl Strategy<Value = Address> {
prop::array::uniform20(any::<u8>()).prop_map(Address::from)
}
fn arb_slot_bytes() -> impl Strategy<Value = [u8; 32]> {
prop::array::uniform32(any::<u8>())
}
proptest! {
#[test]
fn prop_storage_slot_key_never_panics(
addr in arb_address(),
slot in arb_slot_bytes()
) {
let key = get_storage_slot_key(&addr, &slot);
let is_header_slot = slot[..31].iter().all(|&b| b == 0) && slot[31] < 64;
if !is_header_slot {
prop_assert_eq!(key.subindex, slot[31]);
}
}
#[test]
fn prop_storage_slot_key_deterministic(
addr in arb_address(),
slot in arb_slot_bytes()
) {
let key1 = get_storage_slot_key(&addr, &slot);
let key2 = get_storage_slot_key(&addr, &slot);
prop_assert_eq!(key1, key2);
}
#[test]
fn prop_storage_slot_key_collision_resistant(
addr in arb_address(),
slot1 in arb_slot_bytes(),
slot2 in arb_slot_bytes()
) {
prop_assume!(slot1 != slot2);
let key1 = get_storage_slot_key(&addr, &slot1);
let key2 = get_storage_slot_key(&addr, &slot2);
prop_assert_ne!(key1, key2);
}
#[test]
fn prop_storage_slot_key_address_isolation(
addr1 in arb_address(),
addr2 in arb_address(),
slot in arb_slot_bytes()
) {
prop_assume!(addr1 != addr2);
let key1 = get_storage_slot_key(&addr1, &slot);
let key2 = get_storage_slot_key(&addr2, &slot);
prop_assert_ne!(key1, key2);
}
#[test]
fn prop_embedding_keys_never_panic(
addr in arb_address(),
chunk_num in 0u64..10000
) {
let _basic = get_basic_data_key(&addr);
let _code = get_code_chunk_key(&addr, chunk_num);
}
#[test]
fn prop_header_slots_share_stem(
addr in arb_address(),
slot1 in 0u64..64,
slot2 in 0u64..64
) {
let mut s1 = [0u8; 32];
s1[31] = slot1 as u8;
let mut s2 = [0u8; 32];
s2[31] = slot2 as u8;
let key1 = get_storage_slot_key(&addr, &s1);
let key2 = get_storage_slot_key(&addr, &s2);
let basic = get_basic_data_key(&addr);
prop_assert_eq!(key1.stem, basic.stem);
prop_assert_eq!(key2.stem, basic.stem);
}
#[test]
fn prop_main_storage_subindex(
addr in arb_address(),
slot in arb_slot_bytes()
) {
let key = get_storage_slot_key(&addr, &slot);
let is_header_slot = slot[..31].iter().all(|&b| b == 0) && slot[31] < 64;
if is_header_slot {
prop_assert_eq!(key.subindex, 64 + slot[31]);
} else {
prop_assert_eq!(key.subindex, slot[31]);
}
}
}