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