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