1use crate::{
2 EthereumHardfork, FeeManager, PrecompileFactory,
3 eth::{
4 backend::{
5 db::{Db, SerializableState},
6 env::Env,
7 fork::{ClientFork, ClientForkConfig},
8 genesis::GenesisConfig,
9 mem::fork_db::ForkedDatabase,
10 time::duration_since_unix_epoch,
11 },
12 fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE},
13 pool::transactions::{PoolTransaction, TransactionOrder},
14 },
15 mem::{self, in_memory_db::MemDb},
16};
17use alloy_chains::Chain;
18use alloy_consensus::BlockHeader;
19use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams};
20use alloy_evm::EvmEnv;
21use alloy_genesis::Genesis;
22use alloy_network::{AnyNetwork, TransactionResponse};
23use alloy_primitives::{BlockNumber, TxHash, U256, hex, map::HashMap, utils::Unit};
24use alloy_provider::Provider;
25use alloy_rpc_types::{Block, BlockNumberOrTag};
26use alloy_signer::Signer;
27use alloy_signer_local::{
28 MnemonicBuilder, PrivateKeySigner,
29 coins_bip39::{English, Mnemonic},
30};
31use alloy_transport::TransportError;
32use anvil_server::ServerConfig;
33use eyre::{Context, Result};
34use foundry_common::{
35 ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT,
36 provider::{ProviderBuilder, RetryProvider},
37};
38use foundry_config::Config;
39use foundry_evm::{
40 backend::{BlockchainDb, BlockchainDbMeta, SharedBackend},
41 constants::DEFAULT_CREATE2_DEPLOYER,
42 core::AsEnvMut,
43 hardfork::{
44 FoundryHardfork, OpHardfork, ethereum_hardfork_from_block_tag,
45 spec_id_from_ethereum_hardfork,
46 },
47 utils::{apply_chain_and_block_specific_env_changes, get_blob_base_fee_update_fraction},
48};
49use itertools::Itertools;
50use op_revm::OpTransaction;
51use parking_lot::RwLock;
52use rand_08::thread_rng;
53use revm::{
54 context::{BlockEnv, CfgEnv, TxEnv},
55 context_interface::block::BlobExcessGasAndPrice,
56 primitives::hardfork::SpecId,
57};
58use serde_json::{Value, json};
59use std::{
60 fmt::Write as FmtWrite,
61 fs::File,
62 io,
63 net::{IpAddr, Ipv4Addr},
64 path::PathBuf,
65 sync::Arc,
66 time::Duration,
67};
68use tokio::sync::RwLock as TokioRwLock;
69use yansi::Paint;
70
71pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
72use foundry_evm::{
73 traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier},
74 utils::get_blob_params,
75};
76use foundry_evm_networks::NetworkConfigs;
77
78pub const NODE_PORT: u16 = 8545;
80pub const CHAIN_ID: u64 = 31337;
82pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
84pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
86
87pub const DEFAULT_IPC_ENDPOINT: &str =
89 if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
90
91const BANNER: &str = r"
92 _ _
93 (_) | |
94 __ _ _ __ __ __ _ | |
95 / _` | | '_ \ \ \ / / | | | |
96 | (_| | | | | | \ V / | | | |
97 \__,_| |_| |_| \_/ |_| |_|
98";
99
100#[derive(Clone, Debug)]
102pub struct NodeConfig {
103 pub chain_id: Option<u64>,
105 pub gas_limit: Option<u64>,
107 pub disable_block_gas_limit: bool,
109 pub enable_tx_gas_limit: bool,
111 pub gas_price: Option<u128>,
113 pub base_fee: Option<u64>,
115 pub disable_min_priority_fee: bool,
117 pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
119 pub hardfork: Option<FoundryHardfork>,
121 pub genesis_accounts: Vec<PrivateKeySigner>,
123 pub genesis_balance: U256,
125 pub genesis_timestamp: Option<u64>,
127 pub genesis_block_number: Option<u64>,
129 pub signer_accounts: Vec<PrivateKeySigner>,
131 pub block_time: Option<Duration>,
133 pub no_mining: bool,
135 pub mixed_mining: bool,
137 pub port: u16,
139 pub max_transactions: usize,
141 pub eth_rpc_url: Option<String>,
143 pub fork_choice: Option<ForkChoice>,
145 pub fork_headers: Vec<String>,
147 pub fork_chain_id: Option<U256>,
149 pub account_generator: Option<AccountGenerator>,
151 pub enable_tracing: bool,
153 pub no_storage_caching: bool,
155 pub server_config: ServerConfig,
157 pub host: Vec<IpAddr>,
159 pub transaction_order: TransactionOrder,
161 pub config_out: Option<PathBuf>,
163 pub genesis: Option<Genesis>,
165 pub fork_request_timeout: Duration,
167 pub fork_request_retries: u32,
169 pub fork_retry_backoff: Duration,
171 pub compute_units_per_second: u64,
173 pub ipc_path: Option<Option<String>>,
175 pub enable_steps_tracing: bool,
177 pub print_logs: bool,
179 pub print_traces: bool,
181 pub enable_auto_impersonate: bool,
183 pub code_size_limit: Option<usize>,
185 pub prune_history: PruneStateHistoryConfig,
189 pub max_persisted_states: Option<usize>,
191 pub init_state: Option<SerializableState>,
193 pub transaction_block_keeper: Option<usize>,
195 pub disable_default_create2_deployer: bool,
197 pub disable_pool_balance_checks: bool,
199 pub slots_in_an_epoch: u64,
201 pub memory_limit: Option<u64>,
203 pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
205 pub networks: NetworkConfigs,
207 pub silent: bool,
209 pub cache_path: Option<PathBuf>,
211}
212
213impl NodeConfig {
214 fn as_string(&self, fork: Option<&ClientFork>) -> String {
215 let mut s: String = String::new();
216 let _ = write!(s, "\n{}", BANNER.green());
217 let _ = write!(s, "\n {VERSION_MESSAGE}");
218 let _ = write!(s, "\n {}", "https://github.com/foundry-rs/foundry".green());
219
220 let _ = write!(
221 s,
222 r#"
223
224Available Accounts
225==================
226"#
227 );
228 let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
229 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
230 write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap();
231 }
232
233 let _ = write!(
234 s,
235 r#"
236
237Private Keys
238==================
239"#
240 );
241
242 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
243 let hex = hex::encode(wallet.credential().to_bytes());
244 let _ = write!(s, "\n({idx}) 0x{hex}");
245 }
246
247 if let Some(generator) = &self.account_generator {
248 let _ = write!(
249 s,
250 r#"
251
252Wallet
253==================
254Mnemonic: {}
255Derivation path: {}
256"#,
257 generator.phrase,
258 generator.get_derivation_path()
259 );
260 }
261
262 if let Some(fork) = fork {
263 let _ = write!(
264 s,
265 r#"
266
267Fork
268==================
269Endpoint: {}
270Block number: {}
271Block hash: {:?}
272Chain ID: {}
273"#,
274 fork.eth_rpc_url(),
275 fork.block_number(),
276 fork.block_hash(),
277 fork.chain_id()
278 );
279
280 if let Some(tx_hash) = fork.transaction_hash() {
281 let _ = writeln!(s, "Transaction hash: {tx_hash}");
282 }
283 } else {
284 let _ = write!(
285 s,
286 r#"
287
288Chain ID
289==================
290
291{}
292"#,
293 self.get_chain_id().green()
294 );
295 }
296
297 if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) {
298 let _ = write!(
299 s,
300 r#"
301Gas Price
302==================
303
304{}
305"#,
306 self.get_gas_price().green()
307 );
308 } else {
309 let _ = write!(
310 s,
311 r#"
312Base Fee
313==================
314
315{}
316"#,
317 self.get_base_fee().green()
318 );
319 }
320
321 let _ = write!(
322 s,
323 r#"
324Gas Limit
325==================
326
327{}
328"#,
329 {
330 if self.disable_block_gas_limit {
331 "Disabled".to_string()
332 } else {
333 self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| {
334 if self.fork_choice.is_some() {
335 "Forked".to_string()
336 } else {
337 DEFAULT_GAS_LIMIT.to_string()
338 }
339 })
340 }
341 }
342 .green()
343 );
344
345 let _ = write!(
346 s,
347 r#"
348Genesis Timestamp
349==================
350
351{}
352"#,
353 self.get_genesis_timestamp().green()
354 );
355
356 let _ = write!(
357 s,
358 r#"
359Genesis Number
360==================
361
362{}
363"#,
364 self.get_genesis_number().green()
365 );
366
367 s
368 }
369
370 fn as_json(&self, fork: Option<&ClientFork>) -> Value {
371 let mut wallet_description = HashMap::new();
372 let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
373 let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
374
375 for wallet in &self.genesis_accounts {
376 available_accounts.push(format!("{:?}", wallet.address()));
377 private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes())));
378 }
379
380 if let Some(generator) = &self.account_generator {
381 let phrase = generator.get_phrase().to_string();
382 let derivation_path = generator.get_derivation_path().to_string();
383
384 wallet_description.insert("derivation_path".to_string(), derivation_path);
385 wallet_description.insert("mnemonic".to_string(), phrase);
386 };
387
388 let gas_limit = match self.gas_limit {
389 Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()),
391 Some(limit) => Some(limit.to_string()),
392 _ => None,
393 };
394
395 if let Some(fork) = fork {
396 json!({
397 "available_accounts": available_accounts,
398 "private_keys": private_keys,
399 "endpoint": fork.eth_rpc_url(),
400 "block_number": fork.block_number(),
401 "block_hash": fork.block_hash(),
402 "chain_id": fork.chain_id(),
403 "wallet": wallet_description,
404 "base_fee": format!("{}", self.get_base_fee()),
405 "gas_price": format!("{}", self.get_gas_price()),
406 "gas_limit": gas_limit,
407 })
408 } else {
409 json!({
410 "available_accounts": available_accounts,
411 "private_keys": private_keys,
412 "wallet": wallet_description,
413 "base_fee": format!("{}", self.get_base_fee()),
414 "gas_price": format!("{}", self.get_gas_price()),
415 "gas_limit": gas_limit,
416 "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
417 })
418 }
419 }
420}
421
422impl NodeConfig {
423 #[doc(hidden)]
426 pub fn test() -> Self {
427 Self { enable_tracing: true, port: 0, silent: true, ..Default::default() }
428 }
429
430 pub fn empty_state() -> Self {
432 Self {
433 genesis_accounts: vec![],
434 signer_accounts: vec![],
435 disable_default_create2_deployer: true,
436 ..Default::default()
437 }
438 }
439}
440
441impl Default for NodeConfig {
442 fn default() -> Self {
443 let genesis_accounts = AccountGenerator::new(10)
445 .phrase(DEFAULT_MNEMONIC)
446 .generate()
447 .expect("Invalid mnemonic.");
448 Self {
449 chain_id: None,
450 gas_limit: None,
451 disable_block_gas_limit: false,
452 enable_tx_gas_limit: false,
453 gas_price: None,
454 hardfork: None,
455 signer_accounts: genesis_accounts.clone(),
456 genesis_timestamp: None,
457 genesis_block_number: None,
458 genesis_accounts,
459 genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
461 block_time: None,
462 no_mining: false,
463 mixed_mining: false,
464 port: NODE_PORT,
465 max_transactions: 1_000,
466 eth_rpc_url: None,
467 fork_choice: None,
468 account_generator: None,
469 base_fee: None,
470 disable_min_priority_fee: false,
471 blob_excess_gas_and_price: None,
472 enable_tracing: true,
473 enable_steps_tracing: false,
474 print_logs: true,
475 print_traces: false,
476 enable_auto_impersonate: false,
477 no_storage_caching: false,
478 server_config: Default::default(),
479 host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
480 transaction_order: Default::default(),
481 config_out: None,
482 genesis: None,
483 fork_request_timeout: REQUEST_TIMEOUT,
484 fork_headers: vec![],
485 fork_request_retries: 5,
486 fork_retry_backoff: Duration::from_millis(1_000),
487 fork_chain_id: None,
488 compute_units_per_second: ALCHEMY_FREE_TIER_CUPS,
490 ipc_path: None,
491 code_size_limit: None,
492 prune_history: Default::default(),
493 max_persisted_states: None,
494 init_state: None,
495 transaction_block_keeper: None,
496 disable_default_create2_deployer: false,
497 disable_pool_balance_checks: false,
498 slots_in_an_epoch: 32,
499 memory_limit: None,
500 precompile_factory: None,
501 networks: Default::default(),
502 silent: false,
503 cache_path: None,
504 }
505 }
506}
507
508impl NodeConfig {
509 #[must_use]
511 pub fn with_memory_limit(mut self, mems_value: Option<u64>) -> Self {
512 self.memory_limit = mems_value;
513 self
514 }
515 pub fn get_base_fee(&self) -> u64 {
517 self.base_fee
518 .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64)))
519 .unwrap_or(INITIAL_BASE_FEE)
520 }
521
522 pub fn get_gas_price(&self) -> u128 {
524 self.gas_price.unwrap_or(INITIAL_GAS_PRICE)
525 }
526
527 pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice {
528 if let Some(value) = self.blob_excess_gas_and_price {
529 value
530 } else {
531 let excess_blob_gas =
532 self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0);
533 BlobExcessGasAndPrice::new(
534 excess_blob_gas,
535 get_blob_base_fee_update_fraction(
536 self.chain_id.unwrap_or(Chain::mainnet().id()),
537 self.get_genesis_timestamp(),
538 ),
539 )
540 }
541 }
542
543 pub fn get_blob_params(&self) -> BlobParams {
545 get_blob_params(
546 self.chain_id.unwrap_or(Chain::mainnet().id()),
547 self.get_genesis_timestamp(),
548 )
549 }
550
551 pub fn get_hardfork(&self) -> FoundryHardfork {
553 if let Some(hardfork) = self.hardfork {
554 return hardfork;
555 }
556 if self.networks.is_optimism() {
557 return OpHardfork::default().into();
558 }
559 EthereumHardfork::default().into()
560 }
561
562 #[must_use]
564 pub fn with_code_size_limit(mut self, code_size_limit: Option<usize>) -> Self {
565 self.code_size_limit = code_size_limit;
566 self
567 }
568 #[must_use]
570 pub fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self {
571 if disable_code_size_limit {
572 self.code_size_limit = Some(usize::MAX);
573 }
574 self
575 }
576
577 #[must_use]
579 pub fn with_init_state(mut self, init_state: Option<SerializableState>) -> Self {
580 self.init_state = init_state;
581 self
582 }
583
584 #[must_use]
586 #[cfg(feature = "cmd")]
587 pub fn with_init_state_path(mut self, path: impl AsRef<std::path::Path>) -> Self {
588 self.init_state = crate::cmd::StateFile::parse_path(path).ok().and_then(|file| file.state);
589 self
590 }
591
592 #[must_use]
594 pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
595 self.set_chain_id(chain_id);
596 self
597 }
598
599 pub fn get_chain_id(&self) -> u64 {
601 self.chain_id
602 .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
603 .unwrap_or(CHAIN_ID)
604 }
605
606 pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
608 self.chain_id = chain_id.map(Into::into);
609 let chain_id = self.get_chain_id();
610 self.networks.with_chain_id(chain_id);
611 self.genesis_accounts.iter_mut().for_each(|wallet| {
612 *wallet = wallet.clone().with_chain_id(Some(chain_id));
613 });
614 self.signer_accounts.iter_mut().for_each(|wallet| {
615 *wallet = wallet.clone().with_chain_id(Some(chain_id));
616 })
617 }
618
619 #[must_use]
621 pub fn with_gas_limit(mut self, gas_limit: Option<u64>) -> Self {
622 self.gas_limit = gas_limit;
623 self
624 }
625
626 #[must_use]
630 pub fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self {
631 self.disable_block_gas_limit = disable_block_gas_limit;
632 self
633 }
634
635 #[must_use]
639 pub fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self {
640 self.enable_tx_gas_limit = enable_tx_gas_limit;
641 self
642 }
643
644 #[must_use]
646 pub fn with_gas_price(mut self, gas_price: Option<u128>) -> Self {
647 self.gas_price = gas_price;
648 self
649 }
650
651 #[must_use]
653 pub fn set_pruned_history(mut self, prune_history: Option<Option<usize>>) -> Self {
654 self.prune_history = PruneStateHistoryConfig::from_args(prune_history);
655 self
656 }
657
658 #[must_use]
660 pub fn with_max_persisted_states<U: Into<usize>>(
661 mut self,
662 max_persisted_states: Option<U>,
663 ) -> Self {
664 self.max_persisted_states = max_persisted_states.map(Into::into);
665 self
666 }
667
668 #[must_use]
670 pub fn with_transaction_block_keeper<U: Into<usize>>(
671 mut self,
672 transaction_block_keeper: Option<U>,
673 ) -> Self {
674 self.transaction_block_keeper = transaction_block_keeper.map(Into::into);
675 self
676 }
677
678 #[must_use]
680 pub fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
681 self.base_fee = base_fee;
682 self
683 }
684
685 #[must_use]
687 pub fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self {
688 self.disable_min_priority_fee = disable_min_priority_fee;
689 self
690 }
691
692 #[must_use]
694 pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
695 self.genesis = genesis;
696 self
697 }
698
699 pub fn get_genesis_timestamp(&self) -> u64 {
701 self.genesis_timestamp
702 .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
703 .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
704 }
705
706 #[must_use]
708 pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
709 if let Some(timestamp) = timestamp {
710 self.genesis_timestamp = Some(timestamp.into());
711 }
712 self
713 }
714
715 #[must_use]
717 pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
718 if let Some(number) = number {
719 self.genesis_block_number = Some(number.into());
720 }
721 self
722 }
723
724 pub fn get_genesis_number(&self) -> u64 {
726 self.genesis_block_number
727 .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
728 .unwrap_or(0)
729 }
730
731 #[must_use]
733 pub fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
734 self.hardfork = hardfork;
735 self
736 }
737
738 #[must_use]
740 pub fn with_genesis_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
741 self.genesis_accounts = accounts;
742 self
743 }
744
745 #[must_use]
747 pub fn with_signer_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
748 self.signer_accounts = accounts;
749 self
750 }
751
752 pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
755 let accounts = generator.generate()?;
756 self.account_generator = Some(generator);
757 Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
758 }
759
760 #[must_use]
762 pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
763 self.genesis_balance = balance.into();
764 self
765 }
766
767 #[must_use]
769 pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
770 self.block_time = block_time.map(Into::into);
771 self
772 }
773
774 #[must_use]
775 pub fn with_mixed_mining<D: Into<Duration>>(
776 mut self,
777 mixed_mining: bool,
778 block_time: Option<D>,
779 ) -> Self {
780 self.block_time = block_time.map(Into::into);
781 self.mixed_mining = mixed_mining;
782 self
783 }
784
785 #[must_use]
787 pub fn with_no_mining(mut self, no_mining: bool) -> Self {
788 self.no_mining = no_mining;
789 self
790 }
791
792 #[must_use]
794 pub fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self {
795 self.slots_in_an_epoch = slots_in_an_epoch;
796 self
797 }
798
799 #[must_use]
801 pub fn with_port(mut self, port: u16) -> Self {
802 self.port = port;
803 self
804 }
805
806 #[must_use]
813 pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
814 self.ipc_path = ipc_path;
815 self
816 }
817
818 #[must_use]
820 pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
821 self.config_out = config_out;
822 self
823 }
824
825 #[must_use]
827 pub fn no_storage_caching(self) -> Self {
828 self.with_storage_caching(true)
829 }
830
831 #[must_use]
832 pub fn with_storage_caching(mut self, storage_caching: bool) -> Self {
833 self.no_storage_caching = storage_caching;
834 self
835 }
836
837 #[must_use]
839 pub fn with_eth_rpc_url<U: Into<String>>(mut self, eth_rpc_url: Option<U>) -> Self {
840 self.eth_rpc_url = eth_rpc_url.map(Into::into);
841 self
842 }
843
844 #[must_use]
846 pub fn with_fork_block_number<U: Into<u64>>(self, fork_block_number: Option<U>) -> Self {
847 self.with_fork_choice(fork_block_number.map(Into::into))
848 }
849
850 #[must_use]
852 pub fn with_fork_transaction_hash<U: Into<TxHash>>(
853 self,
854 fork_transaction_hash: Option<U>,
855 ) -> Self {
856 self.with_fork_choice(fork_transaction_hash.map(Into::into))
857 }
858
859 #[must_use]
861 pub fn with_fork_choice<U: Into<ForkChoice>>(mut self, fork_choice: Option<U>) -> Self {
862 self.fork_choice = fork_choice.map(Into::into);
863 self
864 }
865
866 #[must_use]
868 pub fn with_fork_chain_id(mut self, fork_chain_id: Option<U256>) -> Self {
869 self.fork_chain_id = fork_chain_id;
870 self
871 }
872
873 #[must_use]
875 pub fn with_fork_headers(mut self, headers: Vec<String>) -> Self {
876 self.fork_headers = headers;
877 self
878 }
879
880 #[must_use]
882 pub fn fork_request_timeout(mut self, fork_request_timeout: Option<Duration>) -> Self {
883 if let Some(fork_request_timeout) = fork_request_timeout {
884 self.fork_request_timeout = fork_request_timeout;
885 }
886 self
887 }
888
889 #[must_use]
891 pub fn fork_request_retries(mut self, fork_request_retries: Option<u32>) -> Self {
892 if let Some(fork_request_retries) = fork_request_retries {
893 self.fork_request_retries = fork_request_retries;
894 }
895 self
896 }
897
898 #[must_use]
900 pub fn fork_retry_backoff(mut self, fork_retry_backoff: Option<Duration>) -> Self {
901 if let Some(fork_retry_backoff) = fork_retry_backoff {
902 self.fork_retry_backoff = fork_retry_backoff;
903 }
904 self
905 }
906
907 #[must_use]
911 pub fn fork_compute_units_per_second(mut self, compute_units_per_second: Option<u64>) -> Self {
912 if let Some(compute_units_per_second) = compute_units_per_second {
913 self.compute_units_per_second = compute_units_per_second;
914 }
915 self
916 }
917
918 #[must_use]
920 pub fn with_tracing(mut self, enable_tracing: bool) -> Self {
921 self.enable_tracing = enable_tracing;
922 self
923 }
924
925 #[must_use]
927 pub fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self {
928 self.enable_steps_tracing = enable_steps_tracing;
929 self
930 }
931
932 #[must_use]
934 pub fn with_print_logs(mut self, print_logs: bool) -> Self {
935 self.print_logs = print_logs;
936 self
937 }
938
939 #[must_use]
941 pub fn with_print_traces(mut self, print_traces: bool) -> Self {
942 self.print_traces = print_traces;
943 self
944 }
945
946 #[must_use]
948 pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
949 self.enable_auto_impersonate = enable_auto_impersonate;
950 self
951 }
952
953 #[must_use]
954 pub fn with_server_config(mut self, config: ServerConfig) -> Self {
955 self.server_config = config;
956 self
957 }
958
959 #[must_use]
961 pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
962 self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
963 self
964 }
965
966 #[must_use]
967 pub fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self {
968 self.transaction_order = transaction_order;
969 self
970 }
971
972 pub fn get_ipc_path(&self) -> Option<String> {
974 match &self.ipc_path {
975 Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
976 None => None,
977 }
978 }
979
980 pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
982 if let Some(path) = &self.config_out {
983 let file = io::BufWriter::new(
984 File::create(path).wrap_err("unable to create anvil config description file")?,
985 );
986 let value = self.as_json(fork);
987 serde_json::to_writer(file, &value).wrap_err("failed writing JSON")?;
988 }
989 if !self.silent {
990 sh_println!("{}", self.as_string(fork))?;
991 }
992 Ok(())
993 }
994
995 pub fn block_cache_path(&self, block: u64) -> Option<PathBuf> {
999 if self.no_storage_caching || self.eth_rpc_url.is_none() {
1000 return None;
1001 }
1002 let chain_id = self.get_chain_id();
1003
1004 Config::foundry_block_cache_file(chain_id, block)
1005 }
1006
1007 #[must_use]
1009 pub fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self {
1010 self.disable_default_create2_deployer = yes;
1011 self
1012 }
1013
1014 #[must_use]
1016 pub fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self {
1017 self.disable_pool_balance_checks = yes;
1018 self
1019 }
1020
1021 #[must_use]
1023 pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
1024 self.precompile_factory = Some(Arc::new(factory));
1025 self
1026 }
1027
1028 #[must_use]
1030 pub fn with_networks(mut self, networks: NetworkConfigs) -> Self {
1031 self.networks = networks;
1032 self
1033 }
1034
1035 #[must_use]
1037 pub fn silent(self) -> Self {
1038 self.set_silent(true)
1039 }
1040
1041 #[must_use]
1042 pub fn set_silent(mut self, silent: bool) -> Self {
1043 self.silent = silent;
1044 self
1045 }
1046
1047 #[must_use]
1049 pub fn with_cache_path(mut self, cache_path: Option<PathBuf>) -> Self {
1050 self.cache_path = cache_path;
1051 self
1052 }
1053
1054 pub(crate) async fn setup(&mut self) -> Result<mem::Backend> {
1059 let mut cfg = CfgEnv::default();
1062 cfg.spec = self.get_hardfork().into();
1063
1064 cfg.chain_id = self.get_chain_id();
1065 cfg.limit_contract_code_size = self.code_size_limit;
1066 cfg.disable_eip3607 = true;
1070 cfg.disable_block_gas_limit = self.disable_block_gas_limit;
1071
1072 if !self.enable_tx_gas_limit {
1073 cfg.tx_gas_limit_cap = Some(u64::MAX);
1074 }
1075
1076 if let Some(value) = self.memory_limit {
1077 cfg.memory_limit = value;
1078 }
1079
1080 let spec_id = cfg.spec;
1081 let mut env = Env::new(
1082 EvmEnv::new(
1083 cfg,
1084 BlockEnv {
1085 gas_limit: self.gas_limit(),
1086 basefee: self.get_base_fee(),
1087 ..Default::default()
1088 },
1089 ),
1090 OpTransaction {
1091 base: TxEnv { chain_id: Some(self.get_chain_id()), ..Default::default() },
1092 ..Default::default()
1093 },
1094 self.networks,
1095 );
1096
1097 let base_fee_params: BaseFeeParams =
1098 self.networks.base_fee_params(self.get_genesis_timestamp());
1099
1100 let fees = FeeManager::new(
1101 spec_id,
1102 self.get_base_fee(),
1103 !self.disable_min_priority_fee,
1104 self.get_gas_price(),
1105 self.get_blob_excess_gas_and_price(),
1106 self.get_blob_params(),
1107 base_fee_params,
1108 );
1109
1110 let (db, fork): (Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>) =
1111 if let Some(eth_rpc_url) = self.eth_rpc_url.clone() {
1112 self.setup_fork_db(eth_rpc_url, &mut env, &fees).await?
1113 } else {
1114 (Arc::new(TokioRwLock::new(Box::<MemDb>::default())), None)
1115 };
1116
1117 if let Some(ref genesis) = self.genesis {
1119 if self.chain_id.is_none() {
1122 env.evm_env.cfg_env.chain_id = genesis.config.chain_id;
1123 }
1124 env.evm_env.block_env.timestamp = U256::from(genesis.timestamp);
1125 if let Some(base_fee) = genesis.base_fee_per_gas {
1126 env.evm_env.block_env.basefee = base_fee.try_into()?;
1127 }
1128 if let Some(number) = genesis.number {
1129 env.evm_env.block_env.number = U256::from(number);
1130 }
1131 env.evm_env.block_env.beneficiary = genesis.coinbase;
1132 }
1133
1134 let genesis = GenesisConfig {
1135 number: self.get_genesis_number(),
1136 timestamp: self.get_genesis_timestamp(),
1137 balance: self.genesis_balance,
1138 accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(),
1139 genesis_init: self.genesis.clone(),
1140 };
1141
1142 let mut decoder_builder = CallTraceDecoderBuilder::new();
1143 if self.print_traces {
1144 if let Ok(identifier) = SignaturesIdentifier::new(false) {
1146 debug!(target: "node", "using signature identifier");
1147 decoder_builder = decoder_builder.with_signature_identifier(identifier);
1148 }
1149 }
1150
1151 let backend = mem::Backend::with_genesis(
1153 db,
1154 Arc::new(RwLock::new(env)),
1155 genesis,
1156 fees,
1157 Arc::new(RwLock::new(fork)),
1158 self.enable_steps_tracing,
1159 self.print_logs,
1160 self.print_traces,
1161 Arc::new(decoder_builder.build()),
1162 self.prune_history,
1163 self.max_persisted_states,
1164 self.transaction_block_keeper,
1165 self.block_time,
1166 self.cache_path.clone(),
1167 Arc::new(TokioRwLock::new(self.clone())),
1168 )
1169 .await?;
1170
1171 if !self.disable_default_create2_deployer && self.eth_rpc_url.is_none() {
1174 backend
1175 .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER)
1176 .await
1177 .wrap_err("failed to create default create2 deployer")?;
1178 }
1179
1180 if let Some(state) = self.init_state.clone() {
1181 backend.load_state(state).await.wrap_err("failed to load init state")?;
1182 }
1183
1184 Ok(backend)
1185 }
1186
1187 pub async fn setup_fork_db(
1194 &mut self,
1195 eth_rpc_url: String,
1196 env: &mut Env,
1197 fees: &FeeManager,
1198 ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>)> {
1199 let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await?;
1200 let db: Arc<TokioRwLock<Box<dyn Db>>> = Arc::new(TokioRwLock::new(Box::new(db)));
1201 let fork = ClientFork::new(config, Arc::clone(&db));
1202 Ok((db, Some(fork)))
1203 }
1204
1205 pub async fn setup_fork_db_config(
1211 &mut self,
1212 eth_rpc_url: String,
1213 env: &mut Env,
1214 fees: &FeeManager,
1215 ) -> Result<(ForkedDatabase, ClientForkConfig)> {
1216 debug!(target: "node", ?eth_rpc_url, "setting up fork db");
1217 let provider = Arc::new(
1218 ProviderBuilder::new(ð_rpc_url)
1219 .timeout(self.fork_request_timeout)
1220 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1221 .compute_units_per_second(self.compute_units_per_second)
1222 .max_retry(self.fork_request_retries)
1223 .headers(self.fork_headers.clone())
1224 .build()
1225 .wrap_err("failed to establish provider to fork url")?,
1226 );
1227
1228 let (fork_block_number, fork_chain_id, force_transactions) = if let Some(fork_choice) =
1229 &self.fork_choice
1230 {
1231 let (fork_block_number, force_transactions) =
1232 derive_block_and_transactions(fork_choice, &provider).await.wrap_err(
1233 "failed to derive fork block number and force transactions from fork choice",
1234 )?;
1235 let chain_id = if let Some(chain_id) = self.fork_chain_id {
1236 Some(chain_id)
1237 } else if self.hardfork.is_none() {
1238 let chain_id =
1240 provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?;
1241 if alloy_chains::NamedChain::Mainnet == chain_id {
1242 let hardfork: EthereumHardfork =
1243 ethereum_hardfork_from_block_tag(fork_block_number);
1244
1245 env.evm_env.cfg_env.spec = spec_id_from_ethereum_hardfork(hardfork);
1246 self.hardfork = Some(FoundryHardfork::Ethereum(hardfork));
1247 }
1248 Some(U256::from(chain_id))
1249 } else {
1250 None
1251 };
1252
1253 (fork_block_number, chain_id, force_transactions)
1254 } else {
1255 let bn = find_latest_fork_block(&provider)
1257 .await
1258 .wrap_err("failed to get fork block number")?;
1259 (bn, None, None)
1260 };
1261
1262 let block = provider
1263 .get_block(BlockNumberOrTag::Number(fork_block_number).into())
1264 .await
1265 .wrap_err("failed to get fork block")?;
1266
1267 let block = if let Some(block) = block {
1268 block
1269 } else {
1270 if let Ok(latest_block) = provider.get_block_number().await {
1271 let mut message = format!(
1272 "Failed to get block for block number: {fork_block_number}\n\
1273latest block number: {latest_block}"
1274 );
1275 if fork_block_number <= latest_block {
1279 message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}"));
1280 }
1281 eyre::bail!("{message}");
1282 }
1283 eyre::bail!("failed to get block for block number: {fork_block_number}")
1284 };
1285
1286 let gas_limit = self.fork_gas_limit(&block);
1287 self.gas_limit = Some(gas_limit);
1288
1289 env.evm_env.block_env = BlockEnv {
1290 number: U256::from(fork_block_number),
1291 timestamp: U256::from(block.header.timestamp),
1292 difficulty: block.header.difficulty,
1293 prevrandao: Some(block.header.mix_hash.unwrap_or_default()),
1295 gas_limit,
1296 beneficiary: env.evm_env.block_env.beneficiary,
1298 basefee: env.evm_env.block_env.basefee,
1299 ..Default::default()
1300 };
1301
1302 if self.base_fee.is_none() {
1304 if let Some(base_fee) = block.header.base_fee_per_gas {
1305 self.base_fee = Some(base_fee);
1306 env.evm_env.block_env.basefee = base_fee;
1307 let next_block_base_fee = fees.get_next_block_base_fee_per_gas(
1310 block.header.gas_used,
1311 gas_limit,
1312 block.header.base_fee_per_gas.unwrap_or_default(),
1313 );
1314
1315 fees.set_base_fee(next_block_base_fee);
1317 }
1318 if let (Some(blob_excess_gas), Some(blob_gas_used)) =
1319 (block.header.excess_blob_gas, block.header.blob_gas_used)
1320 {
1321 let blob_params = get_blob_params(
1323 fork_chain_id
1324 .unwrap_or_else(|| U256::from(Chain::mainnet().id()))
1325 .saturating_to(),
1326 block.header.timestamp,
1327 );
1328
1329 env.evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
1330 blob_excess_gas,
1331 blob_params.update_fraction as u64,
1332 ));
1333
1334 fees.set_blob_params(blob_params);
1335
1336 let next_block_blob_excess_gas =
1337 fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used);
1338 fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
1339 next_block_blob_excess_gas,
1340 blob_params.update_fraction as u64,
1341 ));
1342 }
1343 }
1344
1345 if self.gas_price.is_none()
1347 && let Ok(gas_price) = provider.get_gas_price().await
1348 {
1349 self.gas_price = Some(gas_price);
1350 fees.set_gas_price(gas_price);
1351 }
1352
1353 let block_hash = block.header.hash;
1354
1355 let chain_id = if let Some(chain_id) = self.chain_id {
1356 chain_id
1357 } else {
1358 let chain_id = if let Some(fork_chain_id) = fork_chain_id {
1359 fork_chain_id.to()
1360 } else {
1361 provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?
1362 };
1363
1364 self.set_chain_id(Some(chain_id));
1366 env.evm_env.cfg_env.chain_id = chain_id;
1367 env.tx.base.chain_id = chain_id.into();
1368 chain_id
1369 };
1370 let override_chain_id = self.chain_id;
1371 apply_chain_and_block_specific_env_changes::<AnyNetwork>(
1373 env.as_env_mut(),
1374 &block,
1375 self.networks,
1376 );
1377
1378 let meta = BlockchainDbMeta::new(env.evm_env.block_env.clone(), eth_rpc_url.clone());
1379 let block_chain_db = if self.fork_chain_id.is_some() {
1380 BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number))
1381 } else {
1382 BlockchainDb::new(meta, self.block_cache_path(fork_block_number))
1383 };
1384
1385 let backend = SharedBackend::spawn_backend(
1388 Arc::clone(&provider),
1389 block_chain_db.clone(),
1390 Some(fork_block_number.into()),
1391 )
1392 .await;
1393
1394 let config = ClientForkConfig {
1395 eth_rpc_url,
1396 block_number: fork_block_number,
1397 block_hash,
1398 transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()),
1399 provider,
1400 chain_id,
1401 override_chain_id,
1402 timestamp: block.header.timestamp,
1403 base_fee: block.header.base_fee_per_gas.map(|g| g as u128),
1404 timeout: self.fork_request_timeout,
1405 retries: self.fork_request_retries,
1406 backoff: self.fork_retry_backoff,
1407 compute_units_per_second: self.compute_units_per_second,
1408 total_difficulty: block.header.total_difficulty.unwrap_or_default(),
1409 blob_gas_used: block.header.blob_gas_used.map(|g| g as u128),
1410 blob_excess_gas_and_price: env.evm_env.block_env.blob_excess_gas_and_price,
1411 force_transactions,
1412 };
1413
1414 debug!(target: "node", fork_number=config.block_number, fork_hash=%config.block_hash, "set up fork db");
1415
1416 let mut db = ForkedDatabase::new(backend, block_chain_db);
1417
1418 db.insert_block_hash(U256::from(config.block_number), config.block_hash);
1420
1421 Ok((db, config))
1422 }
1423
1424 pub(crate) fn fork_gas_limit<T: TransactionResponse, H: BlockHeader>(
1428 &self,
1429 block: &Block<T, H>,
1430 ) -> u64 {
1431 if !self.disable_block_gas_limit {
1432 if let Some(gas_limit) = self.gas_limit {
1433 return gas_limit;
1434 } else if block.header.gas_limit() > 0 {
1435 return block.header.gas_limit();
1436 }
1437 }
1438
1439 u64::MAX
1440 }
1441
1442 pub(crate) fn gas_limit(&self) -> u64 {
1446 if self.disable_block_gas_limit {
1447 return u64::MAX;
1448 }
1449
1450 self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)
1451 }
1452}
1453
1454async fn derive_block_and_transactions(
1459 fork_choice: &ForkChoice,
1460 provider: &Arc<RetryProvider>,
1461) -> eyre::Result<(BlockNumber, Option<Vec<PoolTransaction>>)> {
1462 match fork_choice {
1463 ForkChoice::Block(block_number) => {
1464 let block_number = *block_number;
1465 if block_number >= 0 {
1466 return Ok((block_number as u64, None));
1467 }
1468 let latest = provider.get_block_number().await?;
1470
1471 Ok((block_number.saturating_add(latest as i128) as u64, None))
1472 }
1473 ForkChoice::Transaction(transaction_hash) => {
1474 let transaction = provider
1476 .get_transaction_by_hash(transaction_hash.0.into())
1477 .await?
1478 .ok_or_else(|| eyre::eyre!("failed to get fork transaction by hash"))?;
1479 let transaction_block_number = transaction.block_number.ok_or_else(|| {
1480 eyre::eyre!("fork transaction is not mined yet (no block number)")
1481 })?;
1482
1483 let transaction_block = provider
1485 .get_block_by_number(transaction_block_number.into())
1486 .full()
1487 .await?
1488 .ok_or_else(|| eyre::eyre!("failed to get fork block by number"))?;
1489
1490 let filtered_transactions = transaction_block
1492 .transactions
1493 .as_transactions()
1494 .ok_or_else(|| eyre::eyre!("failed to get transactions from full fork block"))?
1495 .iter()
1496 .take_while_inclusive(|&transaction| transaction.tx_hash() != transaction_hash.0)
1497 .collect::<Vec<_>>();
1498
1499 let force_transactions = filtered_transactions
1501 .iter()
1502 .map(|&transaction| PoolTransaction::try_from(transaction.clone()))
1503 .collect::<Result<Vec<_>, _>>()
1504 .map_err(|e| eyre::eyre!("Err converting to pool transactions {e}"))?;
1505 Ok((transaction_block_number.saturating_sub(1), Some(force_transactions)))
1506 }
1507 }
1508}
1509
1510#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1512pub enum ForkChoice {
1513 Block(i128),
1517 Transaction(TxHash),
1519}
1520
1521impl ForkChoice {
1522 pub fn block_number(&self) -> Option<i128> {
1524 match self {
1525 Self::Block(block_number) => Some(*block_number),
1526 Self::Transaction(_) => None,
1527 }
1528 }
1529
1530 pub fn transaction_hash(&self) -> Option<TxHash> {
1532 match self {
1533 Self::Block(_) => None,
1534 Self::Transaction(transaction_hash) => Some(*transaction_hash),
1535 }
1536 }
1537}
1538
1539impl From<TxHash> for ForkChoice {
1541 fn from(tx_hash: TxHash) -> Self {
1542 Self::Transaction(tx_hash)
1543 }
1544}
1545
1546impl From<u64> for ForkChoice {
1548 fn from(block: u64) -> Self {
1549 Self::Block(block as i128)
1550 }
1551}
1552
1553#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1554pub struct PruneStateHistoryConfig {
1555 pub enabled: bool,
1556 pub max_memory_history: Option<usize>,
1557}
1558
1559impl PruneStateHistoryConfig {
1560 pub fn is_state_history_supported(&self) -> bool {
1562 !self.enabled || self.max_memory_history.is_some()
1563 }
1564
1565 pub fn is_config_enabled(&self) -> bool {
1567 self.enabled
1568 }
1569
1570 pub fn from_args(val: Option<Option<usize>>) -> Self {
1571 val.map(|max_memory_history| Self { enabled: true, max_memory_history }).unwrap_or_default()
1572 }
1573}
1574
1575#[derive(Clone, Debug)]
1577pub struct AccountGenerator {
1578 chain_id: u64,
1579 amount: usize,
1580 phrase: String,
1581 derivation_path: Option<String>,
1582}
1583
1584impl AccountGenerator {
1585 pub fn new(amount: usize) -> Self {
1586 Self {
1587 chain_id: CHAIN_ID,
1588 amount,
1589 phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
1590 derivation_path: None,
1591 }
1592 }
1593
1594 #[must_use]
1595 pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
1596 self.phrase = phrase.into();
1597 self
1598 }
1599
1600 fn get_phrase(&self) -> &str {
1601 &self.phrase
1602 }
1603
1604 #[must_use]
1605 pub fn chain_id(mut self, chain_id: impl Into<u64>) -> Self {
1606 self.chain_id = chain_id.into();
1607 self
1608 }
1609
1610 #[must_use]
1611 pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
1612 let mut derivation_path = derivation_path.into();
1613 if !derivation_path.ends_with('/') {
1614 derivation_path.push('/');
1615 }
1616 self.derivation_path = Some(derivation_path);
1617 self
1618 }
1619
1620 fn get_derivation_path(&self) -> &str {
1621 self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
1622 }
1623}
1624
1625impl AccountGenerator {
1626 pub fn generate(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
1627 let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
1628
1629 let derivation_path = self.get_derivation_path();
1631
1632 let mut wallets = Vec::with_capacity(self.amount);
1633 for idx in 0..self.amount {
1634 let builder =
1635 builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap();
1636 let wallet = builder.build()?.with_chain_id(Some(self.chain_id));
1637 wallets.push(wallet)
1638 }
1639 Ok(wallets)
1640 }
1641}
1642
1643pub fn anvil_dir() -> Option<PathBuf> {
1645 Config::foundry_dir().map(|p| p.join("anvil"))
1646}
1647
1648pub fn anvil_tmp_dir() -> Option<PathBuf> {
1650 anvil_dir().map(|p| p.join("tmp"))
1651}
1652
1653async fn find_latest_fork_block<P: Provider<AnyNetwork>>(
1658 provider: P,
1659) -> Result<u64, TransportError> {
1660 let mut num = provider.get_block_number().await?;
1661
1662 for _ in 0..2 {
1665 if let Some(block) = provider.get_block(num.into()).await?
1666 && !block.header.hash.is_zero()
1667 {
1668 break;
1669 }
1670 num = num.saturating_sub(1)
1672 }
1673
1674 Ok(num)
1675}
1676
1677#[cfg(test)]
1678mod tests {
1679 use super::*;
1680
1681 #[test]
1682 fn test_prune_history() {
1683 let config = PruneStateHistoryConfig::default();
1684 assert!(config.is_state_history_supported());
1685 let config = PruneStateHistoryConfig::from_args(Some(None));
1686 assert!(!config.is_state_history_supported());
1687 let config = PruneStateHistoryConfig::from_args(Some(Some(10)));
1688 assert!(config.is_state_history_supported());
1689 }
1690}