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