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