1use crate::{
2 EthereumHardfork, FeeManager, PrecompileFactory,
3 eth::{
4 backend::{
5 db::{Db, SerializableState},
6 fork::{ClientFork, ClientForkConfig},
7 genesis::GenesisConfig,
8 mem::fork_db::ForkedDatabase,
9 time::duration_since_unix_epoch,
10 },
11 fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE},
12 pool::transactions::{PoolTransaction, TransactionOrder},
13 },
14 mem::{self, in_memory_db::MemDb},
15};
16use alloy_consensus::BlockHeader;
17use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams};
18use alloy_evm::EvmEnv;
19use alloy_genesis::Genesis;
20use alloy_network::{AnyNetwork, BlockResponse, TransactionResponse};
21use alloy_primitives::{Address, BlockNumber, TxHash, U256, hex, map::HashMap, utils::Unit};
22use alloy_provider::Provider;
23use alloy_rpc_types::BlockNumberOrTag;
24use alloy_signer::Signer;
25use alloy_signer_local::{
26 MnemonicBuilder, PrivateKeySigner,
27 coins_bip39::{English, Mnemonic},
28};
29use alloy_transport::TransportError;
30use anvil_server::ServerConfig;
31use eyre::{Context, Result};
32use foundry_common::{
33 ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT,
34 provider::{ProviderBuilder, RetryProvider},
35};
36use foundry_config::Config;
37use foundry_evm::{
38 backend::{BlockchainDb, BlockchainDbMeta, SharedBackend},
39 constants::DEFAULT_CREATE2_DEPLOYER,
40 hardfork::FoundryHardfork,
41 utils::{
42 apply_chain_and_block_specific_env_changes, block_env_from_header,
43 get_blob_base_fee_update_fraction,
44 },
45};
46use foundry_primitives::FoundryTxEnvelope;
47use itertools::Itertools;
48use parking_lot::RwLock;
49use rand_08::thread_rng;
50use revm::{
51 context::{BlockEnv, CfgEnv},
52 context_interface::block::BlobExcessGasAndPrice,
53 primitives::hardfork::SpecId,
54};
55use serde_json::{Value, json};
56use std::{
57 fmt::Write as FmtWrite,
58 net::{IpAddr, Ipv4Addr},
59 path::PathBuf,
60 sync::Arc,
61 time::Duration,
62};
63use tempo_chainspec::hardfork::TempoHardfork;
64use tokio::sync::RwLock as TokioRwLock;
65use yansi::Paint;
66
67pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
68use foundry_evm::{
69 traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier},
70 utils::get_blob_params,
71};
72use foundry_evm_networks::NetworkConfigs;
73
74pub const NODE_PORT: u16 = 8545;
76pub const CHAIN_ID: u64 = 31337;
78pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
80pub const DEFAULT_SLOTS_IN_AN_EPOCH: u64 = 32;
82pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
84
85pub const DEFAULT_IPC_ENDPOINT: &str =
87 if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
88
89const BANNER: &str = r"
90 _ _
91 (_) | |
92 __ _ _ __ __ __ _ | |
93 / _` | | '_ \ \ \ / / | | | |
94 | (_| | | | | | \ V / | | | |
95 \__,_| |_| |_| \_/ |_| |_|
96";
97
98#[derive(Clone, Debug)]
100pub struct NodeConfig {
101 pub chain_id: Option<u64>,
103 pub gas_limit: Option<u64>,
105 pub disable_block_gas_limit: bool,
107 pub enable_tx_gas_limit: bool,
109 pub gas_price: Option<u128>,
111 pub base_fee: Option<u64>,
113 pub disable_min_priority_fee: bool,
115 pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
117 pub hardfork: Option<FoundryHardfork>,
119 pub genesis_accounts: Vec<PrivateKeySigner>,
121 pub genesis_balance: U256,
123 pub genesis_timestamp: Option<u64>,
125 pub genesis_block_number: Option<u64>,
127 pub signer_accounts: Vec<PrivateKeySigner>,
129 pub block_time: Option<Duration>,
131 pub no_mining: bool,
133 pub mixed_mining: bool,
135 pub port: u16,
137 pub max_transactions: usize,
139 pub fork_urls: Vec<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 pub funded_accounts: HashMap<Address, U256>,
214}
215
216impl NodeConfig {
217 fn as_string(&self, fork: Option<&ClientFork>) -> String {
218 let mut s: String = String::new();
219 let _ = write!(s, "\n{}", BANNER.green());
220 let _ = write!(s, "\n {VERSION_MESSAGE}");
221 let _ = write!(s, "\n {}", "https://github.com/foundry-rs/foundry".green());
222
223 let _ = write!(
224 s,
225 r#"
226
227Available Accounts
228==================
229"#
230 );
231 let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
232 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
233 write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap();
234 }
235
236 let _ = write!(
237 s,
238 r#"
239
240Private Keys
241==================
242"#
243 );
244
245 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
246 let hex = hex::encode(wallet.credential().to_bytes());
247 let _ = write!(s, "\n({idx}) 0x{hex}");
248 }
249
250 if let Some(generator) = &self.account_generator {
251 let _ = write!(
252 s,
253 r#"
254
255Wallet
256==================
257Mnemonic: {}
258Derivation path: {}
259"#,
260 generator.phrase,
261 generator.get_derivation_path()
262 );
263 }
264
265 if let Some(fork) = fork {
266 let _ = write!(
267 s,
268 r#"
269
270Fork
271==================
272Endpoint: {}
273Block number: {}
274Block hash: {:?}
275Chain ID: {}
276"#,
277 fork.eth_rpc_url().as_deref().unwrap_or("none"),
278 fork.block_number(),
279 fork.block_hash(),
280 fork.chain_id()
281 );
282
283 if self.fork_urls.len() > 1 {
284 let _ = writeln!(s, "Endpoints: {}", self.fork_urls.len());
285 for (i, url) in self.fork_urls.iter().enumerate() {
286 let _ = writeln!(s, " ({i}) {url}");
287 }
288 }
289
290 if let Some(tx_hash) = fork.transaction_hash() {
291 let _ = writeln!(s, "Transaction hash: {tx_hash}");
292 }
293 } else {
294 let _ = write!(
295 s,
296 r#"
297
298Chain ID
299==================
300
301{}
302"#,
303 self.get_chain_id().green()
304 );
305 }
306
307 if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) {
308 let _ = write!(
309 s,
310 r#"
311Gas Price
312==================
313
314{}
315"#,
316 self.get_gas_price().green()
317 );
318 } else {
319 let _ = write!(
320 s,
321 r#"
322Base Fee
323==================
324
325{}
326"#,
327 self.get_base_fee().green()
328 );
329 }
330
331 let _ = write!(
332 s,
333 r#"
334Gas Limit
335==================
336
337{}
338"#,
339 {
340 if self.disable_block_gas_limit {
341 "Disabled".to_string()
342 } else {
343 self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| {
344 if self.fork_choice.is_some() {
345 "Forked".to_string()
346 } else {
347 DEFAULT_GAS_LIMIT.to_string()
348 }
349 })
350 }
351 }
352 .green()
353 );
354
355 let _ = write!(
356 s,
357 r#"
358Genesis Timestamp
359==================
360
361{}
362"#,
363 self.get_genesis_timestamp().green()
364 );
365
366 let _ = write!(
367 s,
368 r#"
369Genesis Number
370==================
371
372{}
373"#,
374 self.get_genesis_number().green()
375 );
376
377 s
378 }
379
380 fn as_json(&self, fork: Option<&ClientFork>) -> Value {
381 let mut wallet_description = HashMap::new();
382 let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
383 let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
384
385 for wallet in &self.genesis_accounts {
386 available_accounts.push(format!("{:?}", wallet.address()));
387 private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes())));
388 }
389
390 if let Some(generator) = &self.account_generator {
391 let phrase = generator.get_phrase().to_string();
392 let derivation_path = generator.get_derivation_path().to_string();
393
394 wallet_description.insert("derivation_path".to_string(), derivation_path);
395 wallet_description.insert("mnemonic".to_string(), phrase);
396 };
397
398 let gas_limit = match self.gas_limit {
399 Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()),
401 Some(limit) => Some(limit.to_string()),
402 _ => None,
403 };
404
405 if let Some(fork) = fork {
406 json!({
407 "available_accounts": available_accounts,
408 "private_keys": private_keys,
409 "endpoint": fork.eth_rpc_url().unwrap_or_default(),
410 "block_number": fork.block_number(),
411 "block_hash": fork.block_hash(),
412 "chain_id": fork.chain_id(),
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 })
418 } else {
419 json!({
420 "available_accounts": available_accounts,
421 "private_keys": private_keys,
422 "wallet": wallet_description,
423 "base_fee": format!("{}", self.get_base_fee()),
424 "gas_price": format!("{}", self.get_gas_price()),
425 "gas_limit": gas_limit,
426 "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
427 })
428 }
429 }
430}
431
432impl NodeConfig {
433 #[doc(hidden)]
436 pub fn test() -> Self {
437 Self { enable_tracing: true, port: 0, silent: true, ..Default::default() }
438 }
439
440 #[doc(hidden)]
442 pub fn test_tempo() -> Self {
443 Self { networks: NetworkConfigs::with_tempo(), ..Self::test() }
444 }
445
446 pub fn empty_state() -> Self {
448 Self {
449 genesis_accounts: vec![],
450 signer_accounts: vec![],
451 disable_default_create2_deployer: true,
452 ..Default::default()
453 }
454 }
455}
456
457impl Default for NodeConfig {
458 fn default() -> Self {
459 let genesis_accounts = AccountGenerator::new(10)
461 .phrase(DEFAULT_MNEMONIC)
462 .generate()
463 .expect("Invalid mnemonic.");
464 Self {
465 chain_id: None,
466 gas_limit: None,
467 disable_block_gas_limit: false,
468 enable_tx_gas_limit: false,
469 gas_price: None,
470 hardfork: None,
471 signer_accounts: genesis_accounts.clone(),
472 genesis_timestamp: None,
473 genesis_block_number: None,
474 genesis_accounts,
475 genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
477 block_time: None,
478 no_mining: false,
479 mixed_mining: false,
480 port: NODE_PORT,
481 max_transactions: 1_000,
482 fork_urls: vec![],
483 fork_choice: None,
484 account_generator: None,
485 base_fee: None,
486 disable_min_priority_fee: false,
487 blob_excess_gas_and_price: None,
488 enable_tracing: true,
489 enable_steps_tracing: false,
490 print_logs: true,
491 print_traces: false,
492 enable_auto_impersonate: false,
493 no_storage_caching: false,
494 server_config: Default::default(),
495 host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
496 transaction_order: Default::default(),
497 config_out: None,
498 genesis: None,
499 fork_request_timeout: REQUEST_TIMEOUT,
500 fork_headers: vec![],
501 fork_request_retries: 5,
502 fork_retry_backoff: Duration::from_millis(1_000),
503 fork_chain_id: None,
504 compute_units_per_second: ALCHEMY_FREE_TIER_CUPS,
506 ipc_path: None,
507 code_size_limit: None,
508 prune_history: Default::default(),
509 max_persisted_states: None,
510 init_state: None,
511 transaction_block_keeper: None,
512 disable_default_create2_deployer: false,
513 disable_pool_balance_checks: false,
514 slots_in_an_epoch: DEFAULT_SLOTS_IN_AN_EPOCH,
515 memory_limit: None,
516 precompile_factory: None,
517 networks: Default::default(),
518 silent: false,
519 cache_path: None,
520 funded_accounts: HashMap::default(),
521 }
522 }
523}
524
525impl NodeConfig {
526 #[must_use]
528 pub const fn with_memory_limit(mut self, mems_value: Option<u64>) -> Self {
529 self.memory_limit = mems_value;
530 self
531 }
532
533 pub fn get_base_fee(&self) -> u64 {
537 let default = if self.networks.is_tempo() {
538 TempoHardfork::from(self.get_hardfork()).base_fee()
539 } else {
540 INITIAL_BASE_FEE
541 };
542 self.base_fee
543 .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64)))
544 .unwrap_or(default)
545 }
546
547 pub fn get_gas_price(&self) -> u128 {
551 let default = if self.networks.is_tempo() {
552 TempoHardfork::from(self.get_hardfork()).base_fee() as u128
553 } else {
554 INITIAL_GAS_PRICE
555 };
556 self.gas_price.unwrap_or(default)
557 }
558
559 pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice {
560 if let Some(value) = self.blob_excess_gas_and_price {
561 value
562 } else {
563 let excess_blob_gas =
564 self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0);
565 BlobExcessGasAndPrice::new(
566 excess_blob_gas,
567 get_blob_base_fee_update_fraction(
568 self.get_chain_id(),
569 self.get_genesis_timestamp(),
570 ),
571 )
572 }
573 }
574
575 pub fn get_blob_params(&self) -> BlobParams {
577 get_blob_params(self.get_chain_id(), self.get_genesis_timestamp())
578 }
579
580 pub fn get_hardfork(&self) -> FoundryHardfork {
582 if let Some(hardfork) = self.hardfork {
583 return hardfork;
584 }
585 if self.networks.is_tempo()
586 && let Some(hardfork) = TempoHardfork::from_chain_and_timestamp(
587 self.get_chain_id(),
588 self.get_genesis_timestamp(),
589 )
590 {
591 return hardfork.into();
592 }
593 #[cfg(feature = "optimism")]
594 if self.networks.is_optimism() {
595 return foundry_evm::hardforks::OpHardfork::default().into();
596 }
597 if self.networks.is_tempo() {
598 return TempoHardfork::default().into();
599 }
600 EthereumHardfork::default().into()
601 }
602
603 #[must_use]
605 pub const fn with_code_size_limit(mut self, code_size_limit: Option<usize>) -> Self {
606 self.code_size_limit = code_size_limit;
607 self
608 }
609 #[must_use]
611 pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self {
612 if disable_code_size_limit {
613 self.code_size_limit = Some(usize::MAX);
614 }
615 self
616 }
617
618 #[must_use]
620 pub fn with_init_state(mut self, init_state: Option<SerializableState>) -> Self {
621 self.init_state = init_state;
622 self
623 }
624
625 #[must_use]
627 #[cfg(feature = "cmd")]
628 pub fn with_init_state_path(mut self, path: impl AsRef<std::path::Path>) -> Self {
629 self.init_state = crate::cmd::StateFile::parse_path(path).ok().and_then(|file| file.state);
630 self
631 }
632
633 #[must_use]
635 pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
636 self.set_chain_id(chain_id);
637 self
638 }
639
640 pub fn get_chain_id(&self) -> u64 {
642 self.chain_id
643 .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
644 .unwrap_or(CHAIN_ID)
645 }
646
647 pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
649 self.chain_id = chain_id.map(Into::into);
650 let chain_id = self.get_chain_id();
651 self.networks = self.networks.with_chain_id(chain_id);
652 self.genesis_accounts.iter_mut().for_each(|wallet| {
653 *wallet = wallet.clone().with_chain_id(Some(chain_id));
654 });
655 self.signer_accounts.iter_mut().for_each(|wallet| {
656 *wallet = wallet.clone().with_chain_id(Some(chain_id));
657 })
658 }
659
660 #[must_use]
662 pub const fn with_gas_limit(mut self, gas_limit: Option<u64>) -> Self {
663 self.gas_limit = gas_limit;
664 self
665 }
666
667 #[must_use]
671 pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self {
672 self.disable_block_gas_limit = disable_block_gas_limit;
673 self
674 }
675
676 #[must_use]
680 pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self {
681 self.enable_tx_gas_limit = enable_tx_gas_limit;
682 self
683 }
684
685 #[must_use]
687 pub const fn with_gas_price(mut self, gas_price: Option<u128>) -> Self {
688 self.gas_price = gas_price;
689 self
690 }
691
692 #[must_use]
694 pub fn set_pruned_history(mut self, prune_history: Option<Option<usize>>) -> Self {
695 self.prune_history = PruneStateHistoryConfig::from_args(prune_history);
696 self
697 }
698
699 #[must_use]
701 pub fn with_max_persisted_states<U: Into<usize>>(
702 mut self,
703 max_persisted_states: Option<U>,
704 ) -> Self {
705 self.max_persisted_states = max_persisted_states.map(Into::into);
706 self
707 }
708
709 #[must_use]
711 pub const fn with_max_transactions(mut self, max_transactions: Option<usize>) -> Self {
712 if let Some(max_transactions) = max_transactions {
713 self.max_transactions = max_transactions;
714 }
715 self
716 }
717
718 #[must_use]
720 pub fn with_transaction_block_keeper<U: Into<usize>>(
721 mut self,
722 transaction_block_keeper: Option<U>,
723 ) -> Self {
724 self.transaction_block_keeper = transaction_block_keeper.map(Into::into);
725 self
726 }
727
728 #[must_use]
730 pub const fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
731 self.base_fee = base_fee;
732 self
733 }
734
735 #[must_use]
737 pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self {
738 self.disable_min_priority_fee = disable_min_priority_fee;
739 self
740 }
741
742 #[must_use]
744 pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
745 self.genesis = genesis;
746 self
747 }
748
749 pub fn get_genesis_timestamp(&self) -> u64 {
751 self.genesis_timestamp
752 .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
753 .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
754 }
755
756 #[must_use]
758 pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
759 if let Some(timestamp) = timestamp {
760 self.genesis_timestamp = Some(timestamp.into());
761 }
762 self
763 }
764
765 #[must_use]
767 pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
768 if let Some(number) = number {
769 self.genesis_block_number = Some(number.into());
770 }
771 self
772 }
773
774 pub fn get_genesis_number(&self) -> u64 {
776 self.genesis_block_number
777 .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
778 .unwrap_or(0)
779 }
780
781 #[must_use]
783 pub const fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
784 self.hardfork = hardfork;
785 self
786 }
787
788 #[must_use]
790 pub fn with_genesis_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
791 self.genesis_accounts = accounts;
792 self
793 }
794
795 #[must_use]
797 pub fn with_signer_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
798 self.signer_accounts = accounts;
799 self
800 }
801
802 pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
805 let accounts = generator.generate()?;
806 self.account_generator = Some(generator);
807 Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
808 }
809
810 #[must_use]
812 pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
813 self.genesis_balance = balance.into();
814 self
815 }
816
817 #[must_use]
819 pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
820 self.block_time = block_time.map(Into::into);
821 self
822 }
823
824 #[must_use]
825 pub fn with_mixed_mining<D: Into<Duration>>(
826 mut self,
827 mixed_mining: bool,
828 block_time: Option<D>,
829 ) -> Self {
830 self.block_time = block_time.map(Into::into);
831 self.mixed_mining = mixed_mining;
832 self
833 }
834
835 #[must_use]
837 pub const fn with_no_mining(mut self, no_mining: bool) -> Self {
838 self.no_mining = no_mining;
839 self
840 }
841
842 #[must_use]
844 pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self {
845 self.slots_in_an_epoch = slots_in_an_epoch;
846 self
847 }
848
849 #[must_use]
851 pub const fn with_port(mut self, port: u16) -> Self {
852 self.port = port;
853 self
854 }
855
856 #[must_use]
863 pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
864 self.ipc_path = ipc_path;
865 self
866 }
867
868 #[must_use]
870 pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
871 self.config_out = config_out;
872 self
873 }
874
875 #[must_use]
876 pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self {
877 self.no_storage_caching = no_storage_caching;
878 self
879 }
880
881 #[must_use]
883 pub fn with_eth_rpc_url<U: Into<String>>(mut self, eth_rpc_url: Option<U>) -> Self {
884 if let Some(url) = eth_rpc_url {
885 self.fork_urls = vec![url.into()];
886 }
887 self
888 }
889
890 #[must_use]
892 pub fn with_fork_urls(mut self, fork_urls: Vec<String>) -> Self {
893 self.fork_urls = fork_urls;
894 self
895 }
896
897 #[must_use]
899 pub fn with_fork_block_number<U: Into<u64>>(self, fork_block_number: Option<U>) -> Self {
900 self.with_fork_choice(fork_block_number.map(Into::into))
901 }
902
903 #[must_use]
905 pub fn with_fork_transaction_hash<U: Into<TxHash>>(
906 self,
907 fork_transaction_hash: Option<U>,
908 ) -> Self {
909 self.with_fork_choice(fork_transaction_hash.map(Into::into))
910 }
911
912 #[must_use]
914 pub fn with_fork_choice<U: Into<ForkChoice>>(mut self, fork_choice: Option<U>) -> Self {
915 self.fork_choice = fork_choice.map(Into::into);
916 self
917 }
918
919 #[must_use]
921 pub const fn with_fork_chain_id(mut self, fork_chain_id: Option<U256>) -> Self {
922 self.fork_chain_id = fork_chain_id;
923 self
924 }
925
926 #[must_use]
928 pub fn with_fork_headers(mut self, headers: Vec<String>) -> Self {
929 self.fork_headers = headers;
930 self
931 }
932
933 #[must_use]
935 pub const fn fork_request_timeout(mut self, fork_request_timeout: Option<Duration>) -> Self {
936 if let Some(fork_request_timeout) = fork_request_timeout {
937 self.fork_request_timeout = fork_request_timeout;
938 }
939 self
940 }
941
942 #[must_use]
944 pub const fn fork_request_retries(mut self, fork_request_retries: Option<u32>) -> Self {
945 if let Some(fork_request_retries) = fork_request_retries {
946 self.fork_request_retries = fork_request_retries;
947 }
948 self
949 }
950
951 #[must_use]
953 pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option<Duration>) -> Self {
954 if let Some(fork_retry_backoff) = fork_retry_backoff {
955 self.fork_retry_backoff = fork_retry_backoff;
956 }
957 self
958 }
959
960 #[must_use]
964 pub const fn fork_compute_units_per_second(
965 mut self,
966 compute_units_per_second: Option<u64>,
967 ) -> Self {
968 if let Some(compute_units_per_second) = compute_units_per_second {
969 self.compute_units_per_second = compute_units_per_second;
970 }
971 self
972 }
973
974 #[must_use]
976 pub const fn with_tracing(mut self, enable_tracing: bool) -> Self {
977 self.enable_tracing = enable_tracing;
978 self
979 }
980
981 #[must_use]
983 pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self {
984 self.enable_steps_tracing = enable_steps_tracing;
985 self
986 }
987
988 #[must_use]
990 pub const fn with_print_logs(mut self, print_logs: bool) -> Self {
991 self.print_logs = print_logs;
992 self
993 }
994
995 #[must_use]
997 pub const fn with_print_traces(mut self, print_traces: bool) -> Self {
998 self.print_traces = print_traces;
999 self
1000 }
1001
1002 #[must_use]
1004 pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
1005 self.enable_auto_impersonate = enable_auto_impersonate;
1006 self
1007 }
1008
1009 #[must_use]
1010 pub fn with_server_config(mut self, config: ServerConfig) -> Self {
1011 self.server_config = config;
1012 self
1013 }
1014
1015 #[must_use]
1017 pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
1018 self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
1019 self
1020 }
1021
1022 #[must_use]
1023 pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self {
1024 self.transaction_order = transaction_order;
1025 self
1026 }
1027
1028 pub fn get_ipc_path(&self) -> Option<String> {
1030 match &self.ipc_path {
1031 Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
1032 None => None,
1033 }
1034 }
1035
1036 pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
1038 if let Some(path) = &self.config_out {
1039 let value = self.as_json(fork);
1040 foundry_common::fs::write_json_file(path, &value).wrap_err("failed writing JSON")?;
1041 }
1042 if !self.silent {
1043 sh_println!("{}", self.as_string(fork))?;
1044 }
1045 Ok(())
1046 }
1047
1048 pub fn block_cache_path(&self, block: u64) -> Option<PathBuf> {
1052 if self.no_storage_caching || self.fork_urls.is_empty() {
1053 return None;
1054 }
1055 let chain_id = self.get_chain_id();
1056
1057 Config::foundry_block_cache_file(chain_id, block)
1058 }
1059
1060 #[must_use]
1062 pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self {
1063 self.disable_default_create2_deployer = yes;
1064 self
1065 }
1066
1067 #[must_use]
1069 pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self {
1070 self.disable_pool_balance_checks = yes;
1071 self
1072 }
1073
1074 #[must_use]
1076 pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
1077 self.precompile_factory = Some(Arc::new(factory));
1078 self
1079 }
1080
1081 #[must_use]
1083 pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self {
1084 self.networks = networks;
1085 self
1086 }
1087
1088 #[must_use]
1090 pub fn with_tempo(mut self) -> Self {
1091 self.networks = NetworkConfigs::with_tempo();
1092 self
1093 }
1094
1095 #[cfg(feature = "optimism")]
1097 #[must_use]
1098 pub fn with_optimism(mut self) -> Self {
1099 self.networks = NetworkConfigs::with_optimism();
1100 self
1101 }
1102
1103 #[must_use]
1105 pub const fn silent(self) -> Self {
1106 self.set_silent(true)
1107 }
1108
1109 #[must_use]
1110 pub const fn set_silent(mut self, silent: bool) -> Self {
1111 self.silent = silent;
1112 self
1113 }
1114
1115 #[must_use]
1120 pub fn with_cache_path(mut self, cache_path: Option<PathBuf>) -> Self {
1121 self.cache_path = cache_path;
1122 self
1123 }
1124
1125 #[must_use]
1127 pub fn with_funded_accounts(mut self, accounts: HashMap<Address, U256>) -> Self {
1128 self.funded_accounts = accounts;
1129 self
1130 }
1131
1132 pub(crate) async fn setup<N>(&mut self) -> Result<mem::Backend<N>>
1137 where
1138 N: alloy_network::Network<
1139 TxEnvelope = foundry_primitives::FoundryTxEnvelope,
1140 ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope,
1141 >,
1142 {
1143 let mut cfg = CfgEnv::default();
1146 cfg.spec = self.get_hardfork().into();
1147
1148 cfg.chain_id = self.get_chain_id();
1149 cfg.limit_contract_code_size = self.code_size_limit;
1150 cfg.disable_eip3607 = true;
1154 cfg.disable_block_gas_limit = self.disable_block_gas_limit;
1155
1156 if !self.enable_tx_gas_limit {
1157 cfg.tx_gas_limit_cap = Some(u64::MAX);
1158 }
1159
1160 if let Some(value) = self.memory_limit {
1161 cfg.memory_limit = value;
1162 }
1163
1164 let spec_id = cfg.spec;
1165 let mut evm_env = EvmEnv::new(
1166 cfg,
1167 BlockEnv {
1168 gas_limit: self.gas_limit(),
1169 basefee: self.get_base_fee(),
1170 ..Default::default()
1171 },
1172 );
1173
1174 let base_fee_params: BaseFeeParams =
1175 self.networks.base_fee_params(self.get_genesis_timestamp());
1176
1177 let fees = FeeManager::new(
1178 spec_id,
1179 self.get_base_fee(),
1180 !self.disable_min_priority_fee,
1181 self.get_gas_price(),
1182 self.get_blob_excess_gas_and_price(),
1183 self.get_blob_params(),
1184 base_fee_params,
1185 );
1186
1187 let (db, fork): (Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>) =
1188 if let Some(eth_rpc_url) = self.fork_urls.first().cloned() {
1189 self.setup_fork_db(eth_rpc_url, &mut evm_env, &fees).await?
1190 } else {
1191 (Arc::new(TokioRwLock::new(Box::<MemDb>::default())), None)
1192 };
1193
1194 if let Some(ref genesis) = self.genesis {
1196 if self.chain_id.is_none() {
1199 evm_env.cfg_env.chain_id = genesis.config.chain_id;
1200 }
1201 evm_env.block_env.timestamp = U256::from(genesis.timestamp);
1202 if let Some(base_fee) = genesis.base_fee_per_gas {
1203 evm_env.block_env.basefee = base_fee.try_into()?;
1204 }
1205 if let Some(number) = genesis.number {
1206 evm_env.block_env.number = U256::from(number);
1207 }
1208 evm_env.block_env.beneficiary = genesis.coinbase;
1209 }
1210
1211 let genesis = GenesisConfig {
1212 number: self.get_genesis_number(),
1213 timestamp: self.get_genesis_timestamp(),
1214 balance: self.genesis_balance,
1215 accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(),
1216 genesis_init: self.genesis.clone(),
1217 };
1218
1219 let mut decoder_builder = CallTraceDecoderBuilder::new();
1220 if self.print_traces {
1221 if let Ok(identifier) = SignaturesIdentifier::new(false) {
1223 debug!(target: "node", "using signature identifier");
1224 decoder_builder = decoder_builder.with_signature_identifier(identifier);
1225 }
1226 }
1227
1228 let backend = mem::Backend::with_genesis(
1230 db,
1231 Arc::new(RwLock::new(evm_env)),
1232 self.networks,
1233 genesis,
1234 fees,
1235 Arc::new(RwLock::new(fork)),
1236 self.enable_steps_tracing,
1237 self.print_logs,
1238 self.print_traces,
1239 Arc::new(decoder_builder.build()),
1240 self.prune_history,
1241 self.max_persisted_states,
1242 self.transaction_block_keeper,
1243 self.block_time,
1244 self.cache_path.clone(),
1245 Arc::new(TokioRwLock::new(self.clone())),
1246 )
1247 .await?;
1248
1249 if !self.disable_default_create2_deployer && self.fork_urls.is_empty() {
1252 backend
1253 .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER)
1254 .await
1255 .wrap_err("failed to create default create2 deployer")?;
1256 }
1257
1258 if !self.funded_accounts.is_empty() {
1259 for (address, balance) in &self.funded_accounts {
1260 backend
1261 .set_balance(*address, *balance)
1262 .await
1263 .wrap_err_with(|| format!("failed to fund account {address}"))?;
1264 }
1265 }
1266
1267 Ok(backend)
1268 }
1269
1270 pub async fn setup_fork_db(
1277 &mut self,
1278 eth_rpc_url: String,
1279 evm_env: &mut EvmEnv,
1280 fees: &FeeManager,
1281 ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>)> {
1282 let (db, config) = self.setup_fork_db_config(eth_rpc_url, evm_env, fees).await?;
1283 let db: Arc<TokioRwLock<Box<dyn Db>>> = Arc::new(TokioRwLock::new(Box::new(db)));
1284 let fork = ClientFork::new(config, Arc::clone(&db));
1285 Ok((db, Some(fork)))
1286 }
1287
1288 pub async fn setup_fork_db_config(
1294 &mut self,
1295 eth_rpc_url: String,
1296 evm_env: &mut EvmEnv,
1297 fees: &FeeManager,
1298 ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig)> {
1299 debug!(target: "node", ?eth_rpc_url, "setting up fork db");
1300
1301 let provider = Arc::new(
1305 ProviderBuilder::new(ð_rpc_url)
1306 .timeout(self.fork_request_timeout)
1307 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1308 .compute_units_per_second(self.compute_units_per_second)
1309 .max_retry(self.fork_request_retries)
1310 .headers(self.fork_headers.clone())
1311 .build()
1312 .wrap_err("failed to establish provider to fork url")?,
1313 );
1314
1315 let (fork_block_number, fork_chain_id, force_transactions) = if let Some(fork_choice) =
1316 &self.fork_choice
1317 {
1318 let (fork_block_number, force_transactions) =
1319 derive_block_and_transactions(fork_choice, &provider).await.wrap_err(
1320 "failed to derive fork block number and force transactions from fork choice",
1321 )?;
1322 let chain_id = if let Some(chain_id) = self.fork_chain_id {
1323 Some(chain_id)
1324 } else if self.hardfork.is_none() {
1325 let chain_id =
1326 provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?;
1327 Some(U256::from(chain_id))
1328 } else {
1329 None
1330 };
1331
1332 (fork_block_number, chain_id, force_transactions)
1333 } else {
1334 let bn = find_latest_fork_block(&provider)
1336 .await
1337 .wrap_err("failed to get fork block number")?;
1338 (bn, None, None)
1339 };
1340
1341 let block = provider
1342 .get_block(BlockNumberOrTag::Number(fork_block_number).into())
1343 .await
1344 .wrap_err("failed to get fork block")?;
1345
1346 let block = if let Some(block) = block {
1347 block
1348 } else {
1349 if let Ok(latest_block) = provider.get_block_number().await {
1350 let mut message = format!(
1351 "Failed to get block for block number: {fork_block_number}\n\
1352latest block number: {latest_block}"
1353 );
1354 if fork_block_number <= latest_block {
1358 message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}"));
1359 }
1360 eyre::bail!("{message}");
1361 }
1362 eyre::bail!("failed to get block for block number: {fork_block_number}")
1363 };
1364
1365 let gas_limit = self.fork_gas_limit(&block);
1366 self.gas_limit = Some(gas_limit);
1367
1368 evm_env.block_env = BlockEnv {
1369 gas_limit,
1370 beneficiary: evm_env.block_env.beneficiary,
1372 basefee: evm_env.block_env.basefee,
1373 ..block_env_from_header(&block.header)
1374 };
1375
1376 let chain_id = if let Some(chain_id) = self.chain_id {
1378 chain_id
1379 } else {
1380 let chain_id = if let Some(fork_chain_id) = fork_chain_id {
1381 fork_chain_id.to()
1382 } else {
1383 provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?
1384 };
1385
1386 self.set_chain_id(Some(chain_id));
1388 evm_env.cfg_env.chain_id = chain_id;
1389 chain_id
1390 };
1391
1392 if self.hardfork.is_none()
1394 && let Some(hardfork) =
1395 FoundryHardfork::from_chain_and_timestamp(chain_id, block.header.timestamp())
1396 {
1397 evm_env.cfg_env.spec = SpecId::from(hardfork);
1398 self.hardfork = Some(hardfork);
1399 }
1400
1401 if self.base_fee.is_none()
1403 && let Some(base_fee) = block.header.base_fee_per_gas()
1404 {
1405 self.base_fee = Some(base_fee);
1406 evm_env.block_env.basefee = base_fee;
1407 let next_block_base_fee = fees.get_next_block_base_fee_per_gas(
1410 block.header.gas_used(),
1411 gas_limit,
1412 block.header.base_fee_per_gas().unwrap_or_default(),
1413 );
1414
1415 fees.set_base_fee(next_block_base_fee);
1417 }
1418
1419 if let (Some(blob_excess_gas), Some(blob_gas_used)) =
1420 (block.header.excess_blob_gas(), block.header.blob_gas_used())
1421 {
1422 let blob_params = get_blob_params(chain_id, block.header.timestamp());
1424
1425 evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
1426 blob_excess_gas,
1427 blob_params.update_fraction as u64,
1428 ));
1429
1430 fees.set_blob_params(blob_params);
1431
1432 let next_block_blob_excess_gas =
1433 fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used);
1434 fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
1435 next_block_blob_excess_gas,
1436 blob_params.update_fraction as u64,
1437 ));
1438 }
1439
1440 if self.gas_price.is_none()
1442 && let Ok(gas_price) = provider.get_gas_price().await
1443 {
1444 self.gas_price = Some(gas_price);
1445 fees.set_gas_price(gas_price);
1446 }
1447
1448 let block_hash = block.header.hash;
1449
1450 let override_chain_id = self.chain_id;
1451 apply_chain_and_block_specific_env_changes::<AnyNetwork, _, _>(
1453 evm_env,
1454 &block,
1455 self.networks,
1456 );
1457
1458 let meta = BlockchainDbMeta::new(evm_env.block_env.clone(), eth_rpc_url.clone());
1459 let block_chain_db = if self.fork_chain_id.is_some() {
1460 BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number))
1461 } else {
1462 BlockchainDb::new(meta, self.block_cache_path(fork_block_number))
1463 };
1464
1465 let provider = if self.fork_urls.len() > 1 {
1469 debug!(target: "node", urls=?self.fork_urls, "using multi-endpoint round-robin provider");
1470 Arc::new(
1471 ProviderBuilder::new(ð_rpc_url)
1472 .timeout(self.fork_request_timeout)
1473 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1474 .compute_units_per_second(self.compute_units_per_second)
1475 .max_retry(self.fork_request_retries)
1476 .headers(self.fork_headers.clone())
1477 .build_fallback(self.fork_urls.clone())
1478 .wrap_err("failed to establish round-robin provider to fork urls")?,
1479 )
1480 } else {
1481 provider
1482 };
1483
1484 let backend = SharedBackend::spawn_backend(
1487 Arc::clone(&provider),
1488 block_chain_db.clone(),
1489 Some(fork_block_number.into()),
1490 )
1491 .await;
1492
1493 let config = ClientForkConfig {
1494 fork_urls: self.fork_urls.clone(),
1495 block_number: fork_block_number,
1496 block_hash,
1497 transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()),
1498 provider,
1499 chain_id,
1500 override_chain_id,
1501 hardfork: self.hardfork,
1502 timestamp: block.header.timestamp(),
1503 base_fee: block.header.base_fee_per_gas().map(|g| g as u128),
1504 timeout: self.fork_request_timeout,
1505 retries: self.fork_request_retries,
1506 backoff: self.fork_retry_backoff,
1507 compute_units_per_second: self.compute_units_per_second,
1508 headers: self.fork_headers.clone(),
1509 total_difficulty: block.header.total_difficulty.unwrap_or_default(),
1510 blob_gas_used: block.header.blob_gas_used().map(|g| g as u128),
1511 blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price,
1512 force_transactions,
1513 };
1514
1515 debug!(target: "node", fork_number=config.block_number, fork_hash=%config.block_hash, "set up fork db");
1516
1517 let mut db = ForkedDatabase::new(backend, block_chain_db);
1518
1519 db.insert_block_hash(U256::from(config.block_number), config.block_hash);
1521
1522 Ok((db, config))
1523 }
1524
1525 pub(crate) fn fork_gas_limit<B: BlockResponse<Header: BlockHeader>>(&self, block: &B) -> u64 {
1529 if !self.disable_block_gas_limit {
1530 if let Some(gas_limit) = self.gas_limit {
1531 return gas_limit;
1532 } else if block.header().gas_limit() > 0 {
1533 return block.header().gas_limit();
1534 }
1535 }
1536
1537 u64::MAX
1538 }
1539
1540 pub(crate) fn gas_limit(&self) -> u64 {
1544 if self.disable_block_gas_limit {
1545 return u64::MAX;
1546 }
1547
1548 self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)
1549 }
1550}
1551
1552async fn derive_block_and_transactions(
1557 fork_choice: &ForkChoice,
1558 provider: &Arc<RetryProvider>,
1559) -> eyre::Result<(BlockNumber, Option<Vec<PoolTransaction<FoundryTxEnvelope>>>)> {
1560 match fork_choice {
1561 ForkChoice::Block(block_number) => {
1562 let block_number = *block_number;
1563 if block_number >= 0 {
1564 return Ok((block_number as u64, None));
1565 }
1566 let latest = provider.get_block_number().await?;
1568
1569 Ok((block_number.saturating_add(latest as i128) as u64, None))
1570 }
1571 ForkChoice::Transaction(transaction_hash) => {
1572 let transaction = provider
1574 .get_transaction_by_hash(transaction_hash.0.into())
1575 .await?
1576 .ok_or_else(|| eyre::eyre!("failed to get fork transaction by hash"))?;
1577 let transaction_block_number = transaction.block_number().ok_or_else(|| {
1578 eyre::eyre!("fork transaction is not mined yet (no block number)")
1579 })?;
1580
1581 let transaction_block = provider
1583 .get_block_by_number(transaction_block_number.into())
1584 .full()
1585 .await?
1586 .ok_or_else(|| eyre::eyre!("failed to get fork block by number"))?;
1587
1588 let filtered_transactions = transaction_block
1590 .transactions
1591 .as_transactions()
1592 .ok_or_else(|| eyre::eyre!("failed to get transactions from full fork block"))?
1593 .iter()
1594 .take_while_inclusive(|&transaction| transaction.tx_hash() != transaction_hash.0)
1595 .collect::<Vec<_>>();
1596
1597 let force_transactions = filtered_transactions
1599 .iter()
1600 .map(|&transaction| PoolTransaction::try_from(transaction.clone()))
1601 .collect::<Result<Vec<_>, _>>()
1602 .map_err(|e| eyre::eyre!("Err converting to pool transactions {e}"))?;
1603 Ok((transaction_block_number.saturating_sub(1), Some(force_transactions)))
1604 }
1605 }
1606}
1607
1608#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1610pub enum ForkChoice {
1611 Block(i128),
1615 Transaction(TxHash),
1617}
1618
1619impl ForkChoice {
1620 pub const fn block_number(&self) -> Option<i128> {
1622 match self {
1623 Self::Block(block_number) => Some(*block_number),
1624 Self::Transaction(_) => None,
1625 }
1626 }
1627
1628 pub const fn transaction_hash(&self) -> Option<TxHash> {
1630 match self {
1631 Self::Block(_) => None,
1632 Self::Transaction(transaction_hash) => Some(*transaction_hash),
1633 }
1634 }
1635}
1636
1637impl From<TxHash> for ForkChoice {
1639 fn from(tx_hash: TxHash) -> Self {
1640 Self::Transaction(tx_hash)
1641 }
1642}
1643
1644impl From<u64> for ForkChoice {
1646 fn from(block: u64) -> Self {
1647 Self::Block(block as i128)
1648 }
1649}
1650
1651#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1652pub struct PruneStateHistoryConfig {
1653 pub enabled: bool,
1654 pub max_memory_history: Option<usize>,
1655}
1656
1657impl PruneStateHistoryConfig {
1658 pub const fn is_state_history_supported(&self) -> bool {
1660 if !self.enabled {
1661 return true;
1662 }
1663
1664 match self.max_memory_history {
1665 Some(limit) => limit > 0,
1666 None => false,
1667 }
1668 }
1669
1670 pub const fn is_config_enabled(&self) -> bool {
1672 self.enabled
1673 }
1674
1675 pub fn from_args(val: Option<Option<usize>>) -> Self {
1676 val.map(|max_memory_history| Self {
1677 enabled: true,
1678 max_memory_history: max_memory_history.filter(|limit| *limit > 0),
1679 })
1680 .unwrap_or_default()
1681 }
1682}
1683
1684#[derive(Clone, Debug)]
1686pub struct AccountGenerator {
1687 chain_id: u64,
1688 amount: usize,
1689 phrase: String,
1690 derivation_path: Option<String>,
1691}
1692
1693impl AccountGenerator {
1694 pub fn new(amount: usize) -> Self {
1695 Self {
1696 chain_id: CHAIN_ID,
1697 amount,
1698 phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
1699 derivation_path: None,
1700 }
1701 }
1702
1703 #[must_use]
1704 pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
1705 self.phrase = phrase.into();
1706 self
1707 }
1708
1709 fn get_phrase(&self) -> &str {
1710 &self.phrase
1711 }
1712
1713 #[must_use]
1714 pub fn chain_id(mut self, chain_id: impl Into<u64>) -> Self {
1715 self.chain_id = chain_id.into();
1716 self
1717 }
1718
1719 #[must_use]
1720 pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
1721 let mut derivation_path = derivation_path.into();
1722 if !derivation_path.ends_with('/') {
1723 derivation_path.push('/');
1724 }
1725 self.derivation_path = Some(derivation_path);
1726 self
1727 }
1728
1729 fn get_derivation_path(&self) -> &str {
1730 self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
1731 }
1732}
1733
1734impl AccountGenerator {
1735 pub fn generate(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
1736 let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
1737
1738 let derivation_path = self.get_derivation_path();
1740
1741 let mut wallets = Vec::with_capacity(self.amount);
1742 for idx in 0..self.amount {
1743 let builder =
1744 builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap();
1745 let wallet = builder.build()?.with_chain_id(Some(self.chain_id));
1746 wallets.push(wallet)
1747 }
1748 Ok(wallets)
1749 }
1750}
1751
1752pub fn anvil_dir() -> Option<PathBuf> {
1754 Config::foundry_dir().map(|p| p.join("anvil"))
1755}
1756
1757pub fn anvil_tmp_dir() -> Option<PathBuf> {
1759 anvil_dir().map(|p| p.join("tmp"))
1760}
1761
1762async fn find_latest_fork_block<P: Provider<AnyNetwork>>(
1767 provider: P,
1768) -> Result<u64, TransportError> {
1769 let mut num = provider.get_block_number().await?;
1770
1771 for _ in 0..2 {
1774 if let Some(block) = provider.get_block(num.into()).await?
1775 && !block.header.hash.is_zero()
1776 {
1777 break;
1778 }
1779 num = num.saturating_sub(1)
1781 }
1782
1783 Ok(num)
1784}
1785
1786#[cfg(test)]
1787mod tests {
1788 use super::*;
1789
1790 #[test]
1791 fn test_prune_history() {
1792 let config = PruneStateHistoryConfig::default();
1793 assert!(config.is_state_history_supported());
1794 let config = PruneStateHistoryConfig::from_args(Some(None));
1795 assert!(!config.is_state_history_supported());
1796 let config = PruneStateHistoryConfig::from_args(Some(Some(0)));
1797 assert!(config.is_config_enabled());
1798 assert!(!config.is_state_history_supported());
1799 let config = PruneStateHistoryConfig::from_args(Some(Some(10)));
1800 assert!(config.is_state_history_supported());
1801 }
1802
1803 #[cfg(feature = "optimism")]
1804 #[test]
1805 fn set_chain_id_updates_network_config() {
1806 let mut config = NodeConfig::test();
1807 config.set_chain_id(Some(10u64));
1808
1809 assert!(config.networks.is_optimism());
1810 }
1811
1812 #[test]
1813 fn get_hardfork_on_tempo_never_returns_non_tempo_variant() {
1814 let shanghai_ts = 1_681_338_455u64;
1816
1817 let config = NodeConfig::test_tempo()
1818 .with_chain_id(Some(1u64))
1819 .with_genesis_timestamp(Some(shanghai_ts));
1820
1821 assert!(config.networks.is_tempo());
1822 assert!(matches!(config.get_hardfork(), FoundryHardfork::Tempo(_)));
1823 }
1824}