Skip to content

Commit f4aee27

Browse files
authored
test: add more tests to the DB code we wrote (#144)
* test: add more tests to the DB code we wrote * chore: hide
1 parent 88b8d83 commit f4aee27

File tree

4 files changed

+345
-0
lines changed

4 files changed

+345
-0
lines changed

src/db/cow/mod.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,9 @@ impl<Db> DatabaseCommit for CacheOnWrite<Db> {
258258
#[cfg(test)]
259259
mod tests {
260260
use super::*;
261+
use crate::db::test_utils::DbTestExt;
261262
use revm::{
263+
bytecode::Bytecode,
262264
database::InMemoryDB,
263265
state::{AccountStatus, EvmStorageSlot},
264266
};
@@ -320,6 +322,100 @@ mod tests {
320322
assert_eq!(cow_db.storage(address, U256::from(0)).unwrap(), U256::from(42));
321323
assert_eq!(cow_db.storage(address, U256::from(1)).unwrap(), U256::from(42));
322324
}
325+
326+
#[test]
327+
fn test_set_balance() {
328+
let addr = Address::repeat_byte(1);
329+
let mut cow = CacheOnWrite::new(InMemoryDB::default());
330+
331+
cow.test_set_balance(addr, U256::from(1000));
332+
assert_eq!(cow.basic(addr).unwrap().unwrap().balance, U256::from(1000));
333+
334+
cow.test_increase_balance(addr, U256::from(500));
335+
assert_eq!(cow.basic(addr).unwrap().unwrap().balance, U256::from(1500));
336+
337+
cow.test_decrease_balance(addr, U256::from(200));
338+
assert_eq!(cow.basic(addr).unwrap().unwrap().balance, U256::from(1300));
339+
}
340+
341+
#[test]
342+
fn test_set_nonce() {
343+
let addr = Address::repeat_byte(2);
344+
let mut cow = CacheOnWrite::new(InMemoryDB::default());
345+
346+
cow.test_set_nonce(addr, 42);
347+
assert_eq!(cow.basic(addr).unwrap().unwrap().nonce, 42);
348+
}
349+
350+
#[test]
351+
fn test_set_storage() {
352+
let addr = Address::repeat_byte(3);
353+
let mut cow = CacheOnWrite::new(InMemoryDB::default());
354+
355+
cow.test_set_storage(addr, U256::from(10), U256::from(999));
356+
assert_eq!(cow.storage(addr, U256::from(10)).unwrap(), U256::from(999));
357+
358+
// Set another slot, verify first slot still readable
359+
cow.test_set_storage(addr, U256::from(20), U256::from(888));
360+
assert_eq!(cow.storage(addr, U256::from(10)).unwrap(), U256::from(999));
361+
assert_eq!(cow.storage(addr, U256::from(20)).unwrap(), U256::from(888));
362+
}
363+
364+
#[test]
365+
fn test_set_bytecode() {
366+
let addr = Address::repeat_byte(4);
367+
let mut cow = CacheOnWrite::new(InMemoryDB::default());
368+
let code =
369+
Bytecode::new_raw(alloy::primitives::Bytes::from_static(&[0x60, 0x00, 0x60, 0x00]));
370+
let code_hash = code.hash_slow();
371+
372+
cow.test_set_bytecode(addr, code.clone());
373+
374+
let info = cow.basic(addr).unwrap().unwrap();
375+
assert_eq!(info.code_hash, code_hash);
376+
assert_eq!(cow.code_by_hash(code_hash).unwrap(), code);
377+
}
378+
379+
#[test]
380+
fn test_cow_isolates_writes_from_inner() {
381+
let addr = Address::repeat_byte(5);
382+
let mut inner = InMemoryDB::default();
383+
inner.test_set_balance(addr, U256::from(100));
384+
385+
let mut cow = CacheOnWrite::new(inner);
386+
cow.test_set_balance(addr, U256::from(500));
387+
388+
// COW sees updated balance
389+
assert_eq!(cow.basic(addr).unwrap().unwrap().balance, U256::from(500));
390+
// Inner still has original via ref
391+
assert_eq!(cow.inner().basic_ref(addr).unwrap().unwrap().balance, U256::from(100));
392+
}
393+
394+
#[test]
395+
fn test_nested_cow_flatten() {
396+
let addr1 = Address::repeat_byte(6);
397+
let addr2 = Address::repeat_byte(7);
398+
let mut inner = InMemoryDB::default();
399+
inner.test_set_balance(addr1, U256::from(100));
400+
401+
let mut cow = CacheOnWrite::new(inner);
402+
cow.test_set_balance(addr2, U256::from(50));
403+
404+
let mut nested = cow.nest();
405+
nested.test_set_balance(addr1, U256::from(200));
406+
407+
// Flatten nested -> cow
408+
let mut cow = nested.flatten();
409+
// addr1 was overwritten in nested layer
410+
assert_eq!(cow.basic(addr1).unwrap().unwrap().balance, U256::from(200));
411+
// addr2 untouched in nested, visible from cow layer
412+
assert_eq!(cow.basic(addr2).unwrap().unwrap().balance, U256::from(50));
413+
414+
// Flatten cow -> inner
415+
let inner = cow.flatten();
416+
assert_eq!(inner.basic_ref(addr1).unwrap().unwrap().balance, U256::from(200));
417+
assert_eq!(inner.basic_ref(addr2).unwrap().unwrap().balance, U256::from(50));
418+
}
323419
}
324420

325421
// Some code above and documentation is adapted from the revm crate, and is

src/db/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ pub mod cow;
1313
#[cfg(feature = "alloy-db")]
1414
/// Alloy-powered revm Database implementation that fetches data over the network.
1515
pub mod alloy;
16+
17+
/// Test utilities for databases.
18+
#[doc(hidden)]
19+
pub mod test_utils;

src/db/sync/state.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,89 @@ mod test {
477477
U256::from(100)
478478
);
479479
}
480+
481+
#[test]
482+
fn test_increment_balances() {
483+
let addr = Address::repeat_byte(10);
484+
let mut state = ConcurrentState::new(EmptyDB::new(), Default::default());
485+
486+
state.increment_balances([(addr, 1000)]).unwrap();
487+
assert_eq!(state.basic(addr).unwrap().unwrap().balance, U256::from(1000));
488+
489+
state.increment_balances([(addr, 500)]).unwrap();
490+
assert_eq!(state.basic(addr).unwrap().unwrap().balance, U256::from(1500));
491+
}
492+
493+
#[test]
494+
fn test_drain_balances() {
495+
let addr = Address::repeat_byte(11);
496+
let mut state = ConcurrentState::new(EmptyDB::new(), Default::default());
497+
498+
state.increment_balances([(addr, 1000)]).unwrap();
499+
let drained = state.drain_balances([addr]).unwrap();
500+
assert_eq!(drained, vec![1000]);
501+
assert_eq!(state.basic(addr).unwrap().unwrap().balance, U256::ZERO);
502+
}
503+
504+
#[test]
505+
fn test_insert_account() {
506+
let addr = Address::repeat_byte(12);
507+
let mut state = ConcurrentState::new(EmptyDB::new(), Default::default());
508+
509+
let info = AccountInfo { balance: U256::from(999), nonce: 42, ..Default::default() };
510+
state.insert_account(addr, info);
511+
512+
let loaded = state.basic(addr).unwrap().unwrap();
513+
assert_eq!(loaded.balance, U256::from(999));
514+
assert_eq!(loaded.nonce, 42);
515+
}
516+
517+
#[test]
518+
fn test_child_isolation() {
519+
let addr = Address::repeat_byte(14);
520+
let parent = Arc::new(ConcurrentState::new(EmptyDB::new(), Default::default()));
521+
522+
// Use increment_balances which properly handles account loading
523+
let mut child = parent.child();
524+
child.increment_balances([(addr, 500)]).unwrap();
525+
526+
// Child sees balance
527+
assert_eq!(
528+
child.info.cache.accounts.get(&addr).unwrap().account_info().unwrap().balance,
529+
U256::from(500)
530+
);
531+
// Parent sees account loaded but with zero balance (from EmptyDB lookup)
532+
assert_eq!(
533+
parent.info.cache.accounts.get(&addr).unwrap().account_info(),
534+
None // Not existing account loaded
535+
);
536+
537+
// After merge, parent sees child's value
538+
let mut parent = parent;
539+
parent.merge_child(child).unwrap();
540+
assert_eq!(parent.basic_ref(addr).unwrap().unwrap().balance, U256::from(500));
541+
}
542+
543+
#[test]
544+
fn test_child_merge_accumulates() {
545+
let addr = Address::repeat_byte(15);
546+
let mut parent = Arc::new(ConcurrentState::new(EmptyDB::new(), Default::default()));
547+
548+
// Set initial balance on parent
549+
Arc::get_mut(&mut parent).unwrap().increment_balances([(addr, 100)]).unwrap();
550+
551+
let mut child = parent.child();
552+
child.increment_balances([(addr, 50)]).unwrap();
553+
554+
// Child sees accumulated balance
555+
assert_eq!(child.basic(addr).unwrap().unwrap().balance, U256::from(150));
556+
// Parent sees original
557+
assert_eq!(parent.basic_ref(addr).unwrap().unwrap().balance, U256::from(100));
558+
559+
// After merge
560+
parent.merge_child(child).unwrap();
561+
assert_eq!(parent.basic_ref(addr).unwrap().unwrap().balance, U256::from(150));
562+
}
480563
}
481564

482565
// Some code above and documentation is adapted from the revm crate, and is

src/db/test_utils.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
use alloy::primitives::{Address, U256};
2+
use revm::{
3+
bytecode::Bytecode,
4+
database::{Database, DatabaseCommit, TryDatabaseCommit},
5+
primitives::HashMap,
6+
state::{Account, AccountInfo, AccountStatus, EvmStorageSlot},
7+
};
8+
9+
/// Test utilities for databases.
10+
#[doc(hidden)]
11+
pub trait DbTestExt: Database + DatabaseCommit {
12+
/// Modify an account via closure, creating it if it doesn't exist.
13+
fn test_modify_account<F>(&mut self, address: Address, f: F)
14+
where
15+
F: FnOnce(&mut AccountInfo),
16+
{
17+
let mut info = self.basic(address).ok().flatten().unwrap_or_default();
18+
f(&mut info);
19+
let account = Account {
20+
info,
21+
storage: Default::default(),
22+
status: AccountStatus::Touched,
23+
transaction_id: 0,
24+
};
25+
self.commit(HashMap::from_iter([(address, account)]));
26+
}
27+
28+
/// Set account balance.
29+
fn test_set_balance(&mut self, address: Address, balance: U256) {
30+
self.test_modify_account(address, |info| info.balance = balance);
31+
}
32+
33+
/// Increase account balance (saturating).
34+
fn test_increase_balance(&mut self, address: Address, amount: U256) {
35+
self.test_modify_account(address, |info| {
36+
info.balance = info.balance.saturating_add(amount)
37+
});
38+
}
39+
40+
/// Decrease account balance (saturating).
41+
fn test_decrease_balance(&mut self, address: Address, amount: U256) {
42+
self.test_modify_account(address, |info| {
43+
info.balance = info.balance.saturating_sub(amount)
44+
});
45+
}
46+
47+
/// Set account nonce.
48+
fn test_set_nonce(&mut self, address: Address, nonce: u64) {
49+
self.test_modify_account(address, |info| info.nonce = nonce);
50+
}
51+
52+
/// Set a storage slot.
53+
fn test_set_storage(&mut self, address: Address, slot: U256, value: U256) {
54+
let info = self.basic(address).ok().flatten().unwrap_or_default();
55+
let storage =
56+
HashMap::from_iter([(slot, EvmStorageSlot::new_changed(U256::ZERO, value, 0))]);
57+
let account = Account { info, storage, status: AccountStatus::Touched, transaction_id: 0 };
58+
self.commit(HashMap::from_iter([(address, account)]));
59+
}
60+
61+
/// Set account bytecode.
62+
fn test_set_bytecode(&mut self, address: Address, bytecode: Bytecode) {
63+
self.test_modify_account(address, |info| {
64+
info.code_hash = bytecode.hash_slow();
65+
info.code = Some(bytecode);
66+
});
67+
}
68+
}
69+
70+
impl<Db: Database + DatabaseCommit> DbTestExt for Db {}
71+
72+
/// Test utilities for databases that support fallible commits.
73+
#[doc(hidden)]
74+
pub trait TryDbTestExt: Database + TryDatabaseCommit {
75+
/// Modify an account via closure, creating it if it doesn't exist.
76+
fn test_try_modify_account<F>(
77+
&mut self,
78+
address: Address,
79+
f: F,
80+
) -> Result<(), <Self as TryDatabaseCommit>::Error>
81+
where
82+
F: FnOnce(&mut AccountInfo),
83+
{
84+
let mut info = self.basic(address).ok().flatten().unwrap_or_default();
85+
f(&mut info);
86+
let account = Account {
87+
info,
88+
storage: Default::default(),
89+
status: AccountStatus::Touched,
90+
transaction_id: 0,
91+
};
92+
self.try_commit(HashMap::from_iter([(address, account)]))
93+
}
94+
95+
/// Set account balance.
96+
fn test_try_set_balance(
97+
&mut self,
98+
address: Address,
99+
balance: U256,
100+
) -> Result<(), <Self as TryDatabaseCommit>::Error> {
101+
self.test_try_modify_account(address, |info| info.balance = balance)
102+
}
103+
104+
/// Increase account balance (saturating).
105+
fn test_try_increase_balance(
106+
&mut self,
107+
address: Address,
108+
amount: U256,
109+
) -> Result<(), <Self as TryDatabaseCommit>::Error> {
110+
self.test_try_modify_account(address, |info| {
111+
info.balance = info.balance.saturating_add(amount)
112+
})
113+
}
114+
115+
/// Decrease account balance (saturating).
116+
fn test_try_decrease_balance(
117+
&mut self,
118+
address: Address,
119+
amount: U256,
120+
) -> Result<(), <Self as TryDatabaseCommit>::Error> {
121+
self.test_try_modify_account(address, |info| {
122+
info.balance = info.balance.saturating_sub(amount)
123+
})
124+
}
125+
126+
/// Set account nonce.
127+
fn test_try_set_nonce(
128+
&mut self,
129+
address: Address,
130+
nonce: u64,
131+
) -> Result<(), <Self as TryDatabaseCommit>::Error> {
132+
self.test_try_modify_account(address, |info| info.nonce = nonce)
133+
}
134+
135+
/// Set a storage slot.
136+
fn test_try_set_storage(
137+
&mut self,
138+
address: Address,
139+
slot: U256,
140+
value: U256,
141+
) -> Result<(), <Self as TryDatabaseCommit>::Error> {
142+
let info = self.basic(address).ok().flatten().unwrap_or_default();
143+
let storage =
144+
HashMap::from_iter([(slot, EvmStorageSlot::new_changed(U256::ZERO, value, 0))]);
145+
let account = Account { info, storage, status: AccountStatus::Touched, transaction_id: 0 };
146+
self.try_commit(HashMap::from_iter([(address, account)]))
147+
}
148+
149+
/// Set account bytecode.
150+
fn test_try_set_bytecode(
151+
&mut self,
152+
address: Address,
153+
bytecode: Bytecode,
154+
) -> Result<(), <Self as TryDatabaseCommit>::Error> {
155+
self.test_try_modify_account(address, |info| {
156+
info.code_hash = bytecode.hash_slow();
157+
info.code = Some(bytecode);
158+
})
159+
}
160+
}
161+
162+
impl<Db: Database + TryDatabaseCommit> TryDbTestExt for Db {}

0 commit comments

Comments
 (0)