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