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