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