1use crate::{
2 FeeManager, PrecompileFactory,
3 eth::{
4 backend::{
5 db::{Db, SerializableState},
6 fork::{
7 ClientFork, ClientForkConfig, ForkEndpointIdentity, ensure_fork_network_supported,
8 },
9 genesis::GenesisConfig,
10 mem::fork_db::ForkedDatabase,
11 time::duration_since_unix_epoch,
12 },
13 fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE},
14 miner::INSTANT_COALESCE_WINDOW,
15 pool::transactions::TransactionOrder,
16 },
17 mem::{self, in_memory_db::StateRootDb},
18};
19use alloy_chains::Chain;
20use alloy_consensus::BlockHeader;
21use alloy_eips::{BlockId, eip1559::BaseFeeParams, eip7840::BlobParams};
22use alloy_evm::EvmEnv;
23use alloy_genesis::Genesis;
24use alloy_network::{AnyNetwork, AnyRpcBlock, BlockResponse, TransactionResponse};
25use alloy_primitives::{
26 Address, B256, BlockNumber, TxHash, U256, hex, keccak256, map::HashMap, utils::Unit,
27};
28use alloy_provider::Provider;
29use alloy_rpc_types::anvil::{Metadata, NodeInfo};
30use alloy_signer::Signer;
31use alloy_signer_local::{
32 MnemonicBuilder, PrivateKeySigner,
33 coins_bip39::{English, Mnemonic},
34};
35use alloy_transport::TransportError;
36use anvil_server::ServerConfig;
37use eyre::{Context, Result};
38use foundry_common::{
39 ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT,
40 provider::{ProviderBuilder, RetryProvider, is_rpc_method_not_found, redact_url},
41};
42use foundry_config::Config;
43use foundry_evm::{
44 backend::{
45 BlockchainDb, BlockchainDbMeta, ForkBlock, SharedBackend, account_fetch_policy_for_source,
46 },
47 constants::DEFAULT_CREATE2_DEPLOYER,
48 hardfork::FoundryHardfork,
49 traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier},
50 utils::{
51 apply_chain_and_block_specific_env_changes_for_chain, block_env_from_header,
52 get_blob_params, get_blob_params_by_hardfork,
53 },
54};
55use foundry_evm_networks::{NetworkConfigs, NetworkVariant};
56use parking_lot::RwLock;
57use rand_08::thread_rng;
58use revm::{
59 context::{BlockEnv, CfgEnv},
60 context_interface::block::BlobExcessGasAndPrice,
61 primitives::hardfork::SpecId,
62};
63use serde_json::{Value, json};
64use std::{
65 fmt::Write as FmtWrite,
66 net::{IpAddr, Ipv4Addr},
67 path::PathBuf,
68 sync::Arc,
69 time::Duration,
70};
71use tempo_hardfork::{
72 TempoHardfork,
73 constants::gas::{TEMPO_T0_BASE_FEE, TEMPO_T1_BASE_FEE},
74};
75use tempo_precompiles::TIP_FEE_MANAGER_ADDRESS;
76use tokio::sync::RwLock as TokioRwLock;
77use yansi::Paint;
78
79#[cfg(feature = "base")]
80use alloy_chains::NamedChain;
81
82pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
83
84pub const NODE_PORT: u16 = 8545;
86pub const CHAIN_ID: u64 = 31337;
88pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
90pub const DEFAULT_SLOTS_IN_AN_EPOCH: u64 = 32;
92pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
94
95pub(crate) fn source_hardfork(
97 _execution_network: NetworkVariant,
98 chain_id: u64,
99 timestamp: u64,
100) -> Option<FoundryHardfork> {
101 #[cfg(feature = "base")]
102 if _execution_network.is_base() {
103 return _execution_network.historical_hardfork(chain_id, timestamp);
104 }
105 #[cfg(feature = "optimism")]
106 if _execution_network.is_optimism()
107 && let Some(hardfork) = _execution_network.historical_hardfork(chain_id, timestamp)
108 {
109 return Some(hardfork);
110 }
111 #[cfg(feature = "base")]
112 if matches!(NamedChain::try_from(chain_id), Ok(NamedChain::Base | NamedChain::BaseSepolia)) {
113 #[cfg(feature = "optimism")]
114 return foundry_evm::hardfork::OpHardfork::from_chain_and_timestamp(
115 Chain::from_id(chain_id),
116 timestamp,
117 )
118 .map(FoundryHardfork::Optimism);
119 #[cfg(not(feature = "optimism"))]
120 return None;
121 }
122 FoundryHardfork::from_chain_and_timestamp(chain_id, timestamp)
123}
124
125#[derive(Clone, Copy, Debug)]
126struct ForkOverrides {
127 gas_limit: Option<u64>,
128 gas_price: Option<u128>,
129 base_fee: Option<u64>,
130}
131
132struct StableForkSnapshot {
133 endpoint_identity: ForkEndpointIdentity,
134 state_is_mutable: bool,
135 block_number: u64,
136 transaction_replay: Option<ForkTransactionReplay>,
137 block: Option<AnyRpcBlock>,
138 gas_price: u128,
139}
140
141const FORK_IDENTITY_PROBE_TIMEOUT: Duration = Duration::from_millis(500);
143
144#[derive(Clone, Copy, Debug, Default)]
152struct AnvilNodeInfoProbe {
153 identified: bool,
154 skip: bool,
155 inconclusive: bool,
157}
158
159impl AnvilNodeInfoProbe {
160 const fn new(identified: bool, skip: bool) -> Self {
161 Self { identified, skip, inconclusive: false }
162 }
163
164 async fn request(&mut self, provider: &RetryProvider) -> Result<Option<NodeInfo>> {
165 if self.skip {
166 return Ok(None);
167 }
168 let Ok(response) = tokio::time::timeout(
169 FORK_IDENTITY_PROBE_TIMEOUT,
170 provider.raw_request::<_, NodeInfo>("anvil_nodeInfo".into(), ()),
171 )
172 .await
173 else {
174 self.inconclusive = true;
175 return Ok(None);
176 };
177 match response {
178 Ok(node_info) => {
179 self.identified = true;
180 Ok(Some(node_info))
181 }
182 Err(error) if !self.identified => {
183 self.inconclusive |= !is_rpc_method_not_found(&error);
184 Ok(None)
185 }
186 Err(error) => {
187 Err(error).wrap_err("failed to determine network family from fork endpoint")
188 }
189 }
190 }
191}
192
193#[derive(Clone, Debug)]
195pub(crate) struct ForkTransactionReplay {
196 pub(crate) source_block: AnyRpcBlock,
197 pub(crate) target_index: usize,
198}
199
200pub const DEFAULT_IPC_ENDPOINT: &str =
202 if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
203
204const BANNER: &str = r"
205 _ _
206 (_) | |
207 __ _ _ __ __ __ _ | |
208 / _` | | '_ \ \ \ / / | | | |
209 | (_| | | | | | \ V / | | | |
210 \__,_| |_| |_| \_/ |_| |_|
211";
212
213fn fork_source_id(urls: &[String], headers: &[String]) -> B256 {
214 let mut encoded = Vec::new();
215 for parts in [urls, headers] {
216 encoded.extend_from_slice(&(parts.len() as u64).to_be_bytes());
217 for part in parts {
218 encoded.extend_from_slice(&(part.len() as u64).to_be_bytes());
219 encoded.extend_from_slice(part.as_bytes());
220 }
221 }
222 keccak256(encoded)
223}
224
225#[derive(Clone, Debug)]
227pub struct NodeConfig {
228 pub chain_id: Option<u64>,
230 pub gas_limit: Option<u64>,
232 pub disable_block_gas_limit: bool,
234 pub enable_tx_gas_limit: bool,
236 pub gas_price: Option<u128>,
238 pub base_fee: Option<u64>,
240 pub disable_min_priority_fee: bool,
242 pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
244 pub hardfork: Option<FoundryHardfork>,
246 pub genesis_accounts: Vec<PrivateKeySigner>,
248 pub genesis_balance: U256,
250 pub genesis_timestamp: Option<u64>,
252 pub genesis_block_number: Option<u64>,
254 pub signer_accounts: Vec<PrivateKeySigner>,
256 pub block_time: Option<Duration>,
258 pub transaction_coalescing_window: Duration,
260 pub no_mining: bool,
262 pub mixed_mining: bool,
264 pub port: u16,
266 pub max_transactions: usize,
268 pub fork_urls: Vec<String>,
272 pub fork_choice: Option<ForkChoice>,
274 pub fork_headers: Vec<String>,
276 pub fork_chain_id: Option<U256>,
278 pub no_fork_node_info: bool,
280 pub no_bal: bool,
282 pub fork_state_by_number: bool,
284 pub fork_source_chain_id: Option<u64>,
286 pub fork_execution_chain_id: Option<u64>,
288 pub(crate) fork_endpoint_is_anvil: bool,
290 inferred_fork_network: Option<NetworkVariant>,
292 chain_id_network_base: Option<NetworkConfigs>,
294 fork_overrides: Option<ForkOverrides>,
296 pub account_generator: Option<AccountGenerator>,
298 pub enable_tracing: bool,
300 pub no_storage_caching: bool,
302 pub server_config: ServerConfig,
304 pub host: Vec<IpAddr>,
306 pub transaction_order: TransactionOrder,
308 pub config_out: Option<PathBuf>,
310 pub genesis: Option<Genesis>,
312 pub fork_request_timeout: Duration,
314 pub fork_request_retries: u32,
316 pub fork_retry_backoff: Duration,
318 pub compute_units_per_second: u64,
320 pub ipc_path: Option<Option<String>>,
322 pub enable_steps_tracing: bool,
324 pub print_logs: bool,
326 pub print_traces: bool,
328 pub enable_auto_impersonate: bool,
330 pub code_size_limit: Option<usize>,
332 pub prune_history: PruneStateHistoryConfig,
336 pub max_persisted_states: Option<usize>,
338 pub init_state: Option<SerializableState>,
340 pub transaction_block_keeper: Option<usize>,
342 pub disable_default_create2_deployer: bool,
344 pub disable_pool_balance_checks: bool,
346 pub slots_in_an_epoch: u64,
348 pub memory_limit: Option<u64>,
350 pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
352 pub networks: NetworkConfigs,
354 #[cfg(feature = "base")]
356 pub base_activation_admin: Option<Address>,
357 pub tempo_fee_payer: Option<Address>,
361 pub silent: bool,
363 pub cache_path: Option<PathBuf>,
366 pub funded_accounts: HashMap<Address, U256>,
368}
369
370impl NodeConfig {
371 fn as_string(&self, fork: Option<&ClientFork>) -> String {
372 let mut s: String = String::new();
373 let _ = write!(s, "\n{}", BANNER.green());
374 let _ = write!(s, "\n {VERSION_MESSAGE}");
375 let _ = write!(s, "\n {}", "https://github.com/foundry-rs/foundry".green());
376
377 let _ = write!(
378 s,
379 r#"
380
381Available Accounts
382==================
383"#
384 );
385 let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
386 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
387 write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap();
388 }
389
390 let _ = write!(
391 s,
392 r#"
393
394Private Keys
395==================
396"#
397 );
398
399 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
400 let hex = hex::encode(wallet.credential().to_bytes());
401 let _ = write!(s, "\n({idx}) 0x{hex}");
402 }
403
404 if let Some(generator) = &self.account_generator {
405 let _ = write!(
406 s,
407 r#"
408
409Wallet
410==================
411Mnemonic: {}
412Derivation path: {}
413"#,
414 generator.phrase,
415 generator.get_derivation_path()
416 );
417 }
418
419 if let Some(fee_payer) = self.tempo_fee_payer_address() {
420 let _ = write!(
421 s,
422 r#"
423
424Tempo Fee Payer
425==================
426{fee_payer}
427"#
428 );
429 }
430
431 if let Some(fork) = fork {
432 let _ = write!(
433 s,
434 r#"
435
436Fork
437==================
438Endpoint: {}
439Block number: {}
440Block hash: {:?}
441Chain ID: {}
442"#,
443 fork.eth_rpc_url().as_deref().map(redact_url).unwrap_or_else(|| "none".to_string()),
444 fork.block_number(),
445 fork.block_hash(),
446 fork.execution_chain_id()
447 );
448 if fork.chain_id() != fork.execution_chain_id() {
449 let _ = writeln!(s, "Source chain ID: {}", fork.chain_id());
450 }
451
452 if self.fork_urls.len() > 1 {
453 let _ = writeln!(s, "Endpoints: {}", self.fork_urls.len());
454 for (i, url) in self.fork_urls.iter().enumerate() {
455 let _ = writeln!(s, " ({i}) {}", redact_url(url));
456 }
457 }
458
459 if let Some(tx_hash) = fork.transaction_hash() {
460 let _ = writeln!(s, "Transaction hash: {tx_hash}");
461 }
462 } else {
463 let _ = write!(
464 s,
465 r#"
466
467Chain ID
468==================
469
470{}
471"#,
472 self.get_chain_id().green()
473 );
474 }
475
476 if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) {
477 let _ = write!(
478 s,
479 r#"
480Gas Price
481==================
482
483{}
484"#,
485 self.get_gas_price().green()
486 );
487 } else {
488 let _ = write!(
489 s,
490 r#"
491Base Fee
492==================
493
494{}
495"#,
496 self.get_base_fee().green()
497 );
498 }
499
500 let _ = write!(
501 s,
502 r#"
503Gas Limit
504==================
505
506{}
507"#,
508 {
509 if self.disable_block_gas_limit {
510 "Disabled".to_string()
511 } else {
512 self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| {
513 if self.fork_choice.is_some() {
514 "Forked".to_string()
515 } else {
516 DEFAULT_GAS_LIMIT.to_string()
517 }
518 })
519 }
520 }
521 .green()
522 );
523
524 let _ = write!(
525 s,
526 r#"
527Genesis Timestamp
528==================
529
530{}
531"#,
532 self.get_genesis_timestamp().green()
533 );
534
535 let _ = write!(
536 s,
537 r#"
538Genesis Number
539==================
540
541{}
542"#,
543 self.get_genesis_number().green()
544 );
545
546 s
547 }
548
549 fn as_json(&self, fork: Option<&ClientFork>) -> Value {
550 let mut wallet_description = HashMap::new();
551 let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
552 let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
553
554 for wallet in &self.genesis_accounts {
555 available_accounts.push(format!("{:?}", wallet.address()));
556 private_keys.push(hex::encode_prefixed(wallet.credential().to_bytes()));
557 }
558
559 if let Some(generator) = &self.account_generator {
560 let phrase = generator.get_phrase().to_string();
561 let derivation_path = generator.get_derivation_path().to_string();
562
563 wallet_description.insert("derivation_path".to_string(), derivation_path);
564 wallet_description.insert("mnemonic".to_string(), phrase);
565 };
566
567 let gas_limit = match self.gas_limit {
568 Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()),
570 Some(limit) => Some(limit.to_string()),
571 _ => None,
572 };
573
574 if let Some(fork) = fork {
575 json!({
576 "available_accounts": available_accounts,
577 "private_keys": private_keys,
578 "endpoint": fork.eth_rpc_url().as_deref().map(redact_url).unwrap_or_default(),
579 "block_number": fork.block_number(),
580 "block_hash": fork.block_hash(),
581 "chain_id": fork.execution_chain_id(),
582 "source_chain_id": fork.chain_id(),
583 "wallet": wallet_description,
584 "base_fee": format!("{}", self.get_base_fee()),
585 "gas_price": format!("{}", self.get_gas_price()),
586 "gas_limit": gas_limit,
587 })
588 } else {
589 json!({
590 "available_accounts": available_accounts,
591 "private_keys": private_keys,
592 "wallet": wallet_description,
593 "base_fee": format!("{}", self.get_base_fee()),
594 "gas_price": format!("{}", self.get_gas_price()),
595 "gas_limit": gas_limit,
596 "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
597 })
598 }
599 }
600}
601
602impl NodeConfig {
603 #[doc(hidden)]
606 pub fn test() -> Self {
607 Self { enable_tracing: true, port: 0, silent: true, ..Default::default() }
608 }
609
610 #[doc(hidden)]
612 pub fn test_tempo() -> Self {
613 Self { networks: NetworkConfigs::with_tempo(), ..Self::test() }
614 }
615
616 #[cfg(feature = "monad")]
618 #[doc(hidden)]
619 pub fn test_monad() -> Self {
620 Self { networks: NetworkConfigs::with_monad(), ..Self::test() }
621 }
622
623 #[cfg(feature = "base")]
625 #[doc(hidden)]
626 pub fn test_base() -> Self {
627 Self::test()
628 .with_networks(NetworkConfigs::with_base())
629 .with_chain_id(Some(NamedChain::Base as u64))
630 }
631
632 pub fn empty_state() -> Self {
634 Self {
635 genesis_accounts: vec![],
636 signer_accounts: vec![],
637 disable_default_create2_deployer: true,
638 ..Default::default()
639 }
640 }
641}
642
643impl Default for NodeConfig {
644 fn default() -> Self {
645 let genesis_accounts = AccountGenerator::new(10)
647 .phrase(DEFAULT_MNEMONIC)
648 .generate()
649 .expect("Invalid mnemonic.");
650 Self {
651 chain_id: None,
652 gas_limit: None,
653 disable_block_gas_limit: false,
654 enable_tx_gas_limit: false,
655 gas_price: None,
656 hardfork: None,
657 signer_accounts: genesis_accounts.clone(),
658 genesis_timestamp: None,
659 genesis_block_number: None,
660 genesis_accounts,
661 genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
663 block_time: None,
664 transaction_coalescing_window: INSTANT_COALESCE_WINDOW,
665 no_mining: false,
666 mixed_mining: false,
667 port: NODE_PORT,
668 max_transactions: 1_000,
669 fork_urls: vec![],
670 fork_choice: None,
671 account_generator: None,
672 base_fee: None,
673 disable_min_priority_fee: false,
674 blob_excess_gas_and_price: None,
675 enable_tracing: true,
676 enable_steps_tracing: false,
677 print_logs: true,
678 print_traces: false,
679 enable_auto_impersonate: false,
680 no_storage_caching: false,
681 server_config: Default::default(),
682 host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
683 transaction_order: Default::default(),
684 config_out: None,
685 genesis: None,
686 fork_request_timeout: REQUEST_TIMEOUT,
687 fork_headers: vec![],
688 fork_request_retries: 5,
689 fork_retry_backoff: Duration::from_millis(1_000),
690 fork_chain_id: None,
691 no_fork_node_info: false,
692 no_bal: false,
693 fork_state_by_number: false,
694 fork_source_chain_id: None,
695 fork_execution_chain_id: None,
696 fork_endpoint_is_anvil: false,
697 inferred_fork_network: None,
698 chain_id_network_base: None,
699 fork_overrides: None,
700 compute_units_per_second: ALCHEMY_FREE_TIER_CUPS,
702 ipc_path: None,
703 code_size_limit: None,
704 prune_history: Default::default(),
705 max_persisted_states: None,
706 init_state: None,
707 transaction_block_keeper: None,
708 disable_default_create2_deployer: false,
709 disable_pool_balance_checks: false,
710 slots_in_an_epoch: DEFAULT_SLOTS_IN_AN_EPOCH,
711 memory_limit: None,
712 precompile_factory: None,
713 networks: Default::default(),
714 #[cfg(feature = "base")]
715 base_activation_admin: None,
716 tempo_fee_payer: None,
717 silent: false,
718 cache_path: None,
719 funded_accounts: HashMap::default(),
720 }
721 }
722}
723
724impl NodeConfig {
725 pub(crate) fn apply_tempo_fork_beneficiary_default<N>(&self, evm_env: &mut EvmEnv<N>) {
728 if self.networks.is_tempo()
729 && !self.fork_urls.is_empty()
730 && evm_env.block_env.beneficiary.is_zero()
731 {
732 evm_env.block_env.beneficiary = TIP_FEE_MANAGER_ADDRESS;
739 }
740 }
741
742 #[must_use]
744 pub const fn with_memory_limit(mut self, mems_value: Option<u64>) -> Self {
745 self.memory_limit = mems_value;
746 self
747 }
748
749 pub fn get_base_fee(&self) -> u64 {
753 let default = if self.networks.is_tempo() {
754 tempo_default_base_fee(TempoHardfork::from(self.get_hardfork()))
755 } else {
756 INITIAL_BASE_FEE
757 };
758 self.base_fee
759 .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64)))
760 .unwrap_or(default)
761 }
762
763 pub fn get_gas_price(&self) -> u128 {
767 let default = if self.networks.is_tempo() {
768 tempo_default_base_fee(TempoHardfork::from(self.get_hardfork())) as u128
769 } else {
770 INITIAL_GAS_PRICE
771 };
772 self.gas_price.unwrap_or(default)
773 }
774
775 pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice {
776 if let Some(value) = self.blob_excess_gas_and_price {
777 value
778 } else {
779 let excess_blob_gas =
780 self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0);
781 BlobExcessGasAndPrice::new(
782 excess_blob_gas,
783 self.get_blob_params().update_fraction as u64,
784 )
785 }
786 }
787
788 pub fn get_blob_params(&self) -> BlobParams {
790 get_blob_params_by_hardfork(self.get_hardfork())
791 }
792
793 pub fn get_hardfork(&self) -> FoundryHardfork {
795 if let Some(hardfork) = self.hardfork {
796 return hardfork;
797 }
798 self.networks
799 .execution_network()
800 .hardfork_at(self.protocol_chain_id(), self.get_genesis_timestamp())
801 }
802
803 #[must_use]
805 pub const fn with_code_size_limit(mut self, code_size_limit: Option<usize>) -> Self {
806 self.code_size_limit = code_size_limit;
807 self
808 }
809 #[must_use]
811 pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self {
812 if disable_code_size_limit {
813 self.code_size_limit = Some(usize::MAX);
814 }
815 self
816 }
817
818 #[must_use]
820 pub fn with_init_state(mut self, init_state: Option<SerializableState>) -> Self {
821 self.init_state = init_state;
822 self
823 }
824
825 #[must_use]
827 #[cfg(feature = "cmd")]
828 pub fn with_init_state_path(mut self, path: impl AsRef<std::path::Path>) -> Self {
829 self.init_state = crate::cmd::StateFile::parse_path(path).ok().and_then(|file| file.state);
830 self
831 }
832
833 #[must_use]
835 pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
836 self.set_chain_id(chain_id);
837 self
838 }
839
840 pub fn get_chain_id(&self) -> u64 {
842 self.chain_id
843 .or(self.fork_execution_chain_id)
844 .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
845 .unwrap_or(CHAIN_ID)
846 }
847
848 fn protocol_chain_id(&self) -> u64 {
850 self.fork_source_chain_id.unwrap_or_else(|| self.get_chain_id())
851 }
852
853 pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
855 if let Some(base) = self.chain_id_network_base.take() {
856 self.networks = base;
857 }
858 self.chain_id = chain_id.map(Into::into);
859 let chain_id = self.get_chain_id();
860 let base = self.networks;
861 let inferred = base.with_chain_id(chain_id);
862 if !base.has_network_selection() && inferred.has_network_selection() {
863 self.chain_id_network_base = Some(base);
864 }
865 self.networks = inferred;
866 self.update_wallet_chain_id(chain_id);
867 }
868
869 pub(crate) fn update_wallet_chain_id(&mut self, chain_id: u64) {
870 self.genesis_accounts.iter_mut().for_each(|wallet| {
871 *wallet = wallet.clone().with_chain_id(Some(chain_id));
872 });
873 self.signer_accounts.iter_mut().for_each(|wallet| {
874 *wallet = wallet.clone().with_chain_id(Some(chain_id));
875 })
876 }
877
878 #[must_use]
880 pub const fn with_gas_limit(mut self, gas_limit: Option<u64>) -> Self {
881 self.gas_limit = gas_limit;
882 self
883 }
884
885 #[must_use]
889 pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self {
890 self.disable_block_gas_limit = disable_block_gas_limit;
891 self
892 }
893
894 #[must_use]
898 pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self {
899 self.enable_tx_gas_limit = enable_tx_gas_limit;
900 self
901 }
902
903 #[must_use]
905 pub const fn with_gas_price(mut self, gas_price: Option<u128>) -> Self {
906 self.gas_price = gas_price;
907 self
908 }
909
910 #[must_use]
912 pub fn set_pruned_history(mut self, prune_history: Option<Option<usize>>) -> Self {
913 self.prune_history = PruneStateHistoryConfig::from_args(prune_history);
914 self
915 }
916
917 #[must_use]
919 pub fn with_max_persisted_states<U: Into<usize>>(
920 mut self,
921 max_persisted_states: Option<U>,
922 ) -> Self {
923 self.max_persisted_states = max_persisted_states.map(Into::into);
924 self
925 }
926
927 #[must_use]
929 pub const fn with_max_transactions(mut self, max_transactions: Option<usize>) -> Self {
930 if let Some(max_transactions) = max_transactions {
931 self.max_transactions = max_transactions;
932 }
933 self
934 }
935
936 #[must_use]
938 pub fn with_transaction_block_keeper<U: Into<usize>>(
939 mut self,
940 transaction_block_keeper: Option<U>,
941 ) -> Self {
942 self.transaction_block_keeper = transaction_block_keeper.map(Into::into);
943 self
944 }
945
946 #[must_use]
948 pub const fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
949 self.base_fee = base_fee;
950 self
951 }
952
953 #[must_use]
955 pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self {
956 self.disable_min_priority_fee = disable_min_priority_fee;
957 self
958 }
959
960 #[must_use]
962 pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
963 self.genesis = genesis;
964 self
965 }
966
967 pub fn get_genesis_timestamp(&self) -> u64 {
969 self.genesis_timestamp
970 .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
971 .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
972 }
973
974 #[must_use]
976 pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
977 if let Some(timestamp) = timestamp {
978 self.genesis_timestamp = Some(timestamp.into());
979 }
980 self
981 }
982
983 #[must_use]
985 pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
986 if let Some(number) = number {
987 self.genesis_block_number = Some(number.into());
988 }
989 self
990 }
991
992 pub fn get_genesis_number(&self) -> u64 {
994 self.genesis_block_number
995 .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
996 .unwrap_or(0)
997 }
998
999 #[must_use]
1001 pub const fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
1002 self.hardfork = hardfork;
1003 self
1004 }
1005
1006 #[must_use]
1008 pub fn with_genesis_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
1009 self.genesis_accounts = accounts;
1010 self
1011 }
1012
1013 #[must_use]
1015 pub fn with_signer_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
1016 self.signer_accounts = accounts;
1017 self
1018 }
1019
1020 pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
1023 let accounts = generator.generate()?;
1024 self.account_generator = Some(generator);
1025 Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
1026 }
1027
1028 #[must_use]
1030 pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
1031 self.genesis_balance = balance.into();
1032 self
1033 }
1034
1035 #[must_use]
1037 pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
1038 self.block_time = block_time.map(Into::into);
1039 self
1040 }
1041
1042 #[must_use]
1043 pub fn with_mixed_mining<D: Into<Duration>>(
1044 mut self,
1045 mixed_mining: bool,
1046 block_time: Option<D>,
1047 ) -> Self {
1048 self.block_time = block_time.map(Into::into);
1049 self.mixed_mining = mixed_mining;
1050 self
1051 }
1052
1053 #[must_use]
1055 pub const fn with_transaction_coalescing_window(mut self, window: Duration) -> Self {
1056 self.transaction_coalescing_window = window;
1057 self
1058 }
1059
1060 #[must_use]
1062 pub const fn with_no_mining(mut self, no_mining: bool) -> Self {
1063 self.no_mining = no_mining;
1064 self
1065 }
1066
1067 #[must_use]
1069 pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self {
1070 self.slots_in_an_epoch = slots_in_an_epoch;
1071 self
1072 }
1073
1074 #[must_use]
1076 pub const fn with_port(mut self, port: u16) -> Self {
1077 self.port = port;
1078 self
1079 }
1080
1081 #[must_use]
1088 pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
1089 self.ipc_path = ipc_path;
1090 self
1091 }
1092
1093 #[must_use]
1095 pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
1096 self.config_out = config_out;
1097 self
1098 }
1099
1100 #[must_use]
1101 pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self {
1102 self.no_storage_caching = no_storage_caching;
1103 self
1104 }
1105
1106 #[must_use]
1108 pub fn with_eth_rpc_url<U: Into<String>>(mut self, eth_rpc_url: Option<U>) -> Self {
1109 if let Some(url) = eth_rpc_url {
1110 let fork_urls = vec![url.into()];
1111 if self.fork_urls != fork_urls {
1112 self.fork_endpoint_is_anvil = false;
1113 }
1114 self.fork_urls = fork_urls;
1115 }
1116 self
1117 }
1118
1119 #[must_use]
1121 pub fn with_fork_urls(mut self, fork_urls: Vec<String>) -> Self {
1122 if self.fork_urls != fork_urls {
1123 self.fork_endpoint_is_anvil = false;
1124 }
1125 self.fork_urls = fork_urls;
1126 self
1127 }
1128
1129 #[must_use]
1131 pub fn with_fork_block_number<U: Into<u64>>(self, fork_block_number: Option<U>) -> Self {
1132 self.with_fork_choice(fork_block_number.map(Into::into))
1133 }
1134
1135 #[must_use]
1137 pub fn with_fork_transaction_hash<U: Into<TxHash>>(
1138 self,
1139 fork_transaction_hash: Option<U>,
1140 ) -> Self {
1141 self.with_fork_choice(fork_transaction_hash.map(Into::into))
1142 }
1143
1144 #[must_use]
1146 pub fn with_fork_choice<U: Into<ForkChoice>>(mut self, fork_choice: Option<U>) -> Self {
1147 self.fork_choice = fork_choice.map(Into::into);
1148 self
1149 }
1150
1151 #[must_use]
1153 pub const fn with_fork_state_by_number(mut self, fork_state_by_number: bool) -> Self {
1154 self.fork_state_by_number = fork_state_by_number;
1155 self
1156 }
1157
1158 #[must_use]
1160 pub const fn with_no_bal(mut self, no_bal: bool) -> Self {
1161 self.no_bal = no_bal;
1162 self
1163 }
1164
1165 #[must_use]
1167 pub const fn with_fork_chain_id(mut self, fork_chain_id: Option<U256>) -> Self {
1168 self.fork_chain_id = fork_chain_id;
1169 self
1170 }
1171
1172 #[must_use]
1174 pub const fn with_no_fork_node_info(mut self, no_fork_node_info: bool) -> Self {
1175 self.no_fork_node_info = no_fork_node_info;
1176 self
1177 }
1178
1179 const fn node_info_probe(&self, identified: bool) -> AnvilNodeInfoProbe {
1180 AnvilNodeInfoProbe::new(identified, self.no_fork_node_info)
1181 }
1182
1183 #[must_use]
1185 pub fn with_fork_headers(mut self, headers: Vec<String>) -> Self {
1186 self.fork_headers = headers;
1187 self
1188 }
1189
1190 #[must_use]
1192 pub const fn fork_request_timeout(mut self, fork_request_timeout: Option<Duration>) -> Self {
1193 if let Some(fork_request_timeout) = fork_request_timeout {
1194 self.fork_request_timeout = fork_request_timeout;
1195 }
1196 self
1197 }
1198
1199 #[must_use]
1201 pub const fn fork_request_retries(mut self, fork_request_retries: Option<u32>) -> Self {
1202 if let Some(fork_request_retries) = fork_request_retries {
1203 self.fork_request_retries = fork_request_retries;
1204 }
1205 self
1206 }
1207
1208 #[must_use]
1210 pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option<Duration>) -> Self {
1211 if let Some(fork_retry_backoff) = fork_retry_backoff {
1212 self.fork_retry_backoff = fork_retry_backoff;
1213 }
1214 self
1215 }
1216
1217 #[must_use]
1221 pub const fn fork_compute_units_per_second(
1222 mut self,
1223 compute_units_per_second: Option<u64>,
1224 ) -> Self {
1225 if let Some(compute_units_per_second) = compute_units_per_second {
1226 self.compute_units_per_second = compute_units_per_second;
1227 }
1228 self
1229 }
1230
1231 #[must_use]
1233 pub const fn with_tracing(mut self, enable_tracing: bool) -> Self {
1234 self.enable_tracing = enable_tracing;
1235 self
1236 }
1237
1238 #[must_use]
1240 pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self {
1241 self.enable_steps_tracing = enable_steps_tracing;
1242 self
1243 }
1244
1245 #[must_use]
1247 pub const fn with_print_logs(mut self, print_logs: bool) -> Self {
1248 self.print_logs = print_logs;
1249 self
1250 }
1251
1252 #[must_use]
1254 pub const fn with_print_traces(mut self, print_traces: bool) -> Self {
1255 self.print_traces = print_traces;
1256 self
1257 }
1258
1259 #[must_use]
1261 pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
1262 self.enable_auto_impersonate = enable_auto_impersonate;
1263 self
1264 }
1265
1266 #[must_use]
1267 pub fn with_server_config(mut self, config: ServerConfig) -> Self {
1268 self.server_config = config;
1269 self
1270 }
1271
1272 #[must_use]
1274 pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
1275 self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
1276 self
1277 }
1278
1279 #[must_use]
1280 pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self {
1281 self.transaction_order = transaction_order;
1282 self
1283 }
1284
1285 pub fn get_ipc_path(&self) -> Option<String> {
1287 match &self.ipc_path {
1288 Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
1289 None => None,
1290 }
1291 }
1292
1293 pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
1295 if let Some(path) = &self.config_out {
1296 let value = self.as_json(fork);
1297 foundry_common::fs::write_sensitive_json_file(path, &value)
1298 .wrap_err("failed writing JSON")?;
1299 }
1300 if !self.silent {
1301 sh_println!("{}", self.as_string(fork))?;
1302 if self.networks.is_celo() {
1303 foundry_common::sh_warn!(
1304 "CIP-64 uses native fee accounting; feeCurrency is preserved but token fees are not charged"
1305 )?;
1306 }
1307 }
1308 Ok(())
1309 }
1310
1311 pub fn block_cache_path(&self, block: u64) -> Option<PathBuf> {
1315 self.block_cache_path_for_rpc(self.protocol_chain_id(), block, self.fork_urls.first()?)
1316 }
1317
1318 fn block_cache_path_for_rpc(
1319 &self,
1320 source_chain_id: u64,
1321 block: u64,
1322 rpc_url: &str,
1323 ) -> Option<PathBuf> {
1324 if self.no_storage_caching || self.fork_urls.is_empty() {
1325 return None;
1326 }
1327
1328 let rpc_url_hash = hex::encode(keccak256(rpc_url));
1329 Some(
1330 Config::foundry_block_cache_file(source_chain_id, block)?
1331 .with_file_name(format!("storage-{rpc_url_hash}.json")),
1332 )
1333 }
1334
1335 #[must_use]
1337 pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self {
1338 self.disable_default_create2_deployer = yes;
1339 self
1340 }
1341
1342 #[must_use]
1344 pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self {
1345 self.disable_pool_balance_checks = yes;
1346 self
1347 }
1348
1349 #[must_use]
1351 pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
1352 self.precompile_factory = Some(Arc::new(factory));
1353 self
1354 }
1355
1356 #[must_use]
1358 pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self {
1359 self.networks = networks;
1360 self.inferred_fork_network = None;
1361 self.chain_id_network_base = None;
1362 self
1363 }
1364
1365 #[must_use]
1367 pub fn with_tempo(mut self) -> Self {
1368 self.networks = NetworkConfigs::with_tempo();
1369 self.inferred_fork_network = None;
1370 self.chain_id_network_base = None;
1371 self
1372 }
1373
1374 #[must_use]
1376 pub const fn with_tempo_fee_payer(mut self, fee_payer: Option<Address>) -> Self {
1377 self.tempo_fee_payer = fee_payer;
1378 self
1379 }
1380
1381 pub fn tempo_fee_payer_address(&self) -> Option<Address> {
1387 if !self.networks.is_tempo() {
1388 return None;
1389 }
1390 self.tempo_fee_payer.or_else(|| self.genesis_accounts.last().map(|wallet| wallet.address()))
1391 }
1392
1393 #[cfg(feature = "monad")]
1395 #[must_use]
1396 pub fn with_monad(mut self) -> Self {
1397 self.networks = NetworkConfigs::with_monad();
1398 self.inferred_fork_network = None;
1399 self.chain_id_network_base = None;
1400 self
1401 }
1402
1403 #[cfg(feature = "base")]
1405 #[must_use]
1406 pub fn with_base(mut self) -> Self {
1407 self.networks = NetworkConfigs::with_base();
1408 self.inferred_fork_network = None;
1409 self.chain_id_network_base = None;
1410 self
1411 }
1412
1413 #[cfg(feature = "base")]
1415 #[must_use]
1416 pub const fn with_base_activation_admin(mut self, admin: Option<Address>) -> Self {
1417 self.base_activation_admin = admin;
1418 self
1419 }
1420
1421 #[cfg(feature = "optimism")]
1423 #[must_use]
1424 pub fn with_optimism(mut self) -> Self {
1425 self.networks = NetworkConfigs::with_optimism();
1426 self.inferred_fork_network = None;
1427 self.chain_id_network_base = None;
1428 self
1429 }
1430
1431 #[must_use]
1433 pub const fn silent(self) -> Self {
1434 self.set_silent(true)
1435 }
1436
1437 #[must_use]
1438 pub const fn set_silent(mut self, silent: bool) -> Self {
1439 self.silent = silent;
1440 self
1441 }
1442
1443 #[must_use]
1448 pub fn with_cache_path(mut self, cache_path: Option<PathBuf>) -> Self {
1449 self.cache_path = cache_path;
1450 self
1451 }
1452
1453 #[must_use]
1455 pub fn with_funded_accounts(mut self, accounts: HashMap<Address, U256>) -> Self {
1456 self.funded_accounts = accounts;
1457 self
1458 }
1459
1460 pub(crate) async fn setup<N>(
1465 &mut self,
1466 ) -> Result<(mem::Backend<N>, Option<ForkTransactionReplay>)>
1467 where
1468 N: alloy_network::Network<
1469 TxEnvelope = foundry_primitives::FoundryTxEnvelope,
1470 ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope,
1471 >,
1472 {
1473 let mut cfg = CfgEnv::new();
1476 cfg.set_spec_and_mainnet_gas_params(self.get_hardfork().into());
1477
1478 cfg.chain_id = self.get_chain_id();
1479 cfg.limit_contract_code_size = self.code_size_limit;
1480 cfg.disable_eip3607 = true;
1484 cfg.disable_block_gas_limit = self.disable_block_gas_limit;
1485
1486 if !self.enable_tx_gas_limit {
1487 cfg.tx_gas_limit_cap = Some(u64::MAX);
1488 }
1489
1490 if let Some(value) = self.memory_limit {
1491 cfg.memory_limit = value;
1492 }
1493
1494 let spec_id = cfg.spec;
1495 let mut evm_env = EvmEnv::new(
1496 cfg,
1497 BlockEnv {
1498 gas_limit: self.gas_limit(),
1499 basefee: self.get_base_fee(),
1500 ..Default::default()
1501 },
1502 );
1503
1504 self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1505
1506 let genesis_timestamp = self.get_genesis_timestamp();
1507 let base_fee_params: BaseFeeParams = self.networks.base_fee_params(genesis_timestamp);
1508
1509 let tempo_hardfork =
1511 self.networks.is_tempo().then(|| TempoHardfork::from(self.get_hardfork()));
1512
1513 let fees = FeeManager::new(
1514 spec_id,
1515 self.get_base_fee(),
1516 !self.disable_min_priority_fee,
1517 self.get_gas_price(),
1518 self.get_blob_excess_gas_and_price(),
1519 self.get_blob_params(),
1520 base_fee_params,
1521 tempo_hardfork,
1522 );
1523 #[cfg(feature = "optimism")]
1524 if self.networks.is_optimism() {
1525 fees.set_optimism_hardfork(self.get_hardfork().into());
1526 }
1527
1528 let (db, fork, fork_transaction_replay) =
1529 if let Some(eth_rpc_url) = self.fork_urls.first().cloned() {
1530 self.setup_fork_db_with_replay(eth_rpc_url, &mut evm_env, &fees).await?
1531 } else {
1532 let track_history = self.prune_history.is_state_history_supported();
1533 let db: Arc<TokioRwLock<Box<dyn Db>>> =
1534 Arc::new(TokioRwLock::new(Box::new(StateRootDb::new(track_history))));
1535 (db, None, None)
1536 };
1537
1538 if let Some(ref genesis) = self.genesis {
1540 if self.chain_id.is_none() && fork.is_none() {
1543 evm_env.cfg_env.chain_id = genesis.config.chain_id;
1544 }
1545 evm_env.block_env.timestamp = U256::from(genesis.timestamp);
1546 if let Some(base_fee) = genesis.base_fee_per_gas {
1547 evm_env.block_env.basefee = base_fee.try_into()?;
1548 }
1549 if let Some(number) = genesis.number {
1550 evm_env.block_env.number = U256::from(number);
1551 }
1552 evm_env.block_env.beneficiary = genesis.coinbase;
1553 }
1554
1555 if fork.is_none() {
1560 evm_env.block_env.timestamp = U256::from(genesis_timestamp);
1561 }
1562
1563 self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1564
1565 let genesis = GenesisConfig {
1566 number: self.get_genesis_number(),
1567 timestamp: genesis_timestamp,
1568 balance: self.genesis_balance,
1569 accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(),
1570 genesis_init: self.genesis.clone(),
1571 };
1572
1573 let active_hardfork = fork
1574 .as_ref()
1575 .and_then(|fork| fork.config.read().hardfork)
1576 .unwrap_or_else(|| self.get_hardfork());
1577 let mut decoder_builder = CallTraceDecoderBuilder::new()
1578 .with_networks(self.networks)
1579 .with_hardfork(Some(self.networks.executed_hardfork(active_hardfork)));
1580 if self.print_traces {
1581 if let Ok(identifier) = SignaturesIdentifier::new(false) {
1583 debug!(target: "node", "using signature identifier");
1584 decoder_builder = decoder_builder.with_signature_identifier(identifier);
1585 }
1586 }
1587
1588 let backend = mem::Backend::with_genesis(
1590 db,
1591 Arc::new(RwLock::new(evm_env)),
1592 self.networks,
1593 genesis,
1594 fees,
1595 Arc::new(RwLock::new(fork)),
1596 self.enable_steps_tracing,
1597 self.print_logs,
1598 self.print_traces,
1599 Arc::new(decoder_builder.build()),
1600 self.prune_history,
1601 self.max_persisted_states,
1602 self.transaction_block_keeper,
1603 self.block_time,
1604 self.cache_path.clone(),
1605 Arc::new(TokioRwLock::new(self.clone())),
1606 )
1607 .await?;
1608
1609 if !self.disable_default_create2_deployer && self.fork_urls.is_empty() {
1612 backend
1613 .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER)
1614 .await
1615 .wrap_err("failed to create default create2 deployer")?;
1616 }
1617
1618 if let Some(fork) = backend.get_fork() {
1619 let config = fork.config.read().clone();
1620 if !self
1621 .fork_urls_match_context(
1622 &config.fork_urls,
1623 config.endpoint_identity,
1624 config.block_number,
1625 config.block_hash,
1626 )
1627 .await?
1628 {
1629 eyre::bail!("fork endpoint changed while Anvil was being initialized");
1630 }
1631 }
1632 Ok((backend, fork_transaction_replay))
1633 }
1634
1635 pub async fn setup_fork_db(
1642 &mut self,
1643 eth_rpc_url: String,
1644 evm_env: &mut EvmEnv,
1645 fees: &FeeManager,
1646 ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>)> {
1647 let (db, fork, replay) = self.setup_fork_db_with_replay(eth_rpc_url, evm_env, fees).await?;
1648 eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1649 Ok((db, fork))
1650 }
1651
1652 async fn setup_fork_db_with_replay(
1653 &mut self,
1654 eth_rpc_url: String,
1655 evm_env: &mut EvmEnv,
1656 fees: &FeeManager,
1657 ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>, Option<ForkTransactionReplay>)>
1658 {
1659 let (db, config, replay) =
1660 self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees, None).await?;
1661 if !self.no_bal && !self.no_fork_node_info {
1662 config.prefill_cache(db.inner()).await;
1663 }
1664 let db: Arc<TokioRwLock<Box<dyn Db>>> = Arc::new(TokioRwLock::new(Box::new(db)));
1665 let fork = ClientFork::new(config, Arc::clone(&db));
1666 Ok((db, Some(fork), replay))
1667 }
1668
1669 fn fork_provider(&self, eth_rpc_url: &str) -> Result<RetryProvider> {
1670 ProviderBuilder::new(eth_rpc_url)
1671 .timeout(self.fork_request_timeout)
1672 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1673 .compute_units_per_second(self.compute_units_per_second)
1674 .max_retry(self.fork_request_retries)
1675 .headers(self.fork_headers.clone())
1676 .build()
1677 .wrap_err("failed to establish provider to fork url")
1678 }
1679
1680 async fn fork_endpoint_identity(
1681 &self,
1682 provider: &RetryProvider,
1683 fallback_execution_chain_id: u64,
1684 source_chain_id_override: Option<u64>,
1685 node_info_probe: &mut AnvilNodeInfoProbe,
1686 ) -> Result<ForkEndpointIdentity> {
1687 let Some(node_info) = node_info_probe.request(provider).await? else {
1688 let source_chain_id = source_chain_id_override.unwrap_or(fallback_execution_chain_id);
1689 let explicit_fallback = self.has_explicit_network_selection().then_some(self.networks);
1690 let network_profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
1691 source_chain_id,
1692 None,
1693 explicit_fallback,
1694 )
1695 .map_err(eyre::Report::msg)?;
1696 return Ok(ForkEndpointIdentity {
1697 execution_chain_id: fallback_execution_chain_id,
1698 source_chain_id,
1699 network: network_profile.map(|profile| profile.execution_network()),
1700 network_profile,
1701 hardfork: None,
1702 instance_id: None,
1703 source_fork_block_number: None,
1704 source_fork_block_hash: None,
1705 });
1706 };
1707
1708 let (
1709 execution_chain_id,
1710 source_chain_id,
1711 instance_id,
1712 source_fork_block_number,
1713 source_fork_block_hash,
1714 ) = match tokio::time::timeout(
1715 FORK_IDENTITY_PROBE_TIMEOUT,
1716 provider.raw_request::<_, Metadata>("anvil_metadata".into(), ()),
1717 )
1718 .await
1719 {
1720 Ok(Ok(metadata)) => (
1721 metadata.chain_id,
1722 source_chain_id_override.unwrap_or_else(|| {
1723 metadata.forked_network.map(|fork| fork.chain_id).unwrap_or(metadata.chain_id)
1724 }),
1725 Some(metadata.instance_id),
1726 metadata.forked_network.map(|fork| fork.fork_block_number),
1727 metadata.forked_network.map(|fork| fork.fork_block_hash),
1728 ),
1729 response => {
1730 if let Ok(Err(error)) = response
1731 && !is_rpc_method_not_found(&error)
1732 {
1733 return Err(error).wrap_err("failed to retrieve Anvil fork source identity");
1734 }
1735 (
1736 fallback_execution_chain_id,
1737 source_chain_id_override.unwrap_or(fallback_execution_chain_id),
1738 None,
1739 None,
1740 None,
1741 )
1742 }
1743 };
1744 let identity_chain_id =
1745 if node_info.network.is_some() { execution_chain_id } else { source_chain_id };
1746 let explicit_fallback = self.has_explicit_network_selection().then_some(self.networks);
1747 let network_profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
1748 identity_chain_id,
1749 Some(node_info.network.as_deref()),
1750 explicit_fallback,
1751 )
1752 .map_err(eyre::Report::msg)?
1753 .ok_or_else(|| eyre::eyre!("Anvil metadata did not identify an execution profile"))?;
1754 let network = network_profile.execution_network();
1755 let hardfork = network
1756 .parse_hardfork(&node_info.hard_fork)
1757 .map_err(eyre::Report::msg)
1758 .wrap_err_with(|| {
1759 format!("unsupported hardfork `{}` reported for `{network}`", node_info.hard_fork)
1760 })?;
1761
1762 Ok(ForkEndpointIdentity {
1763 execution_chain_id,
1764 source_chain_id,
1765 network: Some(network),
1766 network_profile: Some(network_profile),
1767 hardfork: Some(hardfork),
1768 instance_id,
1769 source_fork_block_number,
1770 source_fork_block_hash,
1771 })
1772 }
1773
1774 async fn resolved_fork_endpoint_identity(
1775 &self,
1776 provider: &RetryProvider,
1777 node_info_probe: &mut AnvilNodeInfoProbe,
1778 ) -> Result<ForkEndpointIdentity> {
1779 let identity = if let Some(chain_id) = self.fork_chain_id {
1780 let chain_id = chain_id.to();
1781 self.fork_endpoint_identity(provider, chain_id, Some(chain_id), node_info_probe).await
1785 } else {
1786 let execution_chain_id =
1787 provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?;
1788 self.fork_endpoint_identity(provider, execution_chain_id, None, node_info_probe).await
1789 }?;
1790 ensure_fork_network_supported(identity.source_chain_id)?;
1791 Ok(identity)
1792 }
1793
1794 pub(crate) async fn replacement_fork_provider(
1795 &self,
1796 eth_rpc_url: &str,
1797 expected: ForkEndpointIdentity,
1798 block_number: u64,
1799 block_hash: B256,
1800 serving_instance_id: B256,
1801 ) -> Result<(Arc<RetryProvider>, ForkEndpointIdentity)> {
1802 let provider = Arc::new(self.fork_provider(eth_rpc_url)?);
1803 let mut node_info_probe = self.node_info_probe(false);
1804 for _ in 0..3 {
1805 let before =
1806 self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1807 eyre::ensure!(
1808 before.instance_id != Some(serving_instance_id),
1809 "cannot set Anvil's fork provider to its own RPC endpoint"
1810 );
1811 let block = provider
1812 .get_block(BlockId::number(block_number))
1813 .await
1814 .wrap_err("failed to confirm active fork block on replacement endpoint")?;
1815 let after =
1816 self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1817 if before != after {
1818 continue;
1819 }
1820 if !before.context_eq(expected) {
1821 eyre::bail!("replacement fork endpoint has an incompatible execution context");
1822 }
1823 let actual_hash = block.map(|block| block.header.hash);
1824 if actual_hash != Some(block_hash) {
1825 eyre::bail!(
1826 "replacement fork endpoint does not contain active fork block {block_number} with hash {block_hash}"
1827 );
1828 }
1829 return Ok((provider, before));
1830 }
1831 eyre::bail!(
1832 "fork endpoint changed while its identity and active fork block were being resolved"
1833 );
1834 }
1835
1836 async fn stable_fork_snapshot(
1837 &self,
1838 provider: &Arc<RetryProvider>,
1839 fork_overrides: ForkOverrides,
1840 ) -> Result<StableForkSnapshot> {
1841 let mut node_info_probe = self.node_info_probe(self.fork_endpoint_is_anvil);
1842 for _ in 0..3 {
1843 let before =
1844 self.resolved_fork_endpoint_identity(provider, &mut node_info_probe).await?;
1845 let (block_number, transaction_replay) = if let Some(fork_choice) = &self.fork_choice {
1846 derive_block_and_replay(fork_choice, provider).await.wrap_err(
1847 "failed to derive fork block and transaction replay from fork choice",
1848 )?
1849 } else {
1850 (
1851 find_latest_fork_block(provider)
1852 .await
1853 .wrap_err("failed to get fork block number")?,
1854 None,
1855 )
1856 };
1857 let block = provider
1858 .get_block(BlockId::number(block_number))
1859 .await
1860 .wrap_err("failed to get fork block")?;
1861 let gas_price = if let Some(gas_price) = fork_overrides.gas_price {
1862 gas_price
1863 } else {
1864 provider.get_gas_price().await.unwrap_or(INITIAL_GAS_PRICE)
1865 };
1866 let after =
1867 self.resolved_fork_endpoint_identity(provider, &mut node_info_probe).await?;
1868 if before == after {
1869 return Ok(StableForkSnapshot {
1870 endpoint_identity: before,
1871 state_is_mutable: node_info_probe.identified || node_info_probe.inconclusive,
1872 block_number,
1873 transaction_replay,
1874 block,
1875 gas_price,
1876 });
1877 }
1878 }
1879 eyre::bail!(
1880 "fork endpoint changed while its identity and block context were being resolved"
1881 );
1882 }
1883
1884 pub(crate) async fn fork_context_matches(
1885 &self,
1886 eth_rpc_url: &str,
1887 expected: ForkEndpointIdentity,
1888 block_number: u64,
1889 block_hash: B256,
1890 ) -> Result<bool> {
1891 let provider = self.fork_provider(eth_rpc_url)?;
1892 let mut node_info_probe = self.node_info_probe(expected.is_authoritative());
1893 for _ in 0..3 {
1894 let before =
1895 self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1896 let block = provider
1897 .get_block(BlockId::number(block_number))
1898 .await
1899 .wrap_err("failed to confirm fork block context")?;
1900 let after =
1901 self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1902 if before != after {
1903 continue;
1904 }
1905 if before.source_chain_id != expected.source_chain_id {
1906 eyre::bail!(
1907 "fork endpoints must use the same chain ID: expected {}, got {} from {}",
1908 expected.source_chain_id,
1909 before.source_chain_id,
1910 redact_url(eth_rpc_url)
1911 );
1912 }
1913 return Ok(
1914 before == expected && block.is_some_and(|block| block.header.hash == block_hash)
1915 );
1916 }
1917 Ok(false)
1918 }
1919
1920 pub(crate) async fn fork_urls_match_context(
1921 &self,
1922 fork_urls: &[String],
1923 expected: ForkEndpointIdentity,
1924 block_number: u64,
1925 block_hash: B256,
1926 ) -> Result<bool> {
1927 for eth_rpc_url in Self::fork_urls_requiring_revalidation(fork_urls, expected) {
1928 if !self.fork_context_matches(eth_rpc_url, expected, block_number, block_hash).await? {
1929 return Ok(false);
1930 }
1931 }
1932 Ok(true)
1933 }
1934
1935 const fn fork_urls_requiring_revalidation(
1936 fork_urls: &[String],
1937 endpoint_identity: ForkEndpointIdentity,
1938 ) -> &[String] {
1939 if endpoint_identity.is_authoritative() || fork_urls.len() > 1 { fork_urls } else { &[] }
1940 }
1941
1942 pub(crate) fn has_explicit_network_selection(&self) -> bool {
1943 let effective_network =
1944 self.networks.resolved_network().unwrap_or(NetworkVariant::Ethereum);
1945 self.networks.has_network_selection()
1946 && self.chain_id_network_base.is_none()
1947 && self.inferred_fork_network != Some(effective_network)
1948 }
1949
1950 const fn requires_primary_fork_revalidation(
1951 &self,
1952 endpoint_identity: ForkEndpointIdentity,
1953 ) -> bool {
1954 endpoint_identity.is_authoritative() || self.fork_urls.len() > 1
1955 }
1956
1957 pub async fn setup_fork_db_config(
1963 &mut self,
1964 eth_rpc_url: String,
1965 evm_env: &mut EvmEnv,
1966 fees: &FeeManager,
1967 ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig)> {
1968 let (db, config, replay) =
1969 self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees, None).await?;
1970 eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1971 if !self.no_bal && !self.no_fork_node_info {
1972 config.prefill_cache(db.inner()).await;
1973 }
1974 Ok((db, config))
1975 }
1976
1977 pub(crate) async fn setup_fork_db_config_for_reset(
1979 &mut self,
1980 eth_rpc_url: String,
1981 evm_env: &mut EvmEnv,
1982 fees: &FeeManager,
1983 execution_profile: NetworkConfigs,
1984 ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig)> {
1985 let (db, config, replay) = self
1986 .setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees, Some(execution_profile))
1987 .await?;
1988 eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1989 Ok((db, config))
1990 }
1991
1992 pub(crate) async fn setup_fork_db_config_with_replay(
1993 &mut self,
1994 eth_rpc_url: String,
1995 evm_env: &mut EvmEnv,
1996 fees: &FeeManager,
1997 fixed_execution_profile: Option<NetworkConfigs>,
1998 ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig, Option<ForkTransactionReplay>)> {
1999 debug!(target: "node", eth_rpc_url=%redact_url(ð_rpc_url), "setting up fork db");
2000 if self.fork_chain_id.is_some() {
2001 eyre::ensure!(
2002 self.fork_urls.len() == 1,
2003 "multiple fork URLs cannot be validated with --fork-chain-id; remove \
2004 --fork-chain-id to validate every endpoint"
2005 );
2006 }
2007 let fork_overrides = *self.fork_overrides.get_or_insert(ForkOverrides {
2008 gas_limit: self.gas_limit,
2009 gas_price: self.gas_price,
2010 base_fee: self.base_fee,
2011 });
2012
2013 let provider = Arc::new(self.fork_provider(ð_rpc_url)?);
2017
2018 let StableForkSnapshot {
2021 endpoint_identity: fork_identity,
2022 state_is_mutable,
2023 block_number: fork_block_number,
2024 transaction_replay: fork_transaction_replay,
2025 block,
2026 gas_price,
2027 } = self.stable_fork_snapshot(&provider, fork_overrides).await?;
2028 self.fork_endpoint_is_anvil = fork_identity.is_authoritative();
2029
2030 let target_network = fork_identity.network.unwrap_or(NetworkVariant::Ethereum);
2031 let target_profile = fork_identity.network_profile.unwrap_or_default();
2032 if let Some(execution_profile) = fixed_execution_profile {
2033 if !self.has_explicit_network_selection() {
2034 eyre::ensure!(
2035 execution_profile.supports_fork_source(&target_profile),
2036 "cannot reset Anvil across network families ({} -> {}); start a new instance \
2037 with matching network configuration",
2038 execution_profile.execution_profile_name(),
2039 target_profile.execution_profile_name()
2040 );
2041 }
2042 self.networks = execution_profile;
2043 } else if self.inferred_fork_network.is_some() {
2044 eyre::ensure!(
2045 self.networks.supports_fork_source(&target_profile),
2046 "cannot reset Anvil across network families ({} -> {}); start a new instance \
2047 with matching network configuration",
2048 self.networks.execution_profile_name(),
2049 target_profile.execution_profile_name()
2050 );
2051 }
2052 let source_chain_id = fork_identity.source_chain_id;
2053 self.fork_source_chain_id = Some(source_chain_id);
2054 self.fork_execution_chain_id = Some(fork_identity.execution_chain_id);
2055 if fixed_execution_profile.is_none() && !self.has_explicit_network_selection() {
2056 self.networks = self.networks.with_rpc_profile(target_profile);
2057 self.inferred_fork_network = Some(target_network);
2058 self.chain_id_network_base = None;
2059 }
2060
2061 let block = if let Some(block) = block {
2062 block
2063 } else {
2064 if let Ok(latest_block) = provider.get_block_number().await {
2065 let mut message = format!(
2066 "Failed to get block for block number: {fork_block_number}\n\
2067latest block number: {latest_block}"
2068 );
2069 if fork_block_number <= latest_block {
2073 message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}"));
2074 }
2075 eyre::bail!("{message}");
2076 }
2077 eyre::bail!("failed to get block for block number: {fork_block_number}");
2078 };
2079
2080 if let Some(replay) = &fork_transaction_replay {
2081 let source_header = replay.source_block.header();
2082 eyre::ensure!(
2083 block.header.hash == source_header.parent_hash,
2084 "fork transaction block {} at {} has parent {}, but fetched fork block at {} has \
2085 hash {}",
2086 source_header.hash,
2087 source_header.number,
2088 source_header.parent_hash,
2089 block.header.number,
2090 block.header.hash
2091 );
2092 eyre::ensure!(
2093 block.header.number.checked_add(1) == Some(source_header.number),
2094 "fork transaction block {} has number {}, but fetched parent {} has number {}",
2095 source_header.hash,
2096 source_header.number,
2097 block.header.hash,
2098 block.header.number
2099 );
2100 }
2101
2102 let gas_limit = self.fork_gas_limit_with_override(&block, fork_overrides.gas_limit);
2103 self.gas_limit = Some(gas_limit);
2104
2105 let cache_block_env: BlockEnv = block_env_from_header(&block.header);
2108
2109 evm_env.block_env = BlockEnv {
2110 gas_limit,
2111 beneficiary: evm_env.block_env.beneficiary,
2113 basefee: fork_overrides
2114 .base_fee
2115 .or_else(|| block.header.base_fee_per_gas())
2116 .unwrap_or_default(),
2117 ..block_env_from_header(&block.header)
2118 };
2119
2120 let override_chain_id = self.chain_id;
2121 let execution_chain_id = override_chain_id.unwrap_or(fork_identity.execution_chain_id);
2122 if override_chain_id.is_none() {
2123 self.update_wallet_chain_id(fork_identity.execution_chain_id);
2126 }
2127 evm_env.cfg_env.chain_id = execution_chain_id;
2128
2129 let effective_network =
2133 self.networks.resolved_network().unwrap_or(NetworkVariant::Ethereum);
2134 let endpoint_hardfork = fork_identity
2135 .hardfork
2136 .filter(|hardfork| hardfork.namespace() == effective_network.hardfork_namespace());
2137 let inferred_hardfork = endpoint_hardfork.or_else(|| {
2138 effective_network.historical_hardfork(source_chain_id, block.header.timestamp())
2139 });
2140 let source_protocol_hardfork = fork_identity.hardfork.or_else(|| {
2141 source_hardfork(effective_network, source_chain_id, block.header.timestamp())
2142 });
2143 let source_may_omit_blob_fields = source_protocol_hardfork
2144 .map_or(self.hardfork.is_some(), |hardfork| SpecId::from(hardfork) < SpecId::CANCUN);
2145 let fork_hardfork = self.hardfork.or(inferred_hardfork);
2146 let effective_hardfork = fork_hardfork.unwrap_or_else(|| self.get_hardfork());
2147 let effective_spec = SpecId::from(effective_hardfork);
2148 evm_env.cfg_env.set_spec_and_mainnet_gas_params(effective_spec);
2149 fees.set_execution_rules(
2150 effective_spec,
2151 self.networks.base_fee_params(block.header.timestamp()),
2152 self.networks.is_tempo().then(|| TempoHardfork::from(effective_hardfork)),
2153 );
2154 #[cfg(feature = "optimism")]
2155 if self.networks.is_optimism() {
2156 fees.set_optimism_base_fee_rules(block.header.extra_data());
2157 }
2158
2159 self.base_fee = fork_overrides.base_fee.or_else(|| block.header.base_fee_per_gas());
2161 if let Some(base_fee) = fork_overrides.base_fee {
2162 fees.set_base_fee(base_fee);
2163 } else if let Some(base_fee) = block.header.base_fee_per_gas() {
2164 fees.set_base_fee(base_fee);
2167 let next_block_base_fee = fees.get_next_block_base_fee_from_header(&block.header);
2168 fees.set_base_fee(next_block_base_fee);
2169 } else {
2170 fees.set_base_fee(self.get_base_fee());
2171 }
2172
2173 let blob_params = get_blob_params(source_chain_id, block.header.timestamp());
2177 fees.set_blob_params(blob_params);
2178 let blob_update_fraction = blob_params.update_fraction as u64;
2179 let blob_excess_gas = block.header.excess_blob_gas().or_else(|| {
2180 (effective_spec >= SpecId::CANCUN
2186 && ((source_may_omit_blob_fields && block.header.blob_gas_used().is_none())
2187 || Chain::from_id(source_chain_id).is_polygon()
2188 || Chain::from_id(source_chain_id).is_arbitrum()))
2189 .then_some(0)
2190 });
2191 evm_env.block_env.blob_excess_gas_and_price =
2192 blob_excess_gas.map(|excess| BlobExcessGasAndPrice::new(excess, blob_update_fraction));
2193 let next_block_blob_excess_gas = blob_excess_gas.map_or(0, |excess| {
2194 self.networks.next_block_blob_excess_gas(
2195 blob_params,
2196 excess,
2197 block.header.blob_gas_used().unwrap_or_default(),
2198 block.header.base_fee_per_gas().unwrap_or_default(),
2199 )
2200 });
2201 fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
2202 next_block_blob_excess_gas,
2203 blob_update_fraction,
2204 ));
2205
2206 self.gas_price = Some(gas_price);
2208 fees.set_gas_price(gas_price);
2209
2210 let block_hash = block.header.hash;
2211
2212 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
2214 evm_env,
2215 &block,
2216 source_chain_id,
2217 self.networks,
2218 );
2219
2220 for mirror_url in self.fork_urls.iter().skip(1) {
2221 if !self
2222 .fork_context_matches(mirror_url, fork_identity, fork_block_number, block_hash)
2223 .await?
2224 {
2225 eyre::bail!(
2226 "fork fallback endpoint `{}` does not expose the primary endpoint's execution \
2227 and block context",
2228 redact_url(mirror_url)
2229 );
2230 }
2231 }
2232 if self.requires_primary_fork_revalidation(fork_identity)
2233 && !self
2234 .fork_context_matches(ð_rpc_url, fork_identity, fork_block_number, block_hash)
2235 .await?
2236 {
2237 eyre::bail!("primary fork endpoint changed while its context was being validated");
2238 }
2239
2240 let source_id = fork_source_id(&self.fork_urls, &self.fork_headers);
2241 let account_fetch_policy = account_fetch_policy_for_source(source_chain_id, target_profile);
2242 let meta = BlockchainDbMeta::new(cache_block_env, eth_rpc_url.clone())
2243 .with_fork_identity(block_hash, source_id)
2244 .with_account_fetch_policy(account_fetch_policy);
2245 let cache_path =
2246 self.block_cache_path_for_rpc(source_chain_id, fork_block_number, ð_rpc_url);
2247 let block_chain_db = BlockchainDb::new(meta, cache_path);
2248
2249 let provider = if self.fork_urls.len() > 1 {
2253 let urls = self.fork_urls.iter().map(|url| redact_url(url)).collect::<Vec<_>>();
2254 debug!(target: "node", ?urls, "using multi-endpoint round-robin provider");
2255 Arc::new(
2256 ProviderBuilder::new(ð_rpc_url)
2257 .timeout(self.fork_request_timeout)
2258 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
2259 .compute_units_per_second(self.compute_units_per_second)
2260 .max_retry(self.fork_request_retries)
2261 .headers(self.fork_headers.clone())
2262 .build_fallback(self.fork_urls.clone())
2263 .wrap_err("failed to establish round-robin provider to fork urls")?,
2264 )
2265 } else {
2266 provider
2267 };
2268
2269 let anchor = ForkBlock::with_rpc_number(
2272 evm_env.block_env.number.saturating_to(),
2273 fork_block_number,
2274 block_hash,
2275 );
2276 let (backend, handler) = if self.fork_state_by_number {
2277 SharedBackend::new_with_anchor_by_number(
2278 Arc::clone(&provider),
2279 block_chain_db.clone(),
2280 anchor,
2281 )?
2282 } else {
2283 SharedBackend::new_with_anchor(Arc::clone(&provider), block_chain_db.clone(), anchor)?
2284 };
2285 std::thread::Builder::new().name("fork-backend".into()).spawn(move || {
2289 tokio::runtime::Builder::new_current_thread()
2290 .enable_all()
2291 .build()
2292 .expect("failed to build fork backend runtime")
2293 .block_on(handler)
2294 })?;
2295
2296 let config = ClientForkConfig {
2297 fork_urls: self.fork_urls.clone(),
2298 block_number: fork_block_number,
2299 evm_block_number: evm_env.block_env.number.saturating_to(),
2300 block_hash,
2301 transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()),
2302 provider,
2303 chain_id: source_chain_id,
2304 execution_chain_id,
2305 override_chain_id,
2306 fork_chain_id: self.fork_chain_id.map(|chain_id| chain_id.to()),
2307 hardfork: Some(effective_hardfork),
2308 endpoint_identity: fork_identity,
2309 state_is_mutable,
2310 timestamp: block.header.timestamp(),
2311 base_fee: block.header.base_fee_per_gas().map(|g| g as u128),
2312 timeout: self.fork_request_timeout,
2313 retries: self.fork_request_retries,
2314 backoff: self.fork_retry_backoff,
2315 compute_units_per_second: self.compute_units_per_second,
2316 headers: self.fork_headers.clone(),
2317 total_difficulty: block.header.total_difficulty.unwrap_or_default(),
2318 blob_gas_used: block.header.blob_gas_used().map(|g| g as u128),
2319 blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price,
2320 };
2321
2322 debug!(target: "node", fork_number=config.block_number, fork_hash=%config.block_hash, "set up fork db");
2323
2324 let mut db = ForkedDatabase::new(backend, block_chain_db);
2325
2326 db.insert_block_hash(U256::from(config.block_number), config.block_hash);
2328
2329 Ok((db, config, fork_transaction_replay))
2330 }
2331
2332 fn fork_gas_limit_with_override<B: BlockResponse<Header: BlockHeader>>(
2336 &self,
2337 block: &B,
2338 gas_limit: Option<u64>,
2339 ) -> u64 {
2340 if !self.disable_block_gas_limit {
2341 if let Some(gas_limit) = gas_limit {
2342 return gas_limit;
2343 } else if block.header().gas_limit() > 0 {
2344 return block.header().gas_limit();
2345 }
2346 }
2347
2348 u64::MAX
2349 }
2350
2351 pub(crate) const fn restore_fork_overrides(&mut self) {
2353 if let Some(overrides) = self.fork_overrides {
2354 self.gas_limit = overrides.gas_limit;
2355 self.gas_price = overrides.gas_price;
2356 self.base_fee = overrides.base_fee;
2357 }
2358 }
2359
2360 pub(crate) fn gas_limit(&self) -> u64 {
2364 if self.disable_block_gas_limit {
2365 return u64::MAX;
2366 }
2367
2368 self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)
2369 }
2370}
2371
2372pub(crate) const fn tempo_default_base_fee(hardfork: TempoHardfork) -> u64 {
2373 if hardfork.is_t1() { TEMPO_T1_BASE_FEE } else { TEMPO_T0_BASE_FEE }
2374}
2375
2376async fn derive_block_and_replay(
2381 fork_choice: &ForkChoice,
2382 provider: &Arc<RetryProvider>,
2383) -> eyre::Result<(BlockNumber, Option<ForkTransactionReplay>)> {
2384 match fork_choice {
2385 ForkChoice::Block(block_number) => {
2386 let block_number = *block_number;
2387 if block_number >= 0 {
2388 return Ok((block_number as u64, None));
2389 }
2390 let latest = provider.get_block_number().await?;
2392
2393 Ok((block_number.saturating_add(latest as i128) as u64, None))
2394 }
2395 ForkChoice::Transaction(transaction_hash) => {
2396 let transaction = provider
2398 .get_transaction_by_hash(transaction_hash.0.into())
2399 .await?
2400 .ok_or_else(|| eyre::eyre!("fork transaction {transaction_hash} was not found"))?;
2401 let transaction_block_number = transaction.block_number().ok_or_else(|| {
2402 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
2403 })?;
2404 let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
2405 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
2406 })?;
2407
2408 let transaction_block =
2410 provider.get_block_by_hash(transaction_block_hash).full().await?.ok_or_else(
2411 || {
2412 eyre::eyre!(
2413 "failed to get fork block {transaction_block_hash} for transaction \
2414 {transaction_hash}"
2415 )
2416 },
2417 )?;
2418 let replay = validate_fork_transaction_replay(
2419 *transaction_hash,
2420 &transaction,
2421 transaction_block,
2422 )?;
2423 Ok((transaction_block_number.saturating_sub(1), Some(replay)))
2424 }
2425 }
2426}
2427
2428fn validate_fork_transaction_replay(
2429 transaction_hash: TxHash,
2430 transaction: &alloy_network::AnyRpcTransaction,
2431 source_block: AnyRpcBlock,
2432) -> eyre::Result<ForkTransactionReplay> {
2433 let source_hash = source_block.header.hash;
2434 let source_number = source_block.header.number;
2435 let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
2436 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
2437 })?;
2438 let transaction_block_number = transaction.block_number().ok_or_else(|| {
2439 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
2440 })?;
2441
2442 eyre::ensure!(
2443 source_hash == transaction_block_hash,
2444 "fork transaction {transaction_hash} reports block {transaction_block_hash}, but fetched \
2445 block hash is {source_hash}"
2446 );
2447 eyre::ensure!(
2448 source_number == transaction_block_number,
2449 "fork transaction {transaction_hash} reports block number {transaction_block_number}, but \
2450 fetched block {source_hash} has number {source_number}"
2451 );
2452 eyre::ensure!(
2453 source_number > 0,
2454 "fork transaction {transaction_hash} is in genesis block {source_hash}, which has no parent"
2455 );
2456
2457 let transactions = source_block.transactions.as_transactions().ok_or_else(|| {
2458 eyre::eyre!("fork block {source_hash} at {source_number} did not include full transactions")
2459 })?;
2460 let mut matches =
2461 transactions.iter().enumerate().filter(|(_, tx)| tx.tx_hash() == transaction_hash);
2462 let target_index = matches.next().map(|(index, _)| index).ok_or_else(|| {
2463 eyre::eyre!(
2464 "fork transaction {transaction_hash} is absent from block {source_hash} at \
2465 {source_number}"
2466 )
2467 })?;
2468 eyre::ensure!(
2469 matches.next().is_none(),
2470 "fork transaction {transaction_hash} occurs more than once in block {source_hash} at \
2471 {source_number}"
2472 );
2473 if let Some(reported_index) = transaction.transaction_index() {
2474 eyre::ensure!(
2475 reported_index == target_index as u64,
2476 "fork transaction {transaction_hash} reports index {reported_index}, but occurs at \
2477 index {target_index} in block {source_hash}"
2478 );
2479 }
2480
2481 Ok(ForkTransactionReplay { source_block, target_index })
2482}
2483
2484#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2486pub enum ForkChoice {
2487 Block(i128),
2491 Transaction(TxHash),
2493}
2494
2495impl ForkChoice {
2496 pub const fn block_number(&self) -> Option<i128> {
2498 match self {
2499 Self::Block(block_number) => Some(*block_number),
2500 Self::Transaction(_) => None,
2501 }
2502 }
2503
2504 pub const fn transaction_hash(&self) -> Option<TxHash> {
2506 match self {
2507 Self::Block(_) => None,
2508 Self::Transaction(transaction_hash) => Some(*transaction_hash),
2509 }
2510 }
2511}
2512
2513impl From<TxHash> for ForkChoice {
2515 fn from(tx_hash: TxHash) -> Self {
2516 Self::Transaction(tx_hash)
2517 }
2518}
2519
2520impl From<u64> for ForkChoice {
2522 fn from(block: u64) -> Self {
2523 Self::Block(block as i128)
2524 }
2525}
2526
2527#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2528pub struct PruneStateHistoryConfig {
2529 pub enabled: bool,
2530 pub max_memory_history: Option<usize>,
2531}
2532
2533impl PruneStateHistoryConfig {
2534 pub const fn is_state_history_supported(&self) -> bool {
2536 if !self.enabled {
2537 return true;
2538 }
2539
2540 match self.max_memory_history {
2541 Some(limit) => limit > 0,
2542 None => false,
2543 }
2544 }
2545
2546 pub const fn is_config_enabled(&self) -> bool {
2548 self.enabled
2549 }
2550
2551 pub fn from_args(val: Option<Option<usize>>) -> Self {
2552 val.map(|max_memory_history| Self {
2553 enabled: true,
2554 max_memory_history: max_memory_history.filter(|limit| *limit > 0),
2555 })
2556 .unwrap_or_default()
2557 }
2558}
2559
2560#[derive(Clone, Debug)]
2562pub struct AccountGenerator {
2563 chain_id: u64,
2564 amount: usize,
2565 phrase: String,
2566 derivation_path: Option<String>,
2567}
2568
2569impl AccountGenerator {
2570 pub fn new(amount: usize) -> Self {
2571 Self {
2572 chain_id: CHAIN_ID,
2573 amount,
2574 phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
2575 derivation_path: None,
2576 }
2577 }
2578
2579 #[must_use]
2580 pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
2581 self.phrase = phrase.into();
2582 self
2583 }
2584
2585 fn get_phrase(&self) -> &str {
2586 &self.phrase
2587 }
2588
2589 #[must_use]
2590 pub fn chain_id(mut self, chain_id: impl Into<u64>) -> Self {
2591 self.chain_id = chain_id.into();
2592 self
2593 }
2594
2595 #[must_use]
2596 pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
2597 let mut derivation_path = derivation_path.into();
2598 if !derivation_path.ends_with('/') {
2599 derivation_path.push('/');
2600 }
2601 self.derivation_path = Some(derivation_path);
2602 self
2603 }
2604
2605 fn get_derivation_path(&self) -> &str {
2606 self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
2607 }
2608}
2609
2610impl AccountGenerator {
2611 pub fn generate(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
2612 let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
2613
2614 let derivation_path = self.get_derivation_path();
2616 foundry_common::wallet::validate_bip32_path(derivation_path).map_err(|e| eyre::eyre!(e))?;
2617
2618 let mut wallets = Vec::with_capacity(self.amount);
2619 for idx in 0..self.amount {
2620 let idx = u32::try_from(idx).map_err(|_| eyre::eyre!("account index overflows u32"))?;
2621 let full_path = foundry_common::wallet::derive_key_path_checked(derivation_path, idx)
2622 .map_err(|e| eyre::eyre!(e))?;
2623 let builder = builder.clone().derivation_path(full_path)?;
2624 let wallet = builder.build()?.with_chain_id(Some(self.chain_id));
2625 wallets.push(wallet)
2626 }
2627 Ok(wallets)
2628 }
2629}
2630
2631pub fn anvil_dir() -> Option<PathBuf> {
2633 Config::foundry_dir().map(|p| p.join("anvil"))
2634}
2635
2636pub fn anvil_tmp_dir() -> Option<PathBuf> {
2638 anvil_dir().map(|p| p.join("tmp"))
2639}
2640
2641async fn find_latest_fork_block<P: Provider<AnyNetwork>>(
2646 provider: P,
2647) -> Result<u64, TransportError> {
2648 let mut num = provider.get_block_number().await?;
2649
2650 for _ in 0..2 {
2653 if let Some(block) = provider.get_block(num.into()).await?
2654 && !block.header.hash.is_zero()
2655 {
2656 break;
2657 }
2658 num = num.saturating_sub(1)
2660 }
2661
2662 Ok(num)
2663}
2664
2665#[cfg(test)]
2666mod tests {
2667 use super::*;
2668 use foundry_evm::{hardfork::EthereumHardfork, hardforks::latest_active_tempo_hardfork};
2669
2670 #[cfg(unix)]
2671 use std::os::unix::fs::PermissionsExt;
2672
2673 #[cfg(feature = "base")]
2674 use foundry_evm::hardforks::BaseUpgrade;
2675
2676 #[cfg(feature = "optimism")]
2677 use foundry_evm::hardfork::OpHardfork;
2678
2679 #[cfg(not(feature = "base"))]
2680 use alloy_chains::NamedChain;
2681
2682 #[cfg(feature = "base")]
2683 #[tokio::test(flavor = "multi_thread")]
2684 async fn base_chain_inference_uses_native_base_forks() {
2685 for chain_id in [8453u64, 84532] {
2686 let (origin_api, origin) = crate::spawn(
2687 NodeConfig::test()
2688 .with_chain_id(Some(chain_id))
2689 .with_genesis_timestamp(Some(1_710_374_401u64)),
2690 )
2691 .await;
2692 assert!(origin_api.backend.is_base());
2693 assert_eq!(origin_api.backend.hardfork(), BaseUpgrade::Ecotone.into());
2694
2695 let anonymous_url = foundry_test_utils::rpc::spawn_rpc_proxy_rejecting_method_after(
2696 origin.http_endpoint(),
2697 "anvil_nodeInfo",
2698 0,
2699 )
2700 .await;
2701 for endpoint in [origin.http_endpoint(), anonymous_url] {
2702 let (api, _handle) =
2703 crate::spawn(NodeConfig::test().with_eth_rpc_url(Some(endpoint))).await;
2704 assert!(api.backend.is_base());
2705 assert_eq!(api.backend.hardfork(), BaseUpgrade::Ecotone.into());
2706 let fork = api.backend.get_fork().unwrap();
2707 assert_eq!(
2708 fork.config.read().endpoint_identity.network,
2709 Some(NetworkVariant::Base)
2710 );
2711 }
2712 }
2713 }
2714
2715 #[tokio::test(flavor = "multi_thread")]
2716 async fn fork_output_redacts_endpoint_credentials() {
2717 let (_api, source) = crate::spawn(NodeConfig::test()).await;
2718 let fork_url = source.http_endpoint().replacen("http://", "http://user:password@", 1)
2719 + "/?api_key=secret";
2720 let mut config = NodeConfig::test().with_eth_rpc_url(Some(fork_url.clone()));
2721 let (api, _handle) = crate::spawn(config.clone()).await;
2722 config.fork_urls.push("https://mirror.example/private-api-key?token=secret".to_string());
2723
2724 let fork = api.backend.get_fork().unwrap();
2725 let node_info = api.anvil_node_info().await.unwrap();
2726 let output = config.as_string(Some(&fork));
2727 let temp = tempfile::tempdir().unwrap();
2728 let config_out = temp.path().join("config.json");
2729 config.config_out = Some(config_out.clone());
2730 config.print(Some(&fork)).unwrap();
2731 let json = serde_json::from_slice::<Value>(&std::fs::read(config_out).unwrap()).unwrap();
2732
2733 assert!(output.contains(&redact_url(&fork_url)));
2734 assert!(output.contains("https://mirror.example/"));
2735 assert!(!output.contains("user"));
2736 assert!(!output.contains("password"));
2737 assert!(!output.contains("private-api-key"));
2738 assert!(!output.contains("secret"));
2739 assert_eq!(json["endpoint"], redact_url(&fork_url));
2740 assert_eq!(node_info.fork_config.fork_url, Some(redact_url(&fork_url)));
2741 assert!(!json.to_string().contains("password"));
2742 assert!(!json.to_string().contains("secret"));
2743 }
2744
2745 #[test]
2746 fn fork_source_identity_includes_all_urls_and_headers() {
2747 let urls = ["http://primary".to_string(), "http://fallback".to_string()];
2748 let headers = ["Authorization: secret".to_string()];
2749 let identity = fork_source_id(&urls, &headers);
2750
2751 assert_ne!(identity, fork_source_id(&urls[..1], &headers));
2752 assert_ne!(identity, fork_source_id(&urls, &[]));
2753 assert_ne!(identity, fork_source_id(&[urls[1].clone(), urls[0].clone()], &headers));
2754 }
2755
2756 #[test]
2757 fn test_prune_history() {
2758 let config = PruneStateHistoryConfig::default();
2759 assert!(config.is_state_history_supported());
2760 let config = PruneStateHistoryConfig::from_args(Some(None));
2761 assert!(!config.is_state_history_supported());
2762 let config = PruneStateHistoryConfig::from_args(Some(Some(0)));
2763 assert!(config.is_config_enabled());
2764 assert!(!config.is_state_history_supported());
2765 let config = PruneStateHistoryConfig::from_args(Some(Some(10)));
2766 assert!(config.is_state_history_supported());
2767 }
2768
2769 #[test]
2770 fn fork_cache_path_can_use_source_chain() {
2771 let rpc_url = "http://localhost:8545";
2772 let mut config = NodeConfig::test()
2773 .with_eth_rpc_url(Some(rpc_url.to_string()))
2774 .with_chain_id(Some(1u64));
2775 let block = 42;
2776 config.fork_source_chain_id = Some(143);
2777 let expected = Config::foundry_block_cache_file(143, block)
2778 .map(|path| path.with_file_name(format!("storage-{:x}.json", keccak256(rpc_url))));
2779
2780 assert_eq!(config.block_cache_path(block), expected);
2781 assert_ne!(
2782 config.block_cache_path_for_rpc(143, block, rpc_url),
2783 config.block_cache_path_for_rpc(143, block, "http://localhost:8546")
2784 );
2785 }
2786
2787 #[test]
2788 fn fork_execution_and_source_chain_ids_remain_distinct() {
2789 let mut config = NodeConfig::test();
2790 config.fork_execution_chain_id = Some(1);
2791 config.fork_source_chain_id = Some(143);
2792
2793 assert_eq!(config.get_chain_id(), 1);
2794 assert_eq!(config.protocol_chain_id(), 143);
2795 }
2796
2797 #[test]
2798 fn fork_chain_id_is_only_an_offline_discovery_hint() {
2799 let mut config = NodeConfig::test()
2800 .with_chain_id(Some(31_337u64))
2801 .with_fork_chain_id(Some(U256::from(143)));
2802
2803 assert_eq!(config.protocol_chain_id(), 31_337);
2804
2805 config.fork_source_chain_id = Some(143);
2806 assert_eq!(config.protocol_chain_id(), 143);
2807
2808 config.fork_source_chain_id = None;
2809 assert_eq!(config.protocol_chain_id(), 31_337);
2810 }
2811
2812 #[test]
2813 fn fork_endpoint_revalidation_requires_authority_or_fallbacks() {
2814 let anonymous = ForkEndpointIdentity {
2815 execution_chain_id: 1,
2816 source_chain_id: 1,
2817 network: Some(NetworkVariant::Ethereum),
2818 network_profile: Some(NetworkConfigs::default()),
2819 hardfork: None,
2820 instance_id: None,
2821 source_fork_block_number: None,
2822 source_fork_block_hash: None,
2823 };
2824 let mut config =
2825 NodeConfig::test().with_eth_rpc_url(Some("http://localhost:8545".to_string()));
2826
2827 assert!(!anonymous.is_authoritative());
2828 assert!(!config.requires_primary_fork_revalidation(anonymous));
2829
2830 config.fork_urls.push("http://localhost:8546".to_string());
2831 assert!(config.requires_primary_fork_revalidation(anonymous));
2832 assert_eq!(
2833 NodeConfig::fork_urls_requiring_revalidation(&config.fork_urls, anonymous),
2834 config.fork_urls
2835 );
2836
2837 config.fork_urls.pop();
2838 let authoritative =
2839 ForkEndpointIdentity { hardfork: Some(EthereumHardfork::Prague.into()), ..anonymous };
2840 assert!(authoritative.is_authoritative());
2841 assert!(config.requires_primary_fork_revalidation(authoritative));
2842 assert_eq!(
2843 NodeConfig::fork_urls_requiring_revalidation(&config.fork_urls, authoritative),
2844 config.fork_urls
2845 );
2846 }
2847
2848 #[tokio::test]
2849 async fn fork_authoritative_identity_keeps_node_info_probe_strict() {
2850 let (_api, origin) =
2851 crate::spawn(NodeConfig::test().with_chain_id(Some(NamedChain::Mainnet as u64))).await;
2852 let fork_url = foundry_test_utils::rpc::spawn_rpc_proxy_rejecting_method_after(
2853 origin.http_endpoint(),
2854 "anvil_nodeInfo",
2855 0,
2856 )
2857 .await;
2858 let expected = ForkEndpointIdentity {
2859 execution_chain_id: NamedChain::Mainnet as u64,
2860 source_chain_id: NamedChain::Mainnet as u64,
2861 network: Some(NetworkVariant::Ethereum),
2862 network_profile: Some(NetworkConfigs::default()),
2863 hardfork: Some(EthereumHardfork::Prague.into()),
2864 instance_id: Some(B256::with_last_byte(1)),
2865 source_fork_block_number: None,
2866 source_fork_block_hash: None,
2867 };
2868
2869 let error = NodeConfig::test()
2870 .fork_context_matches(&fork_url, expected, 0, B256::ZERO)
2871 .await
2872 .unwrap_err();
2873
2874 assert!(
2875 error.to_string().contains("failed to determine network family from fork endpoint"),
2876 "{error}"
2877 );
2878 }
2879
2880 #[cfg(feature = "optimism")]
2881 #[test]
2882 fn set_chain_id_updates_network_config() {
2883 let mut config = NodeConfig::test();
2884 config.set_chain_id(Some(10u64));
2885
2886 assert!(config.networks.is_optimism());
2887 }
2888
2889 #[test]
2890 fn chain_id_network_inference_is_replaceable_and_clearable() {
2891 let mut config = NodeConfig::test();
2892 config.set_chain_id(Some(4217u64));
2893 assert!(config.networks.is_tempo());
2894
2895 config.set_chain_id(Some(NamedChain::Celo as u64));
2896 assert!(config.networks.is_celo());
2897 assert!(!config.networks.is_tempo());
2898
2899 config.set_chain_id(Some(1u64));
2900 assert!(!config.networks.has_network_selection());
2901
2902 config.set_chain_id(Some(4217u64));
2903 config.set_chain_id(None::<u64>);
2904 assert!(!config.networks.has_network_selection());
2905 }
2906
2907 #[test]
2908 fn chain_id_preserves_explicit_network_selection() {
2909 let mut config = NodeConfig::test_tempo();
2910 config.set_chain_id(Some(NamedChain::Celo as u64));
2911
2912 assert!(config.networks.is_tempo());
2913 assert!(!config.networks.is_celo());
2914 }
2915
2916 #[test]
2917 fn get_hardfork_on_tempo_never_returns_non_tempo_variant() {
2918 let shanghai_ts = 1_681_338_455u64;
2920
2921 let config = NodeConfig::test_tempo()
2922 .with_chain_id(Some(1u64))
2923 .with_genesis_timestamp(Some(shanghai_ts));
2924
2925 assert!(config.networks.is_tempo());
2926 assert!(matches!(config.get_hardfork(), FoundryHardfork::Tempo(_)));
2927 }
2928
2929 #[test]
2930 fn get_hardfork_on_ethereum_uses_genesis_timestamp() {
2931 let timestamp = EthereumHardfork::Shanghai.mainnet_activation_timestamp().unwrap();
2932 let config =
2933 NodeConfig::test().with_chain_id(Some(1u64)).with_genesis_timestamp(Some(timestamp));
2934
2935 assert_eq!(config.get_hardfork(), FoundryHardfork::Ethereum(EthereumHardfork::Shanghai));
2936 }
2937
2938 #[test]
2939 #[cfg(feature = "optimism")]
2940 fn get_hardfork_on_optimism_uses_genesis_timestamp() {
2941 let timestamp = 1_704_992_401u64;
2943 let config = NodeConfig::test()
2944 .with_optimism()
2945 .with_chain_id(Some(10u64))
2946 .with_genesis_timestamp(Some(timestamp));
2947
2948 assert_eq!(config.get_hardfork(), FoundryHardfork::Optimism(OpHardfork::Canyon));
2949 }
2950
2951 #[test]
2952 fn get_hardfork_on_local_tempo_defaults_to_latest_active() {
2953 let config = NodeConfig::test_tempo();
2954
2955 assert_eq!(config.get_hardfork(), FoundryHardfork::Tempo(latest_active_tempo_hardfork()));
2956 }
2957
2958 #[test]
2959 #[cfg(feature = "base")]
2960 fn test_base_config_uses_base_network_and_chain_id() {
2961 let config = NodeConfig::test_base();
2962
2963 assert!(config.networks.is_base());
2964 assert_eq!(config.get_chain_id(), NamedChain::Base as u64);
2965 }
2966
2967 #[test]
2968 #[cfg(feature = "base")]
2969 fn get_hardfork_on_base_fork_uses_source_chain_timestamp_mapping() {
2970 let mut config = NodeConfig::test_base()
2971 .with_chain_id(Some(1u64))
2972 .with_genesis_timestamp(Some(u64::MAX));
2973 config.fork_source_chain_id = Some(NamedChain::Base as u64);
2974
2975 assert_eq!(config.get_chain_id(), 1);
2976 assert_eq!(config.get_hardfork(), FoundryHardfork::Base(BaseUpgrade::Beryl));
2977 }
2978
2979 #[test]
2980 #[cfg(feature = "monad")]
2981 fn get_hardfork_on_monad_fork_uses_source_chain_timestamp_mapping() {
2982 let mut config = NodeConfig::test_monad()
2983 .with_chain_id(Some(1u64))
2984 .with_genesis_timestamp(Some(1_763_648_999u64));
2985 config.fork_source_chain_id = Some(143);
2986
2987 assert_eq!(config.get_chain_id(), 1);
2988 assert_eq!(
2989 config.get_hardfork(),
2990 FoundryHardfork::Monad(foundry_evm::hardfork::MonadHardfork::MonadEight)
2991 );
2992 }
2993
2994 #[test]
2995 fn account_generator_rejects_harden_bit_overflow_path() {
2996 let err = AccountGenerator::new(1)
2997 .phrase("test test test test test test test test test test test junk")
2998 .derivation_path("m/44'/60'/0'/0/2147483648'")
2999 .generate()
3000 .unwrap_err()
3001 .to_string();
3002 assert!(err.contains("harden bit"), "{err}");
3003
3004 assert!(
3005 AccountGenerator::new(1)
3006 .phrase("test test test test test test test test test test test junk")
3007 .derivation_path("m/44'/60'/0'/0")
3008 .generate()
3009 .is_ok()
3010 );
3011 }
3012
3013 #[cfg(unix)]
3014 #[test]
3015 fn config_out_has_owner_only_permissions() {
3016 let dir = tempfile::tempdir().unwrap();
3017 let path = dir.path().join("config.json");
3018 let config = NodeConfig::test().set_config_out(Some(path.clone()));
3019
3020 config.print(None).unwrap();
3021
3022 let mode = std::fs::metadata(path).unwrap().permissions().mode();
3023 assert_eq!(mode & 0o777, 0o600);
3024 }
3025}