Skip to main content

anvil/
config.rs

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
87/// Default port the rpc will open
88pub const NODE_PORT: u16 = 8545;
89/// Default chain id of the node
90pub const CHAIN_ID: u64 = 31337;
91/// The default gas limit for all transactions
92pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
93/// The default number of slots in an epoch used for safe/finalized block tags.
94pub const DEFAULT_SLOTS_IN_AN_EPOCH: u64 = 32;
95/// Default mnemonic for dev accounts
96pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
97/// Default number of block heights in a Tempo consensus epoch.
98pub const DEFAULT_TEMPO_EPOCH_LENGTH: NonZeroU64 = NonZeroU64::new(21_600).unwrap();
99
100/// Resolves source-chain upgrades without changing another execution family's historical lookup.
101pub(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
146/// Keep optional identity probes from delaying startup on RPCs that stall on unknown methods.
147const FORK_IDENTITY_PROBE_TIMEOUT: Duration = Duration::from_millis(500);
148
149/// Best-effort Anvil detection that becomes strict after positive identification.
150///
151/// Before the first successful `anvil_nodeInfo` response, any probe failure means that the
152/// optional capability is unavailable. Mandatory standard RPC reads still expose endpoint-wide
153/// failures. Once a response or cached endpoint identity identifies Anvil, every later probe
154/// RPC failure is returned so it cannot hide an endpoint reset or execution-profile change.
155/// Probe timeouts always mean that the optional capability is unavailable.
156#[derive(Clone, Copy, Debug, Default)]
157struct AnvilNodeInfoProbe {
158    identified: bool,
159    skip: bool,
160    /// A failed probe could not distinguish an unsupported method from a mutable Anvil source.
161    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
204/// Decodes an `anvil_nodeInfo` response and the blob params that newer Anvil versions add to it.
205///
206/// `WithOtherFields<NodeInfo>` cannot decode it: serde's flatten buffer rejects the `u128` retry
207/// backoff that fork endpoints report.
208fn 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/// One-shot source data for a transaction-hash fork replay.
219#[derive(Clone, Debug)]
220pub(crate) struct ForkTransactionReplay {
221    pub(crate) source_block: AnyRpcBlock,
222    pub(crate) target_index: usize,
223}
224
225/// The default IPC endpoint
226pub 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/// Configurations of the EVM node
251#[derive(Clone, Debug)]
252pub struct NodeConfig {
253    /// Chain ID of the EVM chain
254    pub chain_id: Option<u64>,
255    /// Default gas limit for all txs
256    pub gas_limit: Option<u64>,
257    /// If set to `true`, disables the block gas limit
258    pub disable_block_gas_limit: bool,
259    /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825). From Amsterdam,
260    /// the cap always applies to regular gas only (EIP-8037).
261    pub enable_tx_gas_limit: bool,
262    /// Default gas price for all txs
263    pub gas_price: Option<u128>,
264    /// Default base fee
265    pub base_fee: Option<u64>,
266    /// If set to `true`, disables the enforcement of a minimum suggested priority fee
267    pub disable_min_priority_fee: bool,
268    /// Default blob excess gas and price
269    pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
270    /// The hardfork to force, or `None` to infer it from chain activation data.
271    pub hardfork: Option<FoundryHardfork>,
272    /// Signer accounts that will be initialised with `genesis_balance` in the genesis block
273    pub genesis_accounts: Vec<PrivateKeySigner>,
274    /// Native token balance of every genesis account in the genesis block
275    pub genesis_balance: U256,
276    /// Genesis block timestamp
277    pub genesis_timestamp: Option<u64>,
278    /// Genesis block number
279    pub genesis_block_number: Option<u64>,
280    /// Signer accounts that can sign messages/transactions from the EVM node
281    pub signer_accounts: Vec<PrivateKeySigner>,
282    /// Configured block time for the EVM chain. Use `None` for instant/auto mining.
283    pub block_time: Option<Duration>,
284    /// Window for grouping ready transactions in auto mining; zero disables coalescing.
285    pub transaction_coalescing_window: Duration,
286    /// Disable auto and interval mining mode and use `MiningMode::None` instead.
287    pub no_mining: bool,
288    /// Enables auto and interval mining mode
289    pub mixed_mining: bool,
290    /// port to use for the server
291    pub port: u16,
292    /// maximum number of transactions in a block
293    pub max_transactions: usize,
294    /// Fork URLs for RPC calls. The first entry is the primary endpoint.
295    /// When multiple URLs are provided, requests are distributed using
296    /// round-robin load balancing with retry-based failover.
297    pub fork_urls: Vec<String>,
298    /// pins the block number or transaction hash for the state fork
299    pub fork_choice: Option<ForkChoice>,
300    /// headers to use with fork RPC endpoints
301    pub fork_headers: Vec<String>,
302    /// specifies chain id for cache to skip fetching from remote in offline-start mode
303    pub fork_chain_id: Option<U256>,
304    /// Skip `anvil_nodeInfo` / `anvil_metadata` probes against the fork URL.
305    pub no_fork_node_info: bool,
306    /// Disables opportunistic BAL post-state prefill of the remote fork cache.
307    pub no_bal: bool,
308    /// Address fork state reads by block number instead of by block hash.
309    pub fork_state_by_number: bool,
310    /// Chain ID discovered from the active fork source.
311    pub fork_source_chain_id: Option<u64>,
312    /// Chain ID exposed by the active fork endpoint.
313    pub fork_execution_chain_id: Option<u64>,
314    /// Whether the active fork endpoint has positively identified itself as Anvil.
315    pub(crate) fork_endpoint_is_anvil: bool,
316    /// Network family most recently inferred from a fork endpoint.
317    inferred_fork_network: Option<NetworkVariant>,
318    /// Network configuration replaced by chain-ID inference, if any.
319    chain_id_network_base: Option<NetworkConfigs>,
320    /// User-provided gas settings captured before fork-derived values are materialized.
321    fork_overrides: Option<ForkOverrides>,
322    /// The generator used to generate the dev accounts
323    pub account_generator: Option<AccountGenerator>,
324    /// whether to enable tracing
325    pub enable_tracing: bool,
326    /// Explicitly disables the use of RPC caching.
327    pub no_storage_caching: bool,
328    /// How to configure the server
329    pub server_config: ServerConfig,
330    /// The host the server will listen on
331    pub host: Vec<IpAddr>,
332    /// How transactions are sorted in the mempool
333    pub transaction_order: TransactionOrder,
334    /// Filename to write anvil output as json
335    pub config_out: Option<PathBuf>,
336    /// The genesis to use to initialize the node
337    pub genesis: Option<Genesis>,
338    /// Timeout in for requests sent to remote JSON-RPC server in forking mode
339    pub fork_request_timeout: Duration,
340    /// Number of request retries for spurious networks
341    pub fork_request_retries: u32,
342    /// The initial retry backoff
343    pub fork_retry_backoff: Duration,
344    /// available CUPS
345    pub compute_units_per_second: u64,
346    /// The ipc path
347    pub ipc_path: Option<Option<String>>,
348    /// Enable transaction/call steps tracing for debug calls returning geth-style traces
349    pub enable_steps_tracing: bool,
350    /// Enable printing of `console.log` invocations.
351    pub print_logs: bool,
352    /// Enable printing of traces.
353    pub print_traces: bool,
354    /// Enable auto impersonation of accounts on startup
355    pub enable_auto_impersonate: bool,
356    /// Configure the code size limit
357    pub code_size_limit: Option<usize>,
358    /// Configures how to remove historic state.
359    ///
360    /// If set to `Some(num)` keep latest num state in memory only.
361    pub prune_history: PruneStateHistoryConfig,
362    /// Max number of states cached on disk.
363    pub max_persisted_states: Option<usize>,
364    /// The initial state to apply and consume during startup.
365    pub init_state: Option<SerializableState>,
366    /// max number of blocks with transactions in memory
367    pub transaction_block_keeper: Option<usize>,
368    /// Disable the default CREATE2 deployer
369    pub disable_default_create2_deployer: bool,
370    /// Disable pool balance checks
371    pub disable_pool_balance_checks: bool,
372    /// Slots in an epoch
373    pub slots_in_an_epoch: u64,
374    /// The memory limit per EVM execution in bytes.
375    pub memory_limit: Option<u64>,
376    /// Factory used by `anvil` to extend the EVM's precompiles.
377    pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
378    /// Networks to enable features for.
379    pub networks: NetworkConfigs,
380    /// Overrides the Base activation-registry administrator.
381    #[cfg(feature = "base")]
382    pub base_activation_admin: Option<Address>,
383    /// The account used to sponsor Tempo fee-payer requests.
384    ///
385    /// Must be an unlocked signer account. Defaults to the last dev account on Tempo networks.
386    pub tempo_fee_payer: Option<Address>,
387    /// Number of block heights in a Tempo consensus epoch.
388    pub tempo_epoch_length: NonZeroU64,
389    /// Optional development consensus seed; otherwise derived from the account mnemonic.
390    pub tempo_consensus_seed: Option<B256>,
391    /// Do not print log messages.
392    pub silent: bool,
393    /// The path where persisted states are cached (used with `max_persisted_states`).
394    /// This does not affect the fork RPC cache location.
395    pub cache_path: Option<PathBuf>,
396    /// Accounts to fund with specific balances on startup (address -> balance in wei).
397    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            // if we have a disabled flag we should max out the limit
599            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    /// Returns a new config intended to be used in tests, which does not print and binds to a
634    /// random, free port by setting it to `0`
635    #[doc(hidden)]
636    pub fn test() -> Self {
637        Self { enable_tracing: true, port: 0, silent: true, ..Default::default() }
638    }
639
640    /// Returns a test config with Tempo network enabled.
641    #[doc(hidden)]
642    pub fn test_tempo() -> Self {
643        Self { networks: NetworkConfigs::with_tempo(), ..Self::test() }
644    }
645
646    /// Returns a test config with Monad network enabled.
647    #[cfg(feature = "monad")]
648    #[doc(hidden)]
649    pub fn test_monad() -> Self {
650        Self { networks: NetworkConfigs::with_monad(), ..Self::test() }
651    }
652
653    /// Returns a test config with Base network enabled.
654    #[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    /// Returns a new config which does not initialize any accounts on node startup.
663    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        // generate some random wallets
676        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            // 100ETH default balance
692            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            // alchemy max cpus <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
731            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    /// Applies Tempo's safe default beneficiary for forked nodes while preserving
758    /// explicit coinbase selections.
759    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            // Tempo mainnet maps the zero validator token to a DONOTUSE sentinel.
765            // Forked transactions with the default zero beneficiary can therefore
766            // fail fee collection before producing a receipt. Use the same neutral
767            // fee-recipient sentinel as Tempo's simulation path so validator token
768            // lookup falls back to the default PathUSD token unless the user has
769            // explicitly supplied a non-zero coinbase.
770            evm_env.block_env.beneficiary = TIP_FEE_MANAGER_ADDRESS;
771        }
772    }
773
774    /// Sets the execution spec, its mainnet gas parameters, and the transaction gas limit cap.
775    ///
776    /// The Amsterdam gas features follow the spec in both directions, so switching to an earlier
777    /// spec disables them. The EIP-7825 cap is lifted unless `enable_tx_gas_limit` is set, except
778    /// under EIP-8037, where the cap divides a transaction's gas into regular gas and the state
779    /// gas reservoir instead of limiting it.
780    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    /// Returns the memory limit of the node
789    #[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    /// Returns the base fee to use.
796    ///
797    /// In Tempo mode, uses the hardfork-specific base fee (10 gwei pre-T1, 20 gwei T1+).
798    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    /// Returns the gas price to use.
810    ///
811    /// In Tempo mode, defaults to the hardfork-specific base fee.
812    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    /// Returns the [`BlobParams`] that should be used.
835    pub fn get_blob_params(&self) -> BlobParams {
836        get_blob_params_by_hardfork(self.get_hardfork())
837    }
838
839    /// Returns the hardfork to use
840    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    /// Sets a custom code size limit
850    #[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    /// Disables  code size limit
856    #[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    /// Sets the init state if any
865    #[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    /// Loads the init state from a file if it exists
872    #[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    /// Sets the chain ID
880    #[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    /// Returns the chain ID to use
887    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    /// Returns the chain ID that defines protocol behavior.
895    fn protocol_chain_id(&self) -> u64 {
896        self.fork_source_chain_id.unwrap_or_else(|| self.get_chain_id())
897    }
898
899    /// Sets the chain id and updates all wallets
900    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    /// Sets the gas limit
925    #[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    /// Disable block gas limit check
932    ///
933    /// If set to `true` block gas limit will not be enforced
934    #[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    /// Enable tx gas limit check
941    ///
942    /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825)
943    #[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    /// Sets the gas price
950    #[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    /// Sets prune history status.
957    #[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    /// Sets max number of states to cache on disk.
964    #[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    /// Sets the max number of transactions in a block
974    #[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    /// Sets max number of blocks with transactions to keep in memory
983    #[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    /// Sets the base fee
993    #[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    /// Disable the enforcement of a minimum suggested priority fee
1000    #[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    /// Sets the init genesis (genesis.json)
1007    #[must_use]
1008    pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
1009        self.genesis = genesis;
1010        self
1011    }
1012
1013    /// Returns the genesis timestamp to use
1014    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    /// Sets the genesis timestamp
1021    #[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    /// Sets the genesis number
1030    #[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    /// Returns the genesis number
1039    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    /// Sets the hardfork
1046    #[must_use]
1047    pub const fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
1048        self.hardfork = hardfork;
1049        self
1050    }
1051
1052    /// Sets the genesis accounts
1053    #[must_use]
1054    pub fn with_genesis_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
1055        self.genesis_accounts = accounts;
1056        self
1057    }
1058
1059    /// Sets the signer accounts
1060    #[must_use]
1061    pub fn with_signer_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
1062        self.signer_accounts = accounts;
1063        self
1064    }
1065
1066    /// Sets both the genesis accounts and the signer accounts
1067    /// so that `genesis_accounts == accounts`
1068    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    /// Sets the balance of the genesis accounts in the genesis block
1075    #[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    /// Sets the block time to automine blocks
1082    #[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    /// Sets the auto-mining coalescing window. Zero disables the delay.
1100    #[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    /// If set to `true` auto mining will be disabled
1107    #[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    /// Sets the slots in an epoch
1114    #[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    /// Sets the port to use
1121    #[must_use]
1122    pub const fn with_port(mut self, port: u16) -> Self {
1123        self.port = port;
1124        self
1125    }
1126
1127    /// Sets the ipc path to use
1128    ///
1129    /// Note: this is a double Option for
1130    ///     - `None` -> no ipc
1131    ///     - `Some(None)` -> use default path
1132    ///     - `Some(Some(path))` -> use custom path
1133    #[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    /// Sets the file path to write the Anvil node's config info to.
1140    #[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    /// Sets the `eth_rpc_url` to use when forking (single endpoint convenience).
1153    #[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    /// Sets the fork URLs for load-balanced multi-endpoint forking.
1166    #[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    /// Sets the `fork_choice` to use to fork off from based on a block number
1176    #[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    /// Sets the `fork_choice` to use to fork off from based on a transaction hash
1182    #[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    /// Sets the `fork_choice` to use to fork off from
1191    #[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    /// Sets whether fork state reads are addressed by block number instead of by block hash.
1198    #[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    /// Disables opportunistic BAL post-state prefill of the remote fork cache.
1205    #[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    /// Sets the `fork_chain_id` to use to fork off local cache from
1212    #[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    /// Skip `anvil_nodeInfo` / `anvil_metadata` probes against the fork URL.
1219    #[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    /// Sets the `fork_headers` to use with fork RPC endpoints
1230    #[must_use]
1231    pub fn with_fork_headers(mut self, headers: Vec<String>) -> Self {
1232        self.fork_headers = headers;
1233        self
1234    }
1235
1236    /// Sets the `fork_request_timeout` to use for requests
1237    #[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    /// Sets the `fork_request_retries` to use for spurious networks
1246    #[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    /// Sets the initial `fork_retry_backoff` for rate limits
1255    #[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    /// Sets the number of assumed available compute units per second
1264    ///
1265    /// See also, <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
1266    #[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    /// Sets whether to enable tracing
1278    #[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    /// Sets whether to enable steps tracing
1285    #[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    /// Sets whether to print `console.log` invocations to stdout.
1292    #[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    /// Sets whether to print traces to stdout.
1299    #[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    /// Sets whether to enable autoImpersonate
1306    #[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    /// Sets the host the server will listen on
1319    #[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    /// Returns the ipc path for the ipc endpoint if any
1332    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    /// Prints the config info
1340    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    /// Returns the endpoint-specific path where the cache file should be stored.
1358    ///
1359    /// See also [`Config::foundry_block_cache_file`].
1360    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    /// Sets whether to disable the default create2 deployer
1382    #[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    /// Sets whether to disable pool balance checks
1389    #[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    /// Injects precompiles to `anvil`'s EVM.
1396    #[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    /// Enable features for provided networks.
1403    #[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    /// Enable Tempo network features.
1412    #[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    /// Sets the account used to sponsor Tempo fee-payer requests.
1421    #[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    /// Sets the number of block heights in a Tempo consensus epoch.
1428    #[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    /// Overrides the development consensus seed on Tempo networks.
1435    #[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    /// Returns the explicit seed or derives it from the effective dev-account mnemonic.
1442    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    /// Returns the effective account used to sponsor Tempo fee-payer requests.
1453    ///
1454    /// Defaults to the last dev account so it rarely collides with the sender accounts commonly
1455    /// used in tests, mirroring the dedicated sponsor account of hosted fee payer services.
1456    /// Returns `None` on non-Tempo networks.
1457    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    /// Enable Monad network features.
1465    #[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    /// Enable Base network features.
1475    #[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    /// Sets the Base activation-registry administrator override.
1485    #[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    /// Enable Optimism network features.
1493    #[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    /// Makes the node silent to not emit anything on stdout
1503    #[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    /// Sets the path where persisted states are cached (used with `max_persisted_states`).
1515    ///
1516    /// Note: This does not control the fork RPC cache location, which uses endpoint-specific files
1517    /// under `~/.foundry/cache/rpc/<chain>/<block>/`.
1518    #[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    /// Sets accounts to fund with custom balances on startup.
1525    #[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    /// Configures everything related to env, backend and database and returns the
1532    /// [Backend](mem::Backend)
1533    ///
1534    /// *Note*: only memory based backend for now
1535    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        // configure the revm environment
1545
1546        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        // EIP-3607 rejects transactions from senders with deployed code.
1552        // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the
1553        // caller is a contract. So we disable the check by default.
1554        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        // On Tempo, the base fee follows the chain's hardfork rules instead of EIP-1559.
1577        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 provided use all settings of `genesis.json`
1606        if let Some(ref genesis) = self.genesis {
1607            // --chain-id flag gets precedence over the genesis.json chain id
1608            // <https://github.com/foundry-rs/foundry/issues/10059>
1609            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        // Fork setup initializes its own timestamp. For a local chain, keep the initial EVM and
1623        // genesis block on the same resolved timestamp, so that calls against the latest block
1624        // observe the genesis time, and timestamp-activated precompiles and validity windows
1625        // behave the same before and after the first mined block.
1626        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 traces should get printed we configure the decoder with the signatures cache
1649            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        // only memory based backend for now
1656        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        // Writes the default create2 deployer to the backend,
1677        // if the option is not disabled and we are not forking.
1678        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    /// Configures everything related to forking based on the passed `eth_rpc_url`:
1703    ///  - returning a tuple of a [ForkedDatabase] wrapped in an [Arc] [RwLock](TokioRwLock) and
1704    ///    [ClientFork] wrapped in an [Option] which can be used in a [Backend](mem::Backend) to
1705    ///    fork from.
1706    ///  - modifying some parameters of the passed `env`
1707    ///  - mutating some members of `self`
1708    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            // `fork_chain_id` avoids depending on `eth_chainId`, but an online Anvil endpoint can
1851            // still expose authoritative family, hardfork, and instance metadata. Probe it so
1852            // mirror validation and reset staging cannot mistake this node for its own upstream.
1853            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    /// Configures everything related to forking based on the passed `eth_rpc_url`:
2027    ///  - returning a tuple of a [ForkedDatabase] and [ClientForkConfig] which can be used to build
2028    ///    a [ClientFork] to fork from.
2029    ///  - modifying some parameters of the passed `env`
2030    ///  - mutating some members of `self`
2031    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    /// Configures a replacement fork while preserving the already-instantiated execution profile.
2047    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(&eth_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        // Always bootstrap with the primary URL only to avoid race conditions
2083        // where discovery calls (get_chain_id, find_latest_fork_block, get_block)
2084        // hit different endpoints that may be at different chain tips.
2085        let provider = Arc::new(self.fork_provider(&eth_rpc_url)?);
2086
2087        // Resolve identity, block, and fee data as one stable snapshot. An upstream Anvil can
2088        // reset between any two RPC calls, so verify the endpoint identity on both sides.
2089        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 the `eth_getBlockByNumber` call succeeds, but returns null instead of
2139                // the block, and the block number is less than equal the latest block, then
2140                // the user is forking from a non-archive node with an older block number.
2141                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        // Cache identity must describe the remote fork block, not local execution overrides that
2175        // can change after mining (for example, the locally advanced base fee).
2176        let cache_block_env: BlockEnv = block_env_from_header(&block.header);
2177
2178        evm_env.block_env = BlockEnv {
2179            gas_limit,
2180            // Preserve configured local overrides while replacing fork-derived block values.
2181            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            // Sign locally produced transactions for the chain ID exposed by the endpoint
2193            // without turning the inferred value into an explicit execution override.
2194            self.update_wallet_chain_id(fork_identity.execution_chain_id);
2195        }
2196        evm_env.cfg_env.chain_id = execution_chain_id;
2197
2198        // Resolve the fork block's hardfork without materializing it into `self.hardfork`.
2199        // That field represents the user's explicit override; keeping inference on the fork
2200        // config lets a later reset re-resolve timestamp-based activations.
2201        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        // if not set explicitly we use the base fee of the latest block
2229        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            // This is the base fee of the current block, but we need the base fee of the next
2234            // block.
2235            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        // Blob rules and fee state belong to the selected fork context even when the target block
2243        // predates Cancun. Always replace both so a reset cannot retain the previous fork's
2244        // schedule or excess gas. The source headers' excess blob gas was produced under the source
2245        // chain's rules, so prefer its public schedule. For chains without one, use the blob params
2246        // an Anvil endpoint reports: it can be a fork that executes a different hardfork than the
2247        // blob schedule it inherited, so its hardfork is only a fallback for older versions.
2248        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            // Pre-Cancun headers, Polygon Bor headers, and Arbitrum Nitro headers omit the blob
2261            // fields. REVM still requires a valid blob environment when executing with the Cancun
2262            // spec; zero is the neutral excess-gas value. On Nitro this makes `BLOBBASEFEE` return
2263            // `1`, although Nitro rejects the opcode; matching that requires Arbitrum-specific EVM
2264            // handling.
2265            (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        // Use the gas price captured in the stable endpoint snapshot.
2287        self.gas_price = Some(gas_price);
2288        fees.set_gas_price(gas_price);
2289
2290        let block_hash = block.header.hash;
2291
2292        // Apply changes such as difficulty -> prevrandao for the remote source chain.
2293        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(&eth_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, &eth_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(&eth_rpc_url);
2332            }
2333            ProviderBuilder::new(&eth_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        // After bootstrap, rebuild the provider with round-robin if multiple URLs are
2343        // configured. This ensures bootstrap used only the primary endpoint for consistency,
2344        // while ongoing requests are distributed across all endpoints.
2345        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        // This will spawn the background thread that will use the provider to fetch
2354        // blockchain data from the other client
2355        let anchor = ForkBlock::with_rpc_number(
2356            evm_env.block_env.number.saturating_to(),
2357            fork_block_number,
2358            block_hash,
2359        );
2360        // Keep the worker's connections on its runtime, independent of foreground RPC requests.
2361        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        // The handler gets its own thread and runtime: `SharedBackend` reads block the calling
2372        // thread via `block_in_place`, so under enough concurrent reads they can occupy every
2373        // thread of the node's runtime and leave none to poll the handler they wait on.
2374        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        // need to insert the forked block's hash
2413        db.insert_block_hash(U256::from(config.block_number), config.block_hash);
2414
2415        Ok((db, config, fork_transaction_replay))
2416    }
2417
2418    /// we only use the gas limit value of the block if it is non-zero and the block gas
2419    /// limit is enabled, since there are networks where this is not used and is always
2420    /// `0x0` which would inevitably result in `OutOfGas` errors as soon as the evm is about to record gas, See also <https://github.com/foundry-rs/foundry/issues/3247>
2421    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    /// Restores user-provided gas settings after leaving fork mode.
2438    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    /// Returns the gas limit for a non forked anvil instance
2447    ///
2448    /// Checks the config for the `disable_block_gas_limit` flag
2449    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
2462/// If the fork choice is a block number, simply return it with an empty list of transactions.
2463/// If the fork choice is a transaction hash, determine the block that the transaction was mined in,
2464/// and return the block number before the fork block along with all transactions in the fork block
2465/// that are before (and including) the fork transaction.
2466async 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            // subtract from latest block number
2477            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            // Determine the block that this transaction was mined in
2483            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            // Get the block pertaining to the fork transaction.
2495            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/// Fork delimiter used to specify which block or transaction to fork from.
2571#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2572pub enum ForkChoice {
2573    /// Block number to fork from.
2574    ///
2575    /// If negative, the given value is subtracted from the `latest` block number.
2576    Block(i128),
2577    /// Transaction hash to fork from.
2578    Transaction(TxHash),
2579}
2580
2581impl ForkChoice {
2582    /// Returns the block number to fork from
2583    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    /// Returns the transaction hash to fork from
2591    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
2599/// Convert a transaction hash into a ForkChoice
2600impl From<TxHash> for ForkChoice {
2601    fn from(tx_hash: TxHash) -> Self {
2602        Self::Transaction(tx_hash)
2603    }
2604}
2605
2606/// Convert a decimal block number into a ForkChoice
2607impl 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    /// Returns `true` if writing state history is supported
2621    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    /// Returns true if this setting was enabled.
2633    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/// Can create dev accounts
2647#[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        // use the derivation path
2701        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
2717/// Returns the path to anvil dir `~/.foundry/anvil`
2718pub fn anvil_dir() -> Option<PathBuf> {
2719    Config::foundry_dir().map(|p| p.join("anvil"))
2720}
2721
2722/// Returns the root path to anvil's temporary storage `~/.foundry/anvil/`
2723pub fn anvil_tmp_dir() -> Option<PathBuf> {
2724    anvil_dir().map(|p| p.join("tmp"))
2725}
2726
2727/// Finds the latest appropriate block to fork
2728///
2729/// This fetches the "latest" block and checks whether the `Block` is fully populated (`hash` field
2730/// is present). This prevents edge cases where anvil forks the "latest" block but `eth_getBlockByNumber` still returns a pending block, <https://github.com/foundry-rs/foundry/issues/2036>
2731async 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    // walk back from the head of the chain, but at most 2 blocks, which should be more than enough
2737    // leeway
2738    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        // block not actually finalized, so we try the block before
2745        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        // Post-Shanghai timestamp on Ethereum mainnet.
3007        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        // OP Mainnet Canyon activation timestamp.
3030        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}