1use std::{
4 collections::BTreeMap,
5 fmt::{self, Debug},
6 path::Path,
7};
8
9use alloy_consensus::{BlockBody, Header};
10use alloy_eips::eip4895::Withdrawals;
11use alloy_network::Network;
12use alloy_primitives::{
13 Address, B256, Bytes, U256, keccak256,
14 map::{AddressMap, HashMap},
15};
16use alloy_rpc_types::BlockId;
17use anvil_core::eth::{
18 block::Block,
19 transaction::{MaybeImpersonatedTransaction, TransactionInfo},
20};
21use foundry_common::errors::FsPathError;
22use foundry_evm::backend::{
23 BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertStateSnapshotAction, StateSnapshot,
24};
25use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope};
26use revm::{
27 Database, DatabaseCommit,
28 bytecode::Bytecode,
29 context::BlockEnv,
30 context_interface::block::BlobExcessGasAndPrice,
31 database::{CacheDB, DatabaseRef, DbAccount},
32 primitives::{KECCAK_EMPTY, eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE},
33 state::AccountInfo,
34};
35use serde::{
36 Deserialize, Deserializer, Serialize,
37 de::{Error as DeError, MapAccess, Visitor},
38};
39use serde_json::Value;
40
41use crate::mem::storage::MinedTransaction;
42
43pub trait MaybeFullDatabase: DatabaseRef<Error = DatabaseError> + Debug {
45 fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
46 None
47 }
48
49 fn clear_into_state_snapshot(&mut self) -> StateSnapshot;
51
52 fn read_as_state_snapshot(&self) -> StateSnapshot;
56
57 fn clear(&mut self);
59
60 fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot);
62}
63
64impl<'a, T: 'a + MaybeFullDatabase + ?Sized> MaybeFullDatabase for &'a T
65where
66 &'a T: DatabaseRef<Error = DatabaseError>,
67{
68 fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
69 T::maybe_as_full_db(self)
70 }
71
72 fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
73 unreachable!("never called for DatabaseRef")
74 }
75
76 fn read_as_state_snapshot(&self) -> StateSnapshot {
77 unreachable!("never called for DatabaseRef")
78 }
79
80 fn clear(&mut self) {}
81
82 fn init_from_state_snapshot(&mut self, _state_snapshot: StateSnapshot) {}
83}
84
85pub trait MaybeForkedDatabase {
87 fn maybe_reset(&mut self, _url: Option<String>, block_number: BlockId) -> Result<(), String>;
88
89 fn maybe_flush_cache(&self) -> Result<(), String>;
90
91 fn maybe_inner(&self) -> Result<&BlockchainDb, String>;
92}
93
94impl alloy_evm::Database for dyn Db {}
97
98#[derive(Debug)]
100pub struct AnvilCacheDB<T>(pub CacheDB<T>);
101
102impl<T: DatabaseRef<Error = DatabaseError>> AnvilCacheDB<T> {
103 pub fn new(inner: T) -> Self {
104 Self(CacheDB::new(inner))
105 }
106}
107
108impl<T: DatabaseRef<Error = DatabaseError>> std::ops::Deref for AnvilCacheDB<T> {
109 type Target = CacheDB<T>;
110 fn deref(&self) -> &Self::Target {
111 &self.0
112 }
113}
114
115impl<T: DatabaseRef<Error = DatabaseError>> std::ops::DerefMut for AnvilCacheDB<T> {
116 fn deref_mut(&mut self) -> &mut Self::Target {
117 &mut self.0
118 }
119}
120
121impl<T: DatabaseRef<Error = DatabaseError> + fmt::Debug> Database for AnvilCacheDB<T> {
122 type Error = DatabaseError;
123
124 fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
125 self.0.basic(address)
126 }
127
128 fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
129 self.0.code_by_hash(code_hash)
130 }
131
132 fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
133 self.0.storage(address, index)
134 }
135
136 fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
137 self.0.block_hash(number)
138 }
139}
140
141impl<T: DatabaseRef<Error = DatabaseError>> DatabaseRef for AnvilCacheDB<T> {
142 type Error = DatabaseError;
143
144 fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
145 self.0.basic_ref(address)
146 }
147
148 fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
149 self.0.code_by_hash_ref(code_hash)
150 }
151
152 fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
153 self.0.storage_ref(address, index)
154 }
155
156 fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
157 self.0.block_hash_ref(number)
158 }
159}
160
161impl<T: DatabaseRef<Error = DatabaseError> + fmt::Debug> DatabaseCommit for AnvilCacheDB<T> {
162 fn commit(&mut self, changes: revm::state::EvmState) {
163 self.0.commit(changes)
164 }
165}
166
167pub trait Db:
169 DatabaseRef<Error = DatabaseError>
170 + Database<Error = DatabaseError>
171 + DatabaseCommit
172 + MaybeFullDatabase
173 + MaybeForkedDatabase
174 + fmt::Debug
175 + Send
176 + Sync
177{
178 fn insert_account(&mut self, address: Address, account: AccountInfo);
180
181 fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<()> {
183 let mut info = self.basic(address)?.unwrap_or_default();
184 info.nonce = nonce;
185 self.insert_account(address, info);
186 Ok(())
187 }
188
189 fn set_balance(&mut self, address: Address, balance: U256) -> DatabaseResult<()> {
191 let mut info = self.basic(address)?.unwrap_or_default();
192 info.balance = balance;
193 self.insert_account(address, info);
194 Ok(())
195 }
196
197 fn set_code(&mut self, address: Address, code: Bytes) -> DatabaseResult<()> {
199 let mut info = self.basic(address)?.unwrap_or_default();
200 let code_hash = if code.as_ref().is_empty() {
201 KECCAK_EMPTY
202 } else {
203 B256::from_slice(&keccak256(code.as_ref())[..])
204 };
205 info.code_hash = code_hash;
206 info.code = Some(Bytecode::new_raw(alloy_primitives::Bytes(code.0)));
207 self.insert_account(address, info);
208 Ok(())
209 }
210
211 fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()>;
213
214 fn insert_block_hash(&mut self, number: U256, hash: B256);
216
217 fn dump_state(
219 &self,
220 at: BlockEnv,
221 best_number: u64,
222 blocks: Vec<SerializableBlock>,
223 transactions: Vec<SerializableTransaction>,
224 historical_states: Option<SerializableHistoricalStates>,
225 ) -> DatabaseResult<Option<SerializableState>>;
226
227 fn load_state(&mut self, state: SerializableState) -> DatabaseResult<bool> {
229 for (addr, account) in state.accounts {
230 let old_account_nonce = DatabaseRef::basic_ref(self, addr)
231 .ok()
232 .and_then(|acc| acc.map(|acc| acc.nonce))
233 .unwrap_or_default();
234 let nonce = std::cmp::max(old_account_nonce, account.nonce);
237
238 self.insert_account(
239 addr,
240 AccountInfo {
241 balance: account.balance,
242 code_hash: KECCAK_EMPTY, code: if account.code.0.is_empty() {
244 None
245 } else {
246 Some(Bytecode::new_raw(alloy_primitives::Bytes(account.code.0)))
247 },
248 nonce,
249 account_id: None,
250 },
251 );
252
253 for (k, v) in account.storage {
254 self.set_storage_at(addr, k, v)?;
255 }
256 }
257 Ok(true)
258 }
259
260 fn snapshot_state(&mut self) -> U256;
262
263 fn revert_state(&mut self, state_snapshot: U256, action: RevertStateSnapshotAction) -> bool;
267
268 fn maybe_state_root(&self) -> Option<B256> {
270 None
271 }
272
273 fn current_state(&self) -> StateDb;
275}
276
277impl<T: DatabaseRef<Error = DatabaseError> + Send + Sync + Clone + fmt::Debug> Db for CacheDB<T> {
282 fn insert_account(&mut self, address: Address, account: AccountInfo) {
283 self.insert_account_info(address, account)
284 }
285
286 fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()> {
287 self.insert_account_storage(address, slot.into(), val.into())
288 }
289
290 fn insert_block_hash(&mut self, number: U256, hash: B256) {
291 self.cache.block_hashes.insert(number, hash);
292 }
293
294 fn dump_state(
295 &self,
296 _at: BlockEnv,
297 _best_number: u64,
298 _blocks: Vec<SerializableBlock>,
299 _transaction: Vec<SerializableTransaction>,
300 _historical_states: Option<SerializableHistoricalStates>,
301 ) -> DatabaseResult<Option<SerializableState>> {
302 Ok(None)
303 }
304
305 fn snapshot_state(&mut self) -> U256 {
306 U256::ZERO
307 }
308
309 fn revert_state(&mut self, _state_snapshot: U256, _action: RevertStateSnapshotAction) -> bool {
310 false
311 }
312
313 fn current_state(&self) -> StateDb {
314 StateDb::new(MemDb::default())
315 }
316}
317
318impl<T: DatabaseRef<Error = DatabaseError> + Debug> MaybeFullDatabase for CacheDB<T> {
319 fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
320 Some(&self.cache.accounts)
321 }
322
323 fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
324 let db_accounts = std::mem::take(&mut self.cache.accounts);
325 let mut accounts = HashMap::default();
326 let mut account_storage = HashMap::default();
327
328 for (addr, mut acc) in db_accounts {
329 account_storage.insert(addr, std::mem::take(&mut acc.storage));
330 let mut info = acc.info;
331 info.code = self.cache.contracts.remove(&info.code_hash);
332 accounts.insert(addr, info);
333 }
334 let block_hashes = std::mem::take(&mut self.cache.block_hashes);
335 StateSnapshot { accounts, storage: account_storage, block_hashes }
336 }
337
338 fn read_as_state_snapshot(&self) -> StateSnapshot {
339 let mut accounts = HashMap::default();
340 let mut account_storage = HashMap::default();
341
342 for (addr, acc) in &self.cache.accounts {
343 account_storage.insert(*addr, acc.storage.clone());
344 let mut info = acc.info.clone();
345 info.code = self.cache.contracts.get(&info.code_hash).cloned();
346 accounts.insert(*addr, info);
347 }
348
349 let block_hashes = self.cache.block_hashes.clone();
350 StateSnapshot { accounts, storage: account_storage, block_hashes }
351 }
352
353 fn clear(&mut self) {
354 self.clear_into_state_snapshot();
355 }
356
357 fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) {
358 let StateSnapshot { accounts, mut storage, block_hashes } = state_snapshot;
359
360 for (addr, mut acc) in accounts {
361 if let Some(code) = acc.code.take() {
362 self.cache.contracts.insert(acc.code_hash, code);
363 }
364 self.cache.accounts.insert(
365 addr,
366 DbAccount {
367 info: acc,
368 storage: storage.remove(&addr).unwrap_or_default(),
369 ..Default::default()
370 },
371 );
372 }
373 self.cache.block_hashes = block_hashes;
374 }
375}
376
377impl<T: DatabaseRef<Error = DatabaseError>> MaybeForkedDatabase for CacheDB<T> {
378 fn maybe_reset(&mut self, _url: Option<String>, _block_number: BlockId) -> Result<(), String> {
379 Err("not supported".to_string())
380 }
381
382 fn maybe_flush_cache(&self) -> Result<(), String> {
383 Err("not supported".to_string())
384 }
385
386 fn maybe_inner(&self) -> Result<&BlockchainDb, String> {
387 Err("not supported".to_string())
388 }
389}
390
391#[derive(Debug)]
393pub struct StateDb(pub(crate) Box<dyn MaybeFullDatabase + Send + Sync>);
394
395impl StateDb {
396 pub fn new(db: impl MaybeFullDatabase + Send + Sync + 'static) -> Self {
397 Self(Box::new(db))
398 }
399
400 pub fn serialize_state(&mut self) -> StateSnapshot {
401 self.read_as_state_snapshot()
404 }
405}
406
407impl DatabaseRef for StateDb {
408 type Error = DatabaseError;
409 fn basic_ref(&self, address: Address) -> DatabaseResult<Option<AccountInfo>> {
410 self.0.basic_ref(address)
411 }
412
413 fn code_by_hash_ref(&self, code_hash: B256) -> DatabaseResult<Bytecode> {
414 self.0.code_by_hash_ref(code_hash)
415 }
416
417 fn storage_ref(&self, address: Address, index: U256) -> DatabaseResult<U256> {
418 self.0.storage_ref(address, index)
419 }
420
421 fn block_hash_ref(&self, number: u64) -> DatabaseResult<B256> {
422 self.0.block_hash_ref(number)
423 }
424}
425
426impl MaybeFullDatabase for StateDb {
427 fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
428 self.0.maybe_as_full_db()
429 }
430
431 fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
432 self.0.clear_into_state_snapshot()
433 }
434
435 fn read_as_state_snapshot(&self) -> StateSnapshot {
436 self.0.read_as_state_snapshot()
437 }
438
439 fn clear(&mut self) {
440 self.0.clear()
441 }
442
443 fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) {
444 self.0.init_from_state_snapshot(state_snapshot)
445 }
446}
447
448#[derive(Debug, Deserialize)]
450#[serde(rename_all = "snake_case")]
451pub struct LegacyBlockEnv {
452 pub number: Option<StringOrU64>,
453 #[serde(alias = "coinbase")]
454 pub beneficiary: Option<Address>,
455 pub timestamp: Option<StringOrU64>,
456 pub gas_limit: Option<StringOrU64>,
457 pub basefee: Option<StringOrU64>,
458 pub difficulty: Option<StringOrU64>,
459 pub prevrandao: Option<B256>,
460 pub blob_excess_gas_and_price: Option<LegacyBlobExcessGasAndPrice>,
461}
462
463#[derive(Debug, Deserialize)]
465pub struct LegacyBlobExcessGasAndPrice {
466 pub excess_blob_gas: u64,
467 pub blob_gasprice: u64,
468}
469
470#[derive(Debug, Deserialize)]
472#[serde(untagged)]
473pub enum StringOrU64 {
474 Hex(String),
475 Dec(u64),
476}
477
478impl StringOrU64 {
479 pub fn to_u64(&self) -> Option<u64> {
480 match self {
481 Self::Dec(n) => Some(*n),
482 Self::Hex(s) => s.strip_prefix("0x").and_then(|s| u64::from_str_radix(s, 16).ok()),
483 }
484 }
485
486 pub fn to_u256(&self) -> Option<U256> {
487 match self {
488 Self::Dec(n) => Some(U256::from(*n)),
489 Self::Hex(s) => s.strip_prefix("0x").and_then(|s| U256::from_str_radix(s, 16).ok()),
490 }
491 }
492}
493
494impl TryFrom<LegacyBlockEnv> for BlockEnv {
496 type Error = &'static str;
497
498 fn try_from(legacy: LegacyBlockEnv) -> Result<Self, Self::Error> {
499 Ok(Self {
500 number: legacy.number.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO),
501 beneficiary: legacy.beneficiary.unwrap_or(Address::ZERO),
502 timestamp: legacy.timestamp.and_then(|v| v.to_u256()).unwrap_or(U256::ONE),
503 gas_limit: legacy.gas_limit.and_then(|v| v.to_u64()).unwrap_or(u64::MAX),
504 basefee: legacy.basefee.and_then(|v| v.to_u64()).unwrap_or(0),
505 difficulty: legacy.difficulty.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO),
506 prevrandao: legacy.prevrandao.or(Some(B256::ZERO)),
507 slot_num: 0,
508 blob_excess_gas_and_price: legacy
509 .blob_excess_gas_and_price
510 .map(|v| BlobExcessGasAndPrice::new(v.excess_blob_gas, v.blob_gasprice))
511 .or_else(|| {
512 Some(BlobExcessGasAndPrice::new(0, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE))
513 }),
514 })
515 }
516}
517
518fn deserialize_block_env_compat<'de, D>(deserializer: D) -> Result<Option<BlockEnv>, D::Error>
520where
521 D: Deserializer<'de>,
522{
523 let value: Option<Value> = Option::deserialize(deserializer)?;
524 let Some(value) = value else {
525 return Ok(None);
526 };
527
528 if let Ok(env) = BlockEnv::deserialize(&value) {
529 return Ok(Some(env));
530 }
531
532 let legacy: LegacyBlockEnv = serde_json::from_value(value).map_err(|e| {
533 D::Error::custom(format!("Legacy deserialization of `BlockEnv` failed: {e}"))
534 })?;
535
536 Ok(Some(BlockEnv::try_from(legacy).map_err(D::Error::custom)?))
537}
538
539fn deserialize_best_block_number_compat<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
541where
542 D: Deserializer<'de>,
543{
544 let value: Option<Value> = Option::deserialize(deserializer)?;
545 let Some(value) = value else {
546 return Ok(None);
547 };
548
549 let number = match value {
550 Value::Number(n) => n.as_u64(),
551 Value::String(s) => {
552 if let Some(s) = s.strip_prefix("0x") {
553 u64::from_str_radix(s, 16).ok()
554 } else {
555 s.parse().ok()
556 }
557 }
558 _ => None,
559 };
560
561 Ok(number)
562}
563
564#[derive(Clone, Debug, Default, Serialize, Deserialize)]
565pub struct SerializableState {
566 #[serde(deserialize_with = "deserialize_block_env_compat")]
570 pub block: Option<BlockEnv>,
571 pub accounts: BTreeMap<Address, SerializableAccountRecord>,
572 #[serde(deserialize_with = "deserialize_best_block_number_compat")]
574 pub best_block_number: Option<u64>,
575 #[serde(default)]
576 pub blocks: Vec<SerializableBlock>,
577 #[serde(default)]
578 pub transactions: Vec<SerializableTransaction>,
579 #[serde(default)]
583 pub historical_states: Option<SerializableHistoricalStates>,
584}
585
586impl SerializableState {
587 pub fn load(path: impl AsRef<Path>) -> Result<Self, FsPathError> {
589 let path = path.as_ref();
590 if path.is_dir() {
591 foundry_common::fs::read_json_file(&path.join("state.json"))
592 } else {
593 foundry_common::fs::read_json_file(path)
594 }
595 }
596
597 #[cfg(feature = "cmd")]
599 pub(crate) fn parse(path: &str) -> Result<Self, String> {
600 Self::load(path).map_err(|err| err.to_string())
601 }
602}
603
604#[derive(Clone, Debug, Serialize, Deserialize)]
605pub struct SerializableAccountRecord {
606 pub nonce: u64,
607 pub balance: U256,
608 pub code: Bytes,
609
610 #[serde(deserialize_with = "deserialize_btree")]
611 pub storage: BTreeMap<B256, B256>,
612}
613
614fn deserialize_btree<'de, D>(deserializer: D) -> Result<BTreeMap<B256, B256>, D::Error>
615where
616 D: Deserializer<'de>,
617{
618 struct BTreeVisitor;
619
620 impl<'de> Visitor<'de> for BTreeVisitor {
621 type Value = BTreeMap<B256, B256>;
622
623 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
624 formatter.write_str("a mapping of hex encoded storage slots to hex encoded state data")
625 }
626
627 fn visit_map<M>(self, mut mapping: M) -> Result<BTreeMap<B256, B256>, M::Error>
628 where
629 M: MapAccess<'de>,
630 {
631 let mut btree = BTreeMap::new();
632 while let Some((key, value)) = mapping.next_entry::<U256, U256>()? {
633 btree.insert(B256::from(key), B256::from(value));
634 }
635
636 Ok(btree)
637 }
638 }
639
640 deserializer.deserialize_map(BTreeVisitor)
641}
642
643#[derive(Clone, Debug, Serialize, Deserialize)]
651#[serde(untagged)]
652pub enum SerializableTransactionType {
653 TypedTransaction(FoundryTxEnvelope),
654 MaybeImpersonatedTransaction(MaybeImpersonatedTransaction<FoundryTxEnvelope>),
655}
656
657#[derive(Clone, Debug, Serialize, Deserialize)]
658pub struct SerializableBlock {
659 pub header: Header,
660 pub transactions: Vec<SerializableTransactionType>,
661 pub ommers: Vec<Header>,
662 #[serde(default)]
663 pub withdrawals: Option<Withdrawals>,
664}
665
666impl From<Block> for SerializableBlock {
667 fn from(block: Block) -> Self {
668 Self {
669 header: block.header,
670 transactions: block.body.transactions.into_iter().map(Into::into).collect(),
671 ommers: block.body.ommers.into_iter().collect(),
672 withdrawals: block.body.withdrawals,
673 }
674 }
675}
676
677impl From<SerializableBlock> for Block {
678 fn from(block: SerializableBlock) -> Self {
679 let transactions = block.transactions.into_iter().map(Into::into).collect();
680 let ommers = block.ommers;
681 let body = BlockBody { transactions, ommers, withdrawals: block.withdrawals };
682 Self::new(block.header, body)
683 }
684}
685
686impl From<MaybeImpersonatedTransaction<FoundryTxEnvelope>> for SerializableTransactionType {
687 fn from(transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>) -> Self {
688 Self::MaybeImpersonatedTransaction(transaction)
689 }
690}
691
692impl From<SerializableTransactionType> for MaybeImpersonatedTransaction<FoundryTxEnvelope> {
693 fn from(transaction: SerializableTransactionType) -> Self {
694 match transaction {
695 SerializableTransactionType::TypedTransaction(tx) => Self::new(tx),
696 SerializableTransactionType::MaybeImpersonatedTransaction(tx) => tx,
697 }
698 }
699}
700
701#[derive(Clone, Debug, Serialize, Deserialize)]
702pub struct SerializableTransaction {
703 pub info: TransactionInfo,
704 pub receipt: FoundryReceiptEnvelope,
705 pub block_hash: B256,
706 pub block_number: u64,
707}
708
709impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> From<MinedTransaction<N>>
710 for SerializableTransaction
711{
712 fn from(transaction: MinedTransaction<N>) -> Self {
713 Self {
714 info: transaction.info,
715 receipt: transaction.receipt,
716 block_hash: transaction.block_hash,
717 block_number: transaction.block_number,
718 }
719 }
720}
721
722impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> From<SerializableTransaction>
723 for MinedTransaction<N>
724{
725 fn from(transaction: SerializableTransaction) -> Self {
726 Self {
727 info: transaction.info,
728 receipt: transaction.receipt,
729 block_hash: transaction.block_hash,
730 block_number: transaction.block_number,
731 }
732 }
733}
734
735#[derive(Clone, Debug, Serialize, Deserialize, Default)]
736pub struct SerializableHistoricalStates(Vec<(B256, StateSnapshot)>);
737
738impl SerializableHistoricalStates {
739 pub const fn new(states: Vec<(B256, StateSnapshot)>) -> Self {
740 Self(states)
741 }
742}
743
744impl IntoIterator for SerializableHistoricalStates {
745 type Item = (B256, StateSnapshot);
746 type IntoIter = std::vec::IntoIter<Self::Item>;
747
748 fn into_iter(self) -> Self::IntoIter {
749 self.0.into_iter()
750 }
751}
752
753#[cfg(test)]
754mod test {
755 use super::*;
756
757 #[test]
758 fn test_deser_block() {
759 let block = r#"{
760 "header": {
761 "parentHash": "0xceb0fe420d6f14a8eeec4319515b89acbb0bb4861cad9983d529ab4b1e4af929",
762 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
763 "miner": "0x0000000000000000000000000000000000000000",
764 "stateRoot": "0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1",
765 "transactionsRoot": "0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988",
766 "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa",
767 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
768 "difficulty": "0x0",
769 "number": "0x2",
770 "gasLimit": "0x1c9c380",
771 "gasUsed": "0x5208",
772 "timestamp": "0x66cdc823",
773 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
774 "nonce": "0x0000000000000000",
775 "baseFeePerGas": "0x342a1c58",
776 "blobGasUsed": "0x0",
777 "excessBlobGas": "0x0",
778 "extraData": "0x"
779 },
780 "transactions": [
781 {
782 "type": "0x2",
783 "chainId": "0x7a69",
784 "nonce": "0x0",
785 "gas": "0x5209",
786 "maxFeePerGas": "0x77359401",
787 "maxPriorityFeePerGas": "0x1",
788 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
789 "value": "0x0",
790 "accessList": [],
791 "input": "0x",
792 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
793 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
794 "yParity": "0x0",
795 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
796 }
797 ],
798 "ommers": []
799 }
800 "#;
801
802 let _block: SerializableBlock = serde_json::from_str(block).unwrap();
803 }
804
805 #[test]
806 fn test_block_withdrawals_preserved() {
807 use alloy_eips::eip4895::Withdrawal;
808
809 let withdrawal = Withdrawal {
811 index: 42,
812 validator_index: 123,
813 address: Address::repeat_byte(1),
814 amount: 1000,
815 };
816
817 let header = Header::default();
818 let body = BlockBody {
819 transactions: vec![],
820 ommers: vec![],
821 withdrawals: Some(vec![withdrawal].into()),
822 };
823 let block = Block::new(header, body);
824
825 let serializable = SerializableBlock::from(block);
827 let restored = Block::from(serializable);
828
829 assert!(restored.body.withdrawals.is_some());
831 let withdrawals = restored.body.withdrawals.unwrap();
832 assert_eq!(withdrawals.len(), 1);
833 assert_eq!(withdrawals[0].index, 42);
834 assert_eq!(withdrawals[0].validator_index, 123);
835 }
836}