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            time::duration_since_unix_epoch,
12        },
13        fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE},
14        pool::transactions::TransactionOrder,
15    },
16    mem::{self, in_memory_db::StateRootDb},
17};
18use alloy_chains::{Chain, NamedChain};
19use alloy_consensus::BlockHeader;
20use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams};
21use alloy_evm::EvmEnv;
22use alloy_genesis::Genesis;
23use alloy_network::{AnyNetwork, AnyRpcBlock, BlockResponse, TransactionResponse};
24use alloy_primitives::{
25    Address, B256, BlockNumber, TxHash, U256, hex, keccak256, map::HashMap, utils::Unit,
26};
27use alloy_provider::Provider;
28use alloy_rpc_types::{
29    BlockNumberOrTag,
30    anvil::{Metadata, NodeInfo},
31};
32use alloy_signer::Signer;
33use alloy_signer_local::{
34    MnemonicBuilder, PrivateKeySigner,
35    coins_bip39::{English, Mnemonic},
36};
37use alloy_transport::TransportError;
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::{BlockchainDb, BlockchainDbMeta, ForkBlock, SharedBackend},
47    constants::DEFAULT_CREATE2_DEPLOYER,
48    hardfork::FoundryHardfork,
49    utils::{apply_chain_and_block_specific_env_changes_for_chain, block_env_from_header},
50};
51use parking_lot::RwLock;
52use rand_08::thread_rng;
53use revm::{
54    context::{BlockEnv, CfgEnv},
55    context_interface::block::BlobExcessGasAndPrice,
56    primitives::hardfork::SpecId,
57};
58use serde_json::{Value, json};
59use std::{
60    fmt::Write as FmtWrite,
61    net::{IpAddr, Ipv4Addr},
62    path::PathBuf,
63    sync::Arc,
64    time::Duration,
65};
66use tempo_hardfork::{
67    TempoHardfork,
68    constants::gas::{TEMPO_T0_BASE_FEE, TEMPO_T1_BASE_FEE},
69};
70use tokio::sync::RwLock as TokioRwLock;
71use yansi::Paint;
72
73pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
74use foundry_evm::{
75    traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier},
76    utils::{get_blob_params, get_blob_params_by_hardfork},
77};
78use foundry_evm_networks::{NetworkConfigs, NetworkVariant};
79use tempo_precompiles::TIP_FEE_MANAGER_ADDRESS;
80
81/// Default port the rpc will open
82pub const NODE_PORT: u16 = 8545;
83/// Default chain id of the node
84pub const CHAIN_ID: u64 = 31337;
85/// The default gas limit for all transactions
86pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
87/// The default number of slots in an epoch used for safe/finalized block tags.
88pub const DEFAULT_SLOTS_IN_AN_EPOCH: u64 = 32;
89/// Default mnemonic for dev accounts
90pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
91
92#[derive(Clone, Copy, Debug)]
93struct ForkOverrides {
94    gas_limit: Option<u64>,
95    gas_price: Option<u128>,
96    base_fee: Option<u64>,
97}
98
99struct StableForkSnapshot {
100    endpoint_identity: ForkEndpointIdentity,
101    block_number: u64,
102    transaction_replay: Option<ForkTransactionReplay>,
103    block: Option<AnyRpcBlock>,
104    gas_price: u128,
105}
106
107/// Best-effort Anvil detection that becomes strict after positive identification.
108///
109/// Before the first successful `anvil_nodeInfo` response, any probe failure means that the
110/// optional capability is unavailable. Mandatory standard RPC reads still expose endpoint-wide
111/// failures. Once a response or cached endpoint identity identifies Anvil, every later probe
112/// failure is returned so it cannot hide an endpoint reset or execution-profile change.
113#[derive(Clone, Copy, Debug, Default)]
114struct AnvilNodeInfoProbe {
115    identified: bool,
116}
117
118impl AnvilNodeInfoProbe {
119    const fn new(identified: bool) -> Self {
120        Self { identified }
121    }
122
123    async fn request(&mut self, provider: &RetryProvider) -> Result<Option<NodeInfo>> {
124        match provider.raw_request::<_, NodeInfo>("anvil_nodeInfo".into(), ()).await {
125            Ok(node_info) => {
126                self.identified = true;
127                Ok(Some(node_info))
128            }
129            Err(_) if !self.identified => Ok(None),
130            Err(error) => {
131                Err(error).wrap_err("failed to determine network family from fork endpoint")
132            }
133        }
134    }
135}
136
137/// One-shot source data for a transaction-hash fork replay.
138#[derive(Clone, Debug)]
139pub(crate) struct ForkTransactionReplay {
140    pub(crate) source_block: AnyRpcBlock,
141    pub(crate) target_index: usize,
142}
143
144/// The default IPC endpoint
145pub const DEFAULT_IPC_ENDPOINT: &str =
146    if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
147
148const BANNER: &str = r"
149                             _   _
150                            (_) | |
151      __ _   _ __   __   __  _  | |
152     / _` | | '_ \  \ \ / / | | | |
153    | (_| | | | | |  \ V /  | | | |
154     \__,_| |_| |_|   \_/   |_| |_|
155";
156
157fn fork_source_id(urls: &[String], headers: &[String]) -> B256 {
158    let mut encoded = Vec::new();
159    for parts in [urls, headers] {
160        encoded.extend_from_slice(&(parts.len() as u64).to_be_bytes());
161        for part in parts {
162            encoded.extend_from_slice(&(part.len() as u64).to_be_bytes());
163            encoded.extend_from_slice(part.as_bytes());
164        }
165    }
166    keccak256(encoded)
167}
168
169/// Configurations of the EVM node
170#[derive(Clone, Debug)]
171pub struct NodeConfig {
172    /// Chain ID of the EVM chain
173    pub chain_id: Option<u64>,
174    /// Default gas limit for all txs
175    pub gas_limit: Option<u64>,
176    /// If set to `true`, disables the block gas limit
177    pub disable_block_gas_limit: bool,
178    /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825)
179    pub enable_tx_gas_limit: bool,
180    /// Default gas price for all txs
181    pub gas_price: Option<u128>,
182    /// Default base fee
183    pub base_fee: Option<u64>,
184    /// If set to `true`, disables the enforcement of a minimum suggested priority fee
185    pub disable_min_priority_fee: bool,
186    /// Default blob excess gas and price
187    pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
188    /// The hardfork to force, or `None` to infer it from chain activation data.
189    pub hardfork: Option<FoundryHardfork>,
190    /// Signer accounts that will be initialised with `genesis_balance` in the genesis block
191    pub genesis_accounts: Vec<PrivateKeySigner>,
192    /// Native token balance of every genesis account in the genesis block
193    pub genesis_balance: U256,
194    /// Genesis block timestamp
195    pub genesis_timestamp: Option<u64>,
196    /// Genesis block number
197    pub genesis_block_number: Option<u64>,
198    /// Signer accounts that can sign messages/transactions from the EVM node
199    pub signer_accounts: Vec<PrivateKeySigner>,
200    /// Configured block time for the EVM chain. Use `None` for instant/auto mining.
201    pub block_time: Option<Duration>,
202    /// Disable auto and interval mining mode and use `MiningMode::None` instead.
203    pub no_mining: bool,
204    /// Enables auto and interval mining mode
205    pub mixed_mining: bool,
206    /// port to use for the server
207    pub port: u16,
208    /// maximum number of transactions in a block
209    pub max_transactions: usize,
210    /// Fork URLs for RPC calls. The first entry is the primary endpoint.
211    /// When multiple URLs are provided, requests are distributed using
212    /// round-robin load balancing with retry-based failover.
213    pub fork_urls: Vec<String>,
214    /// pins the block number or transaction hash for the state fork
215    pub fork_choice: Option<ForkChoice>,
216    /// headers to use with fork RPC endpoints
217    pub fork_headers: Vec<String>,
218    /// specifies chain id for cache to skip fetching from remote in offline-start mode
219    pub fork_chain_id: Option<U256>,
220    /// Chain ID discovered from the active fork source.
221    pub fork_source_chain_id: Option<u64>,
222    /// Chain ID exposed by the active fork endpoint.
223    pub fork_execution_chain_id: Option<u64>,
224    /// Whether the active fork endpoint has positively identified itself as Anvil.
225    pub(crate) fork_endpoint_is_anvil: bool,
226    /// Network family most recently inferred from a fork endpoint.
227    inferred_fork_network: Option<NetworkVariant>,
228    /// Network configuration replaced by chain-ID inference, if any.
229    chain_id_network_base: Option<NetworkConfigs>,
230    /// User-provided gas settings captured before fork-derived values are materialized.
231    fork_overrides: Option<ForkOverrides>,
232    /// The generator used to generate the dev accounts
233    pub account_generator: Option<AccountGenerator>,
234    /// whether to enable tracing
235    pub enable_tracing: bool,
236    /// Explicitly disables the use of RPC caching.
237    pub no_storage_caching: bool,
238    /// How to configure the server
239    pub server_config: ServerConfig,
240    /// The host the server will listen on
241    pub host: Vec<IpAddr>,
242    /// How transactions are sorted in the mempool
243    pub transaction_order: TransactionOrder,
244    /// Filename to write anvil output as json
245    pub config_out: Option<PathBuf>,
246    /// The genesis to use to initialize the node
247    pub genesis: Option<Genesis>,
248    /// Timeout in for requests sent to remote JSON-RPC server in forking mode
249    pub fork_request_timeout: Duration,
250    /// Number of request retries for spurious networks
251    pub fork_request_retries: u32,
252    /// The initial retry backoff
253    pub fork_retry_backoff: Duration,
254    /// available CUPS
255    pub compute_units_per_second: u64,
256    /// The ipc path
257    pub ipc_path: Option<Option<String>>,
258    /// Enable transaction/call steps tracing for debug calls returning geth-style traces
259    pub enable_steps_tracing: bool,
260    /// Enable printing of `console.log` invocations.
261    pub print_logs: bool,
262    /// Enable printing of traces.
263    pub print_traces: bool,
264    /// Enable auto impersonation of accounts on startup
265    pub enable_auto_impersonate: bool,
266    /// Configure the code size limit
267    pub code_size_limit: Option<usize>,
268    /// Configures how to remove historic state.
269    ///
270    /// If set to `Some(num)` keep latest num state in memory only.
271    pub prune_history: PruneStateHistoryConfig,
272    /// Max number of states cached on disk.
273    pub max_persisted_states: Option<usize>,
274    /// The initial state to apply and consume during startup.
275    pub init_state: Option<SerializableState>,
276    /// max number of blocks with transactions in memory
277    pub transaction_block_keeper: Option<usize>,
278    /// Disable the default CREATE2 deployer
279    pub disable_default_create2_deployer: bool,
280    /// Disable pool balance checks
281    pub disable_pool_balance_checks: bool,
282    /// Slots in an epoch
283    pub slots_in_an_epoch: u64,
284    /// The memory limit per EVM execution in bytes.
285    pub memory_limit: Option<u64>,
286    /// Factory used by `anvil` to extend the EVM's precompiles.
287    pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
288    /// Networks to enable features for.
289    pub networks: NetworkConfigs,
290    /// The account used to sponsor Tempo fee-payer requests.
291    ///
292    /// Must be an unlocked signer account. Defaults to the last dev account on Tempo networks.
293    pub tempo_fee_payer: Option<Address>,
294    /// Do not print log messages.
295    pub silent: bool,
296    /// The path where persisted states are cached (used with `max_persisted_states`).
297    /// This does not affect the fork RPC cache location.
298    pub cache_path: Option<PathBuf>,
299    /// Accounts to fund with specific balances on startup (address -> balance in wei).
300    pub funded_accounts: HashMap<Address, U256>,
301}
302
303impl NodeConfig {
304    fn as_string(&self, fork: Option<&ClientFork>) -> String {
305        let mut s: String = String::new();
306        let _ = write!(s, "\n{}", BANNER.green());
307        let _ = write!(s, "\n    {VERSION_MESSAGE}");
308        let _ = write!(s, "\n    {}", "https://github.com/foundry-rs/foundry".green());
309
310        let _ = write!(
311            s,
312            r#"
313
314Available Accounts
315==================
316"#
317        );
318        let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
319        for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
320            write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap();
321        }
322
323        let _ = write!(
324            s,
325            r#"
326
327Private Keys
328==================
329"#
330        );
331
332        for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
333            let hex = hex::encode(wallet.credential().to_bytes());
334            let _ = write!(s, "\n({idx}) 0x{hex}");
335        }
336
337        if let Some(generator) = &self.account_generator {
338            let _ = write!(
339                s,
340                r#"
341
342Wallet
343==================
344Mnemonic:          {}
345Derivation path:   {}
346"#,
347                generator.phrase,
348                generator.get_derivation_path()
349            );
350        }
351
352        if let Some(fee_payer) = self.tempo_fee_payer_address() {
353            let _ = write!(
354                s,
355                r#"
356
357Tempo Fee Payer
358==================
359{fee_payer}
360"#
361            );
362        }
363
364        if let Some(fork) = fork {
365            let _ = write!(
366                s,
367                r#"
368
369Fork
370==================
371Endpoint:       {}
372Block number:   {}
373Block hash:     {:?}
374Chain ID:       {}
375"#,
376                fork.eth_rpc_url().as_deref().map(redact_url).unwrap_or_else(|| "none".to_string()),
377                fork.block_number(),
378                fork.block_hash(),
379                fork.execution_chain_id()
380            );
381            if fork.chain_id() != fork.execution_chain_id() {
382                let _ = writeln!(s, "Source chain ID: {}", fork.chain_id());
383            }
384
385            if self.fork_urls.len() > 1 {
386                let _ = writeln!(s, "Endpoints:      {}", self.fork_urls.len());
387                for (i, url) in self.fork_urls.iter().enumerate() {
388                    let _ = writeln!(s, "  ({i}) {}", redact_url(url));
389                }
390            }
391
392            if let Some(tx_hash) = fork.transaction_hash() {
393                let _ = writeln!(s, "Transaction hash: {tx_hash}");
394            }
395        } else {
396            let _ = write!(
397                s,
398                r#"
399
400Chain ID
401==================
402
403{}
404"#,
405                self.get_chain_id().green()
406            );
407        }
408
409        if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) {
410            let _ = write!(
411                s,
412                r#"
413Gas Price
414==================
415
416{}
417"#,
418                self.get_gas_price().green()
419            );
420        } else {
421            let _ = write!(
422                s,
423                r#"
424Base Fee
425==================
426
427{}
428"#,
429                self.get_base_fee().green()
430            );
431        }
432
433        let _ = write!(
434            s,
435            r#"
436Gas Limit
437==================
438
439{}
440"#,
441            {
442                if self.disable_block_gas_limit {
443                    "Disabled".to_string()
444                } else {
445                    self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| {
446                        if self.fork_choice.is_some() {
447                            "Forked".to_string()
448                        } else {
449                            DEFAULT_GAS_LIMIT.to_string()
450                        }
451                    })
452                }
453            }
454            .green()
455        );
456
457        let _ = write!(
458            s,
459            r#"
460Genesis Timestamp
461==================
462
463{}
464"#,
465            self.get_genesis_timestamp().green()
466        );
467
468        let _ = write!(
469            s,
470            r#"
471Genesis Number
472==================
473
474{}
475"#,
476            self.get_genesis_number().green()
477        );
478
479        s
480    }
481
482    fn as_json(&self, fork: Option<&ClientFork>) -> Value {
483        let mut wallet_description = HashMap::new();
484        let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
485        let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
486
487        for wallet in &self.genesis_accounts {
488            available_accounts.push(format!("{:?}", wallet.address()));
489            private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes())));
490        }
491
492        if let Some(generator) = &self.account_generator {
493            let phrase = generator.get_phrase().to_string();
494            let derivation_path = generator.get_derivation_path().to_string();
495
496            wallet_description.insert("derivation_path".to_string(), derivation_path);
497            wallet_description.insert("mnemonic".to_string(), phrase);
498        };
499
500        let gas_limit = match self.gas_limit {
501            // if we have a disabled flag we should max out the limit
502            Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()),
503            Some(limit) => Some(limit.to_string()),
504            _ => None,
505        };
506
507        if let Some(fork) = fork {
508            json!({
509              "available_accounts": available_accounts,
510              "private_keys": private_keys,
511              "endpoint": fork.eth_rpc_url().as_deref().map(redact_url).unwrap_or_default(),
512              "block_number": fork.block_number(),
513              "block_hash": fork.block_hash(),
514              "chain_id": fork.execution_chain_id(),
515              "source_chain_id": fork.chain_id(),
516              "wallet": wallet_description,
517              "base_fee": format!("{}", self.get_base_fee()),
518              "gas_price": format!("{}", self.get_gas_price()),
519              "gas_limit": gas_limit,
520            })
521        } else {
522            json!({
523              "available_accounts": available_accounts,
524              "private_keys": private_keys,
525              "wallet": wallet_description,
526              "base_fee": format!("{}", self.get_base_fee()),
527              "gas_price": format!("{}", self.get_gas_price()),
528              "gas_limit": gas_limit,
529              "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
530            })
531        }
532    }
533}
534
535impl NodeConfig {
536    /// Returns a new config intended to be used in tests, which does not print and binds to a
537    /// random, free port by setting it to `0`
538    #[doc(hidden)]
539    pub fn test() -> Self {
540        Self { enable_tracing: true, port: 0, silent: true, ..Default::default() }
541    }
542
543    /// Returns a test config with Tempo network enabled.
544    #[doc(hidden)]
545    pub fn test_tempo() -> Self {
546        Self { networks: NetworkConfigs::with_tempo(), ..Self::test() }
547    }
548
549    /// Returns a test config with Monad network enabled.
550    #[cfg(feature = "monad")]
551    #[doc(hidden)]
552    pub fn test_monad() -> Self {
553        Self { networks: NetworkConfigs::with_monad(), ..Self::test() }
554    }
555
556    /// Returns a new config which does not initialize any accounts on node startup.
557    pub fn empty_state() -> Self {
558        Self {
559            genesis_accounts: vec![],
560            signer_accounts: vec![],
561            disable_default_create2_deployer: true,
562            ..Default::default()
563        }
564    }
565}
566
567impl Default for NodeConfig {
568    fn default() -> Self {
569        // generate some random wallets
570        let genesis_accounts = AccountGenerator::new(10)
571            .phrase(DEFAULT_MNEMONIC)
572            .generate()
573            .expect("Invalid mnemonic.");
574        Self {
575            chain_id: None,
576            gas_limit: None,
577            disable_block_gas_limit: false,
578            enable_tx_gas_limit: false,
579            gas_price: None,
580            hardfork: None,
581            signer_accounts: genesis_accounts.clone(),
582            genesis_timestamp: None,
583            genesis_block_number: None,
584            genesis_accounts,
585            // 100ETH default balance
586            genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
587            block_time: None,
588            no_mining: false,
589            mixed_mining: false,
590            port: NODE_PORT,
591            max_transactions: 1_000,
592            fork_urls: vec![],
593            fork_choice: None,
594            account_generator: None,
595            base_fee: None,
596            disable_min_priority_fee: false,
597            blob_excess_gas_and_price: None,
598            enable_tracing: true,
599            enable_steps_tracing: false,
600            print_logs: true,
601            print_traces: false,
602            enable_auto_impersonate: false,
603            no_storage_caching: false,
604            server_config: Default::default(),
605            host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
606            transaction_order: Default::default(),
607            config_out: None,
608            genesis: None,
609            fork_request_timeout: REQUEST_TIMEOUT,
610            fork_headers: vec![],
611            fork_request_retries: 5,
612            fork_retry_backoff: Duration::from_millis(1_000),
613            fork_chain_id: None,
614            fork_source_chain_id: None,
615            fork_execution_chain_id: None,
616            fork_endpoint_is_anvil: false,
617            inferred_fork_network: None,
618            chain_id_network_base: None,
619            fork_overrides: None,
620            // alchemy max cpus <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
621            compute_units_per_second: ALCHEMY_FREE_TIER_CUPS,
622            ipc_path: None,
623            code_size_limit: None,
624            prune_history: Default::default(),
625            max_persisted_states: None,
626            init_state: None,
627            transaction_block_keeper: None,
628            disable_default_create2_deployer: false,
629            disable_pool_balance_checks: false,
630            slots_in_an_epoch: DEFAULT_SLOTS_IN_AN_EPOCH,
631            memory_limit: None,
632            precompile_factory: None,
633            networks: Default::default(),
634            tempo_fee_payer: None,
635            silent: false,
636            cache_path: None,
637            funded_accounts: HashMap::default(),
638        }
639    }
640}
641
642impl NodeConfig {
643    /// Applies Tempo's safe default beneficiary for forked nodes while preserving
644    /// explicit coinbase selections.
645    pub(crate) fn apply_tempo_fork_beneficiary_default<N>(&self, evm_env: &mut EvmEnv<N>) {
646        if self.networks.is_tempo()
647            && !self.fork_urls.is_empty()
648            && evm_env.block_env.beneficiary.is_zero()
649        {
650            // Tempo mainnet maps the zero validator token to a DONOTUSE sentinel.
651            // Forked transactions with the default zero beneficiary can therefore
652            // fail fee collection before producing a receipt. Use the same neutral
653            // fee-recipient sentinel as Tempo's simulation path so validator token
654            // lookup falls back to the default PathUSD token unless the user has
655            // explicitly supplied a non-zero coinbase.
656            evm_env.block_env.beneficiary = TIP_FEE_MANAGER_ADDRESS;
657        }
658    }
659
660    /// Returns the memory limit of the node
661    #[must_use]
662    pub const fn with_memory_limit(mut self, mems_value: Option<u64>) -> Self {
663        self.memory_limit = mems_value;
664        self
665    }
666
667    /// Returns the base fee to use.
668    ///
669    /// In Tempo mode, uses the hardfork-specific base fee (10 gwei pre-T1, 20 gwei T1+).
670    pub fn get_base_fee(&self) -> u64 {
671        let default = if self.networks.is_tempo() {
672            tempo_default_base_fee(TempoHardfork::from(self.get_hardfork()))
673        } else {
674            INITIAL_BASE_FEE
675        };
676        self.base_fee
677            .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64)))
678            .unwrap_or(default)
679    }
680
681    /// Returns the gas price to use.
682    ///
683    /// In Tempo mode, defaults to the hardfork-specific base fee.
684    pub fn get_gas_price(&self) -> u128 {
685        let default = if self.networks.is_tempo() {
686            tempo_default_base_fee(TempoHardfork::from(self.get_hardfork())) as u128
687        } else {
688            INITIAL_GAS_PRICE
689        };
690        self.gas_price.unwrap_or(default)
691    }
692
693    pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice {
694        if let Some(value) = self.blob_excess_gas_and_price {
695            value
696        } else {
697            let excess_blob_gas =
698                self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0);
699            BlobExcessGasAndPrice::new(
700                excess_blob_gas,
701                self.get_blob_params().update_fraction as u64,
702            )
703        }
704    }
705
706    /// Returns the [`BlobParams`] that should be used.
707    pub fn get_blob_params(&self) -> BlobParams {
708        get_blob_params_by_hardfork(self.get_hardfork())
709    }
710
711    /// Returns the hardfork to use
712    pub fn get_hardfork(&self) -> FoundryHardfork {
713        if let Some(hardfork) = self.hardfork {
714            return hardfork;
715        }
716        self.networks
717            .execution_network()
718            .hardfork_at(self.protocol_chain_id(), self.get_genesis_timestamp())
719    }
720
721    /// Sets a custom code size limit
722    #[must_use]
723    pub const fn with_code_size_limit(mut self, code_size_limit: Option<usize>) -> Self {
724        self.code_size_limit = code_size_limit;
725        self
726    }
727    /// Disables  code size limit
728    #[must_use]
729    pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self {
730        if disable_code_size_limit {
731            self.code_size_limit = Some(usize::MAX);
732        }
733        self
734    }
735
736    /// Sets the init state if any
737    #[must_use]
738    pub fn with_init_state(mut self, init_state: Option<SerializableState>) -> Self {
739        self.init_state = init_state;
740        self
741    }
742
743    /// Loads the init state from a file if it exists
744    #[must_use]
745    #[cfg(feature = "cmd")]
746    pub fn with_init_state_path(mut self, path: impl AsRef<std::path::Path>) -> Self {
747        self.init_state = crate::cmd::StateFile::parse_path(path).ok().and_then(|file| file.state);
748        self
749    }
750
751    /// Sets the chain ID
752    #[must_use]
753    pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
754        self.set_chain_id(chain_id);
755        self
756    }
757
758    /// Returns the chain ID to use
759    pub fn get_chain_id(&self) -> u64 {
760        self.chain_id
761            .or(self.fork_execution_chain_id)
762            .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
763            .unwrap_or(CHAIN_ID)
764    }
765
766    /// Returns the chain ID that defines protocol behavior.
767    fn protocol_chain_id(&self) -> u64 {
768        self.fork_source_chain_id.unwrap_or_else(|| self.get_chain_id())
769    }
770
771    /// Sets the chain id and updates all wallets
772    pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
773        if let Some(base) = self.chain_id_network_base.take() {
774            self.networks = base;
775        }
776        self.chain_id = chain_id.map(Into::into);
777        let chain_id = self.get_chain_id();
778        let base = self.networks;
779        let inferred = base.with_chain_id(chain_id);
780        if !base.has_network_selection() && inferred.has_network_selection() {
781            self.chain_id_network_base = Some(base);
782        }
783        self.networks = inferred;
784        self.update_wallet_chain_id(chain_id);
785    }
786
787    pub(crate) fn update_wallet_chain_id(&mut self, chain_id: u64) {
788        self.genesis_accounts.iter_mut().for_each(|wallet| {
789            *wallet = wallet.clone().with_chain_id(Some(chain_id));
790        });
791        self.signer_accounts.iter_mut().for_each(|wallet| {
792            *wallet = wallet.clone().with_chain_id(Some(chain_id));
793        })
794    }
795
796    /// Sets the gas limit
797    #[must_use]
798    pub const fn with_gas_limit(mut self, gas_limit: Option<u64>) -> Self {
799        self.gas_limit = gas_limit;
800        self
801    }
802
803    /// Disable block gas limit check
804    ///
805    /// If set to `true` block gas limit will not be enforced
806    #[must_use]
807    pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self {
808        self.disable_block_gas_limit = disable_block_gas_limit;
809        self
810    }
811
812    /// Enable tx gas limit check
813    ///
814    /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825)
815    #[must_use]
816    pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self {
817        self.enable_tx_gas_limit = enable_tx_gas_limit;
818        self
819    }
820
821    /// Sets the gas price
822    #[must_use]
823    pub const fn with_gas_price(mut self, gas_price: Option<u128>) -> Self {
824        self.gas_price = gas_price;
825        self
826    }
827
828    /// Sets prune history status.
829    #[must_use]
830    pub fn set_pruned_history(mut self, prune_history: Option<Option<usize>>) -> Self {
831        self.prune_history = PruneStateHistoryConfig::from_args(prune_history);
832        self
833    }
834
835    /// Sets max number of states to cache on disk.
836    #[must_use]
837    pub fn with_max_persisted_states<U: Into<usize>>(
838        mut self,
839        max_persisted_states: Option<U>,
840    ) -> Self {
841        self.max_persisted_states = max_persisted_states.map(Into::into);
842        self
843    }
844
845    /// Sets the max number of transactions in a block
846    #[must_use]
847    pub const fn with_max_transactions(mut self, max_transactions: Option<usize>) -> Self {
848        if let Some(max_transactions) = max_transactions {
849            self.max_transactions = max_transactions;
850        }
851        self
852    }
853
854    /// Sets max number of blocks with transactions to keep in memory
855    #[must_use]
856    pub fn with_transaction_block_keeper<U: Into<usize>>(
857        mut self,
858        transaction_block_keeper: Option<U>,
859    ) -> Self {
860        self.transaction_block_keeper = transaction_block_keeper.map(Into::into);
861        self
862    }
863
864    /// Sets the base fee
865    #[must_use]
866    pub const fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
867        self.base_fee = base_fee;
868        self
869    }
870
871    /// Disable the enforcement of a minimum suggested priority fee
872    #[must_use]
873    pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self {
874        self.disable_min_priority_fee = disable_min_priority_fee;
875        self
876    }
877
878    /// Sets the init genesis (genesis.json)
879    #[must_use]
880    pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
881        self.genesis = genesis;
882        self
883    }
884
885    /// Returns the genesis timestamp to use
886    pub fn get_genesis_timestamp(&self) -> u64 {
887        self.genesis_timestamp
888            .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
889            .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
890    }
891
892    /// Sets the genesis timestamp
893    #[must_use]
894    pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
895        if let Some(timestamp) = timestamp {
896            self.genesis_timestamp = Some(timestamp.into());
897        }
898        self
899    }
900
901    /// Sets the genesis number
902    #[must_use]
903    pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
904        if let Some(number) = number {
905            self.genesis_block_number = Some(number.into());
906        }
907        self
908    }
909
910    /// Returns the genesis number
911    pub fn get_genesis_number(&self) -> u64 {
912        self.genesis_block_number
913            .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
914            .unwrap_or(0)
915    }
916
917    /// Sets the hardfork
918    #[must_use]
919    pub const fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
920        self.hardfork = hardfork;
921        self
922    }
923
924    /// Sets the genesis accounts
925    #[must_use]
926    pub fn with_genesis_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
927        self.genesis_accounts = accounts;
928        self
929    }
930
931    /// Sets the signer accounts
932    #[must_use]
933    pub fn with_signer_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
934        self.signer_accounts = accounts;
935        self
936    }
937
938    /// Sets both the genesis accounts and the signer accounts
939    /// so that `genesis_accounts == accounts`
940    pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
941        let accounts = generator.generate()?;
942        self.account_generator = Some(generator);
943        Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
944    }
945
946    /// Sets the balance of the genesis accounts in the genesis block
947    #[must_use]
948    pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
949        self.genesis_balance = balance.into();
950        self
951    }
952
953    /// Sets the block time to automine blocks
954    #[must_use]
955    pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
956        self.block_time = block_time.map(Into::into);
957        self
958    }
959
960    #[must_use]
961    pub fn with_mixed_mining<D: Into<Duration>>(
962        mut self,
963        mixed_mining: bool,
964        block_time: Option<D>,
965    ) -> Self {
966        self.block_time = block_time.map(Into::into);
967        self.mixed_mining = mixed_mining;
968        self
969    }
970
971    /// If set to `true` auto mining will be disabled
972    #[must_use]
973    pub const fn with_no_mining(mut self, no_mining: bool) -> Self {
974        self.no_mining = no_mining;
975        self
976    }
977
978    /// Sets the slots in an epoch
979    #[must_use]
980    pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self {
981        self.slots_in_an_epoch = slots_in_an_epoch;
982        self
983    }
984
985    /// Sets the port to use
986    #[must_use]
987    pub const fn with_port(mut self, port: u16) -> Self {
988        self.port = port;
989        self
990    }
991
992    /// Sets the ipc path to use
993    ///
994    /// Note: this is a double Option for
995    ///     - `None` -> no ipc
996    ///     - `Some(None)` -> use default path
997    ///     - `Some(Some(path))` -> use custom path
998    #[must_use]
999    pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
1000        self.ipc_path = ipc_path;
1001        self
1002    }
1003
1004    /// Sets the file path to write the Anvil node's config info to.
1005    #[must_use]
1006    pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
1007        self.config_out = config_out;
1008        self
1009    }
1010
1011    #[must_use]
1012    pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self {
1013        self.no_storage_caching = no_storage_caching;
1014        self
1015    }
1016
1017    /// Sets the `eth_rpc_url` to use when forking (single endpoint convenience).
1018    #[must_use]
1019    pub fn with_eth_rpc_url<U: Into<String>>(mut self, eth_rpc_url: Option<U>) -> Self {
1020        if let Some(url) = eth_rpc_url {
1021            let fork_urls = vec![url.into()];
1022            if self.fork_urls != fork_urls {
1023                self.fork_endpoint_is_anvil = false;
1024            }
1025            self.fork_urls = fork_urls;
1026        }
1027        self
1028    }
1029
1030    /// Sets the fork URLs for load-balanced multi-endpoint forking.
1031    #[must_use]
1032    pub fn with_fork_urls(mut self, fork_urls: Vec<String>) -> Self {
1033        if self.fork_urls != fork_urls {
1034            self.fork_endpoint_is_anvil = false;
1035        }
1036        self.fork_urls = fork_urls;
1037        self
1038    }
1039
1040    /// Sets the `fork_choice` to use to fork off from based on a block number
1041    #[must_use]
1042    pub fn with_fork_block_number<U: Into<u64>>(self, fork_block_number: Option<U>) -> Self {
1043        self.with_fork_choice(fork_block_number.map(Into::into))
1044    }
1045
1046    /// Sets the `fork_choice` to use to fork off from based on a transaction hash
1047    #[must_use]
1048    pub fn with_fork_transaction_hash<U: Into<TxHash>>(
1049        self,
1050        fork_transaction_hash: Option<U>,
1051    ) -> Self {
1052        self.with_fork_choice(fork_transaction_hash.map(Into::into))
1053    }
1054
1055    /// Sets the `fork_choice` to use to fork off from
1056    #[must_use]
1057    pub fn with_fork_choice<U: Into<ForkChoice>>(mut self, fork_choice: Option<U>) -> Self {
1058        self.fork_choice = fork_choice.map(Into::into);
1059        self
1060    }
1061
1062    /// Sets the `fork_chain_id` to use to fork off local cache from
1063    #[must_use]
1064    pub const fn with_fork_chain_id(mut self, fork_chain_id: Option<U256>) -> Self {
1065        self.fork_chain_id = fork_chain_id;
1066        self
1067    }
1068
1069    /// Sets the `fork_headers` to use with fork RPC endpoints
1070    #[must_use]
1071    pub fn with_fork_headers(mut self, headers: Vec<String>) -> Self {
1072        self.fork_headers = headers;
1073        self
1074    }
1075
1076    /// Sets the `fork_request_timeout` to use for requests
1077    #[must_use]
1078    pub const fn fork_request_timeout(mut self, fork_request_timeout: Option<Duration>) -> Self {
1079        if let Some(fork_request_timeout) = fork_request_timeout {
1080            self.fork_request_timeout = fork_request_timeout;
1081        }
1082        self
1083    }
1084
1085    /// Sets the `fork_request_retries` to use for spurious networks
1086    #[must_use]
1087    pub const fn fork_request_retries(mut self, fork_request_retries: Option<u32>) -> Self {
1088        if let Some(fork_request_retries) = fork_request_retries {
1089            self.fork_request_retries = fork_request_retries;
1090        }
1091        self
1092    }
1093
1094    /// Sets the initial `fork_retry_backoff` for rate limits
1095    #[must_use]
1096    pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option<Duration>) -> Self {
1097        if let Some(fork_retry_backoff) = fork_retry_backoff {
1098            self.fork_retry_backoff = fork_retry_backoff;
1099        }
1100        self
1101    }
1102
1103    /// Sets the number of assumed available compute units per second
1104    ///
1105    /// See also, <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
1106    #[must_use]
1107    pub const fn fork_compute_units_per_second(
1108        mut self,
1109        compute_units_per_second: Option<u64>,
1110    ) -> Self {
1111        if let Some(compute_units_per_second) = compute_units_per_second {
1112            self.compute_units_per_second = compute_units_per_second;
1113        }
1114        self
1115    }
1116
1117    /// Sets whether to enable tracing
1118    #[must_use]
1119    pub const fn with_tracing(mut self, enable_tracing: bool) -> Self {
1120        self.enable_tracing = enable_tracing;
1121        self
1122    }
1123
1124    /// Sets whether to enable steps tracing
1125    #[must_use]
1126    pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self {
1127        self.enable_steps_tracing = enable_steps_tracing;
1128        self
1129    }
1130
1131    /// Sets whether to print `console.log` invocations to stdout.
1132    #[must_use]
1133    pub const fn with_print_logs(mut self, print_logs: bool) -> Self {
1134        self.print_logs = print_logs;
1135        self
1136    }
1137
1138    /// Sets whether to print traces to stdout.
1139    #[must_use]
1140    pub const fn with_print_traces(mut self, print_traces: bool) -> Self {
1141        self.print_traces = print_traces;
1142        self
1143    }
1144
1145    /// Sets whether to enable autoImpersonate
1146    #[must_use]
1147    pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
1148        self.enable_auto_impersonate = enable_auto_impersonate;
1149        self
1150    }
1151
1152    #[must_use]
1153    pub fn with_server_config(mut self, config: ServerConfig) -> Self {
1154        self.server_config = config;
1155        self
1156    }
1157
1158    /// Sets the host the server will listen on
1159    #[must_use]
1160    pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
1161        self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
1162        self
1163    }
1164
1165    #[must_use]
1166    pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self {
1167        self.transaction_order = transaction_order;
1168        self
1169    }
1170
1171    /// Returns the ipc path for the ipc endpoint if any
1172    pub fn get_ipc_path(&self) -> Option<String> {
1173        match &self.ipc_path {
1174            Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
1175            None => None,
1176        }
1177    }
1178
1179    /// Prints the config info
1180    pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
1181        if let Some(path) = &self.config_out {
1182            let value = self.as_json(fork);
1183            foundry_common::fs::write_json_file(path, &value).wrap_err("failed writing JSON")?;
1184        }
1185        if !self.silent {
1186            sh_println!("{}", self.as_string(fork))?;
1187        }
1188        Ok(())
1189    }
1190
1191    /// Returns the endpoint-specific path where the cache file should be stored.
1192    ///
1193    /// See also [`Config::foundry_block_cache_file`].
1194    pub fn block_cache_path(&self, block: u64) -> Option<PathBuf> {
1195        self.block_cache_path_for_rpc(self.protocol_chain_id(), block, self.fork_urls.first()?)
1196    }
1197
1198    fn block_cache_path_for_rpc(
1199        &self,
1200        source_chain_id: u64,
1201        block: u64,
1202        rpc_url: &str,
1203    ) -> Option<PathBuf> {
1204        if self.no_storage_caching || self.fork_urls.is_empty() {
1205            return None;
1206        }
1207
1208        let rpc_url_hash = hex::encode(keccak256(rpc_url));
1209        Some(
1210            Config::foundry_block_cache_file(source_chain_id, block)?
1211                .with_file_name(format!("storage-{rpc_url_hash}.json")),
1212        )
1213    }
1214
1215    /// Sets whether to disable the default create2 deployer
1216    #[must_use]
1217    pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self {
1218        self.disable_default_create2_deployer = yes;
1219        self
1220    }
1221
1222    /// Sets whether to disable pool balance checks
1223    #[must_use]
1224    pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self {
1225        self.disable_pool_balance_checks = yes;
1226        self
1227    }
1228
1229    /// Injects precompiles to `anvil`'s EVM.
1230    #[must_use]
1231    pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
1232        self.precompile_factory = Some(Arc::new(factory));
1233        self
1234    }
1235
1236    /// Enable features for provided networks.
1237    #[must_use]
1238    pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self {
1239        self.networks = networks;
1240        self.inferred_fork_network = None;
1241        self.chain_id_network_base = None;
1242        self
1243    }
1244
1245    /// Enable Tempo network features.
1246    #[must_use]
1247    pub fn with_tempo(mut self) -> Self {
1248        self.networks = NetworkConfigs::with_tempo();
1249        self.inferred_fork_network = None;
1250        self.chain_id_network_base = None;
1251        self
1252    }
1253
1254    /// Sets the account used to sponsor Tempo fee-payer requests.
1255    #[must_use]
1256    pub const fn with_tempo_fee_payer(mut self, fee_payer: Option<Address>) -> Self {
1257        self.tempo_fee_payer = fee_payer;
1258        self
1259    }
1260
1261    /// Returns the effective account used to sponsor Tempo fee-payer requests.
1262    ///
1263    /// Defaults to the last dev account so it rarely collides with the sender accounts commonly
1264    /// used in tests, mirroring the dedicated sponsor account of hosted fee payer services.
1265    /// Returns `None` on non-Tempo networks.
1266    pub fn tempo_fee_payer_address(&self) -> Option<Address> {
1267        if !self.networks.is_tempo() {
1268            return None;
1269        }
1270        self.tempo_fee_payer.or_else(|| self.genesis_accounts.last().map(|wallet| wallet.address()))
1271    }
1272
1273    /// Enable Monad network features.
1274    #[cfg(feature = "monad")]
1275    #[must_use]
1276    pub fn with_monad(mut self) -> Self {
1277        self.networks = NetworkConfigs::with_monad();
1278        self.inferred_fork_network = None;
1279        self.chain_id_network_base = None;
1280        self
1281    }
1282
1283    /// Enable Optimism network features.
1284    #[cfg(feature = "optimism")]
1285    #[must_use]
1286    pub fn with_optimism(mut self) -> Self {
1287        self.networks = NetworkConfigs::with_optimism();
1288        self.inferred_fork_network = None;
1289        self.chain_id_network_base = None;
1290        self
1291    }
1292
1293    /// Makes the node silent to not emit anything on stdout
1294    #[must_use]
1295    pub const fn silent(self) -> Self {
1296        self.set_silent(true)
1297    }
1298
1299    #[must_use]
1300    pub const fn set_silent(mut self, silent: bool) -> Self {
1301        self.silent = silent;
1302        self
1303    }
1304
1305    /// Sets the path where persisted states are cached (used with `max_persisted_states`).
1306    ///
1307    /// Note: This does not control the fork RPC cache location, which uses endpoint-specific files
1308    /// under `~/.foundry/cache/rpc/<chain>/<block>/`.
1309    #[must_use]
1310    pub fn with_cache_path(mut self, cache_path: Option<PathBuf>) -> Self {
1311        self.cache_path = cache_path;
1312        self
1313    }
1314
1315    /// Sets accounts to fund with custom balances on startup.
1316    #[must_use]
1317    pub fn with_funded_accounts(mut self, accounts: HashMap<Address, U256>) -> Self {
1318        self.funded_accounts = accounts;
1319        self
1320    }
1321
1322    /// Configures everything related to env, backend and database and returns the
1323    /// [Backend](mem::Backend)
1324    ///
1325    /// *Note*: only memory based backend for now
1326    pub(crate) async fn setup<N>(
1327        &mut self,
1328    ) -> Result<(mem::Backend<N>, Option<ForkTransactionReplay>)>
1329    where
1330        N: alloy_network::Network<
1331                TxEnvelope = foundry_primitives::FoundryTxEnvelope,
1332                ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope,
1333            >,
1334    {
1335        // configure the revm environment
1336
1337        let mut cfg = CfgEnv::default();
1338        cfg.spec = self.get_hardfork().into();
1339
1340        cfg.chain_id = self.get_chain_id();
1341        cfg.limit_contract_code_size = self.code_size_limit;
1342        // EIP-3607 rejects transactions from senders with deployed code.
1343        // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the
1344        // caller is a contract. So we disable the check by default.
1345        cfg.disable_eip3607 = true;
1346        cfg.disable_block_gas_limit = self.disable_block_gas_limit;
1347
1348        if !self.enable_tx_gas_limit {
1349            cfg.tx_gas_limit_cap = Some(u64::MAX);
1350        }
1351
1352        if let Some(value) = self.memory_limit {
1353            cfg.memory_limit = value;
1354        }
1355
1356        let spec_id = cfg.spec;
1357        let mut evm_env = EvmEnv::new(
1358            cfg,
1359            BlockEnv {
1360                gas_limit: self.gas_limit(),
1361                basefee: self.get_base_fee(),
1362                ..Default::default()
1363            },
1364        );
1365
1366        self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1367
1368        let genesis_timestamp = self.get_genesis_timestamp();
1369        let base_fee_params: BaseFeeParams = self.networks.base_fee_params(genesis_timestamp);
1370
1371        // On Tempo, the base fee follows the chain's hardfork rules instead of EIP-1559.
1372        let tempo_hardfork =
1373            self.networks.is_tempo().then(|| TempoHardfork::from(self.get_hardfork()));
1374
1375        let fees = FeeManager::new(
1376            spec_id,
1377            self.get_base_fee(),
1378            !self.disable_min_priority_fee,
1379            self.get_gas_price(),
1380            self.get_blob_excess_gas_and_price(),
1381            self.get_blob_params(),
1382            base_fee_params,
1383            tempo_hardfork,
1384        );
1385        #[cfg(feature = "optimism")]
1386        if self.networks.is_optimism() {
1387            fees.set_optimism_hardfork(self.get_hardfork());
1388        }
1389
1390        let (db, fork, fork_transaction_replay) =
1391            if let Some(eth_rpc_url) = self.fork_urls.first().cloned() {
1392                self.setup_fork_db_with_replay(eth_rpc_url, &mut evm_env, &fees).await?
1393            } else {
1394                let track_history = self.prune_history.is_state_history_supported();
1395                let db: Arc<TokioRwLock<Box<dyn Db>>> =
1396                    Arc::new(TokioRwLock::new(Box::new(StateRootDb::new(track_history))));
1397                (db, None, None)
1398            };
1399
1400        // if provided use all settings of `genesis.json`
1401        if let Some(ref genesis) = self.genesis {
1402            // --chain-id flag gets precedence over the genesis.json chain id
1403            // <https://github.com/foundry-rs/foundry/issues/10059>
1404            if self.chain_id.is_none() && fork.is_none() {
1405                evm_env.cfg_env.chain_id = genesis.config.chain_id;
1406            }
1407            evm_env.block_env.timestamp = U256::from(genesis.timestamp);
1408            if let Some(base_fee) = genesis.base_fee_per_gas {
1409                evm_env.block_env.basefee = base_fee.try_into()?;
1410            }
1411            if let Some(number) = genesis.number {
1412                evm_env.block_env.number = U256::from(number);
1413            }
1414            evm_env.block_env.beneficiary = genesis.coinbase;
1415        }
1416
1417        // Fork setup initializes its own timestamp. For a local BSC chain, keep the initial EVM
1418        // and genesis block on the same resolved timestamp so chain precompiles are available
1419        // immediately. Preserve the default timestamp behavior for all other local chains.
1420        let is_bsc = matches!(
1421            NamedChain::try_from(evm_env.cfg_env.chain_id),
1422            Ok(NamedChain::BinanceSmartChain | NamedChain::BinanceSmartChainTestnet)
1423        );
1424        if fork.is_none() && (self.genesis_timestamp.is_some() || is_bsc) {
1425            evm_env.block_env.timestamp = U256::from(genesis_timestamp);
1426        }
1427
1428        self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1429
1430        let genesis = GenesisConfig {
1431            number: self.get_genesis_number(),
1432            timestamp: genesis_timestamp,
1433            balance: self.genesis_balance,
1434            accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(),
1435            genesis_init: self.genesis.clone(),
1436        };
1437
1438        let active_hardfork = fork
1439            .as_ref()
1440            .and_then(|fork| fork.config.read().hardfork)
1441            .unwrap_or_else(|| self.get_hardfork());
1442        let mut decoder_builder = CallTraceDecoderBuilder::new()
1443            .with_networks(self.networks)
1444            .with_hardfork(Some(self.networks.executed_hardfork(active_hardfork)));
1445        if self.print_traces {
1446            // if traces should get printed we configure the decoder with the signatures cache
1447            if let Ok(identifier) = SignaturesIdentifier::new(false) {
1448                debug!(target: "node", "using signature identifier");
1449                decoder_builder = decoder_builder.with_signature_identifier(identifier);
1450            }
1451        }
1452
1453        // only memory based backend for now
1454        let backend = mem::Backend::with_genesis(
1455            db,
1456            Arc::new(RwLock::new(evm_env)),
1457            self.networks,
1458            genesis,
1459            fees,
1460            Arc::new(RwLock::new(fork)),
1461            self.enable_steps_tracing,
1462            self.print_logs,
1463            self.print_traces,
1464            Arc::new(decoder_builder.build()),
1465            self.prune_history,
1466            self.max_persisted_states,
1467            self.transaction_block_keeper,
1468            self.block_time,
1469            self.cache_path.clone(),
1470            Arc::new(TokioRwLock::new(self.clone())),
1471        )
1472        .await?;
1473
1474        // Writes the default create2 deployer to the backend,
1475        // if the option is not disabled and we are not forking.
1476        if !self.disable_default_create2_deployer && self.fork_urls.is_empty() {
1477            backend
1478                .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER)
1479                .await
1480                .wrap_err("failed to create default create2 deployer")?;
1481        }
1482
1483        if let Some(fork) = backend.get_fork() {
1484            let config = fork.config.read().clone();
1485            if !self
1486                .fork_urls_match_context(
1487                    &config.fork_urls,
1488                    config.endpoint_identity,
1489                    config.block_number,
1490                    config.block_hash,
1491                )
1492                .await?
1493            {
1494                eyre::bail!("fork endpoint changed while Anvil was being initialized");
1495            }
1496        }
1497        Ok((backend, fork_transaction_replay))
1498    }
1499
1500    /// Configures everything related to forking based on the passed `eth_rpc_url`:
1501    ///  - returning a tuple of a [ForkedDatabase] wrapped in an [Arc] [RwLock](TokioRwLock) and
1502    ///    [ClientFork] wrapped in an [Option] which can be used in a [Backend](mem::Backend) to
1503    ///    fork from.
1504    ///  - modifying some parameters of the passed `env`
1505    ///  - mutating some members of `self`
1506    pub async fn setup_fork_db(
1507        &mut self,
1508        eth_rpc_url: String,
1509        evm_env: &mut EvmEnv,
1510        fees: &FeeManager,
1511    ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>)> {
1512        let (db, fork, replay) = self.setup_fork_db_with_replay(eth_rpc_url, evm_env, fees).await?;
1513        eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1514        Ok((db, fork))
1515    }
1516
1517    async fn setup_fork_db_with_replay(
1518        &mut self,
1519        eth_rpc_url: String,
1520        evm_env: &mut EvmEnv,
1521        fees: &FeeManager,
1522    ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>, Option<ForkTransactionReplay>)>
1523    {
1524        let (db, config, replay) =
1525            self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees).await?;
1526        let db: Arc<TokioRwLock<Box<dyn Db>>> = Arc::new(TokioRwLock::new(Box::new(db)));
1527        let fork = ClientFork::new(config, Arc::clone(&db));
1528        Ok((db, Some(fork), replay))
1529    }
1530
1531    fn fork_provider(&self, eth_rpc_url: &str) -> Result<RetryProvider> {
1532        ProviderBuilder::new(eth_rpc_url)
1533            .timeout(self.fork_request_timeout)
1534            .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1535            .compute_units_per_second(self.compute_units_per_second)
1536            .max_retry(self.fork_request_retries)
1537            .headers(self.fork_headers.clone())
1538            .build()
1539            .wrap_err("failed to establish provider to fork url")
1540    }
1541
1542    async fn fork_endpoint_identity(
1543        &self,
1544        provider: &RetryProvider,
1545        fallback_execution_chain_id: u64,
1546        source_chain_id_override: Option<u64>,
1547        node_info_probe: &mut AnvilNodeInfoProbe,
1548    ) -> Result<ForkEndpointIdentity> {
1549        let Some(node_info) = node_info_probe.request(provider).await? else {
1550            let source_chain_id = source_chain_id_override.unwrap_or(fallback_execution_chain_id);
1551            let explicit_fallback = self.has_explicit_network_selection().then_some(self.networks);
1552            let network_profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
1553                source_chain_id,
1554                None,
1555                explicit_fallback,
1556            )
1557            .map_err(eyre::Report::msg)?;
1558            return Ok(ForkEndpointIdentity {
1559                execution_chain_id: fallback_execution_chain_id,
1560                source_chain_id,
1561                network: network_profile.map(|profile| profile.execution_network()),
1562                network_profile,
1563                hardfork: None,
1564                instance_id: None,
1565                source_fork_block_number: None,
1566                source_fork_block_hash: None,
1567            });
1568        };
1569
1570        let (
1571            execution_chain_id,
1572            source_chain_id,
1573            instance_id,
1574            source_fork_block_number,
1575            source_fork_block_hash,
1576        ) = match provider.raw_request::<_, Metadata>("anvil_metadata".into(), ()).await {
1577            Ok(metadata) => (
1578                metadata.chain_id,
1579                source_chain_id_override.unwrap_or_else(|| {
1580                    metadata.forked_network.map(|fork| fork.chain_id).unwrap_or(metadata.chain_id)
1581                }),
1582                Some(metadata.instance_id),
1583                metadata.forked_network.map(|fork| fork.fork_block_number),
1584                metadata.forked_network.map(|fork| fork.fork_block_hash),
1585            ),
1586            Err(error) if is_rpc_method_not_found(&error) => (
1587                fallback_execution_chain_id,
1588                source_chain_id_override.unwrap_or(fallback_execution_chain_id),
1589                None,
1590                None,
1591                None,
1592            ),
1593            Err(error) => {
1594                return Err(error).wrap_err("failed to retrieve Anvil fork source identity");
1595            }
1596        };
1597        let identity_chain_id =
1598            if node_info.network.is_some() { execution_chain_id } else { source_chain_id };
1599        let explicit_fallback = self.has_explicit_network_selection().then_some(self.networks);
1600        let network_profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
1601            identity_chain_id,
1602            Some(node_info.network.as_deref()),
1603            explicit_fallback,
1604        )
1605        .map_err(eyre::Report::msg)?
1606        .ok_or_else(|| eyre::eyre!("Anvil metadata did not identify an execution profile"))?;
1607        let network = network_profile.execution_network();
1608        let hardfork = network
1609            .parse_hardfork(&node_info.hard_fork)
1610            .map_err(eyre::Report::msg)
1611            .wrap_err_with(|| {
1612                format!("unsupported hardfork `{}` reported for `{network}`", node_info.hard_fork)
1613            })?;
1614
1615        Ok(ForkEndpointIdentity {
1616            execution_chain_id,
1617            source_chain_id,
1618            network: Some(network),
1619            network_profile: Some(network_profile),
1620            hardfork: Some(hardfork),
1621            instance_id,
1622            source_fork_block_number,
1623            source_fork_block_hash,
1624        })
1625    }
1626
1627    async fn resolved_fork_endpoint_identity(
1628        &self,
1629        provider: &RetryProvider,
1630        node_info_probe: &mut AnvilNodeInfoProbe,
1631    ) -> Result<ForkEndpointIdentity> {
1632        let identity = if let Some(chain_id) = self.fork_chain_id {
1633            let chain_id = chain_id.to();
1634            // `fork_chain_id` avoids depending on `eth_chainId`, but an online Anvil endpoint can
1635            // still expose authoritative family, hardfork, and instance metadata. Probe it so
1636            // mirror validation and reset staging cannot mistake this node for its own upstream.
1637            self.fork_endpoint_identity(provider, chain_id, Some(chain_id), node_info_probe).await
1638        } else {
1639            let execution_chain_id =
1640                provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?;
1641            self.fork_endpoint_identity(provider, execution_chain_id, None, node_info_probe).await
1642        }?;
1643        ensure_fork_network_supported(identity.source_chain_id)?;
1644        Ok(identity)
1645    }
1646
1647    pub(crate) async fn replacement_fork_provider(
1648        &self,
1649        eth_rpc_url: &str,
1650        expected: ForkEndpointIdentity,
1651        block_number: u64,
1652        block_hash: B256,
1653        serving_instance_id: B256,
1654    ) -> Result<(Arc<RetryProvider>, ForkEndpointIdentity)> {
1655        let provider = Arc::new(self.fork_provider(eth_rpc_url)?);
1656        let mut node_info_probe = AnvilNodeInfoProbe::default();
1657        for _ in 0..3 {
1658            let before =
1659                self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1660            eyre::ensure!(
1661                before.instance_id != Some(serving_instance_id),
1662                "cannot set Anvil's fork provider to its own RPC endpoint"
1663            );
1664            let block = provider
1665                .get_block(BlockNumberOrTag::Number(block_number).into())
1666                .await
1667                .wrap_err("failed to confirm active fork block on replacement endpoint")?;
1668            let after =
1669                self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1670            if before != after {
1671                continue;
1672            }
1673            if !before.context_eq(expected) {
1674                eyre::bail!("replacement fork endpoint has an incompatible execution context");
1675            }
1676            let actual_hash = block.map(|block| block.header.hash);
1677            if actual_hash != Some(block_hash) {
1678                eyre::bail!(
1679                    "replacement fork endpoint does not contain active fork block {block_number} with hash {block_hash}"
1680                );
1681            }
1682            return Ok((provider, before));
1683        }
1684        eyre::bail!(
1685            "fork endpoint changed while its identity and active fork block were being resolved"
1686        );
1687    }
1688
1689    async fn stable_fork_snapshot(
1690        &self,
1691        provider: &Arc<RetryProvider>,
1692        fork_overrides: ForkOverrides,
1693    ) -> Result<StableForkSnapshot> {
1694        let mut node_info_probe = AnvilNodeInfoProbe::new(self.fork_endpoint_is_anvil);
1695        for _ in 0..3 {
1696            let before =
1697                self.resolved_fork_endpoint_identity(provider, &mut node_info_probe).await?;
1698            let (block_number, transaction_replay) = if let Some(fork_choice) = &self.fork_choice {
1699                derive_block_and_replay(fork_choice, provider).await.wrap_err(
1700                    "failed to derive fork block and transaction replay from fork choice",
1701                )?
1702            } else {
1703                (
1704                    find_latest_fork_block(provider)
1705                        .await
1706                        .wrap_err("failed to get fork block number")?,
1707                    None,
1708                )
1709            };
1710            let block = provider
1711                .get_block(BlockNumberOrTag::Number(block_number).into())
1712                .await
1713                .wrap_err("failed to get fork block")?;
1714            let gas_price = if let Some(gas_price) = fork_overrides.gas_price {
1715                gas_price
1716            } else {
1717                provider.get_gas_price().await.unwrap_or(INITIAL_GAS_PRICE)
1718            };
1719            let after =
1720                self.resolved_fork_endpoint_identity(provider, &mut node_info_probe).await?;
1721            if before == after {
1722                return Ok(StableForkSnapshot {
1723                    endpoint_identity: before,
1724                    block_number,
1725                    transaction_replay,
1726                    block,
1727                    gas_price,
1728                });
1729            }
1730        }
1731        eyre::bail!(
1732            "fork endpoint changed while its identity and block context were being resolved"
1733        );
1734    }
1735
1736    pub(crate) async fn fork_context_matches(
1737        &self,
1738        eth_rpc_url: &str,
1739        expected: ForkEndpointIdentity,
1740        block_number: u64,
1741        block_hash: B256,
1742    ) -> Result<bool> {
1743        let provider = self.fork_provider(eth_rpc_url)?;
1744        let mut node_info_probe = AnvilNodeInfoProbe::new(expected.is_authoritative());
1745        for _ in 0..3 {
1746            let before =
1747                self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1748            let block = provider
1749                .get_block(BlockNumberOrTag::Number(block_number).into())
1750                .await
1751                .wrap_err("failed to confirm fork block context")?;
1752            let after =
1753                self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1754            if before != after {
1755                continue;
1756            }
1757            if before.source_chain_id != expected.source_chain_id {
1758                eyre::bail!(
1759                    "fork endpoints must use the same chain ID: expected {}, got {} from {}",
1760                    expected.source_chain_id,
1761                    before.source_chain_id,
1762                    redact_url(eth_rpc_url)
1763                );
1764            }
1765            return Ok(
1766                before == expected && block.is_some_and(|block| block.header.hash == block_hash)
1767            );
1768        }
1769        Ok(false)
1770    }
1771
1772    pub(crate) async fn fork_urls_match_context(
1773        &self,
1774        fork_urls: &[String],
1775        expected: ForkEndpointIdentity,
1776        block_number: u64,
1777        block_hash: B256,
1778    ) -> Result<bool> {
1779        for eth_rpc_url in Self::fork_urls_requiring_revalidation(fork_urls, expected) {
1780            if !self.fork_context_matches(eth_rpc_url, expected, block_number, block_hash).await? {
1781                return Ok(false);
1782            }
1783        }
1784        Ok(true)
1785    }
1786
1787    const fn fork_urls_requiring_revalidation(
1788        fork_urls: &[String],
1789        endpoint_identity: ForkEndpointIdentity,
1790    ) -> &[String] {
1791        if endpoint_identity.is_authoritative() || fork_urls.len() > 1 { fork_urls } else { &[] }
1792    }
1793
1794    pub(crate) fn has_explicit_network_selection(&self) -> bool {
1795        let effective_network =
1796            self.networks.resolved_network().unwrap_or(NetworkVariant::Ethereum);
1797        self.networks.has_network_selection()
1798            && self.chain_id_network_base.is_none()
1799            && self.inferred_fork_network != Some(effective_network)
1800    }
1801
1802    const fn requires_primary_fork_revalidation(
1803        &self,
1804        endpoint_identity: ForkEndpointIdentity,
1805    ) -> bool {
1806        endpoint_identity.is_authoritative() || self.fork_urls.len() > 1
1807    }
1808
1809    /// Configures everything related to forking based on the passed `eth_rpc_url`:
1810    ///  - returning a tuple of a [ForkedDatabase] and [ClientForkConfig] which can be used to build
1811    ///    a [ClientFork] to fork from.
1812    ///  - modifying some parameters of the passed `env`
1813    ///  - mutating some members of `self`
1814    pub async fn setup_fork_db_config(
1815        &mut self,
1816        eth_rpc_url: String,
1817        evm_env: &mut EvmEnv,
1818        fees: &FeeManager,
1819    ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig)> {
1820        let (db, config, replay) =
1821            self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees).await?;
1822        eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1823        Ok((db, config))
1824    }
1825
1826    pub(crate) async fn setup_fork_db_config_with_replay(
1827        &mut self,
1828        eth_rpc_url: String,
1829        evm_env: &mut EvmEnv,
1830        fees: &FeeManager,
1831    ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig, Option<ForkTransactionReplay>)> {
1832        debug!(target: "node", eth_rpc_url=%redact_url(&eth_rpc_url), "setting up fork db");
1833        if self.fork_chain_id.is_some() {
1834            eyre::ensure!(
1835                self.fork_urls.len() == 1,
1836                "multiple fork URLs cannot be validated with --fork-chain-id; remove \
1837                 --fork-chain-id to validate every endpoint"
1838            );
1839        }
1840        let fork_overrides = *self.fork_overrides.get_or_insert(ForkOverrides {
1841            gas_limit: self.gas_limit,
1842            gas_price: self.gas_price,
1843            base_fee: self.base_fee,
1844        });
1845
1846        // Always bootstrap with the primary URL only to avoid race conditions
1847        // where discovery calls (get_chain_id, find_latest_fork_block, get_block)
1848        // hit different endpoints that may be at different chain tips.
1849        let provider = Arc::new(self.fork_provider(&eth_rpc_url)?);
1850
1851        // Resolve identity, block, and fee data as one stable snapshot. An upstream Anvil can
1852        // reset between any two RPC calls, so verify the endpoint identity on both sides.
1853        let StableForkSnapshot {
1854            endpoint_identity: fork_identity,
1855            block_number: fork_block_number,
1856            transaction_replay: fork_transaction_replay,
1857            block,
1858            gas_price,
1859        } = self.stable_fork_snapshot(&provider, fork_overrides).await?;
1860        self.fork_endpoint_is_anvil = fork_identity.is_authoritative();
1861
1862        let target_network = fork_identity.network.unwrap_or(NetworkVariant::Ethereum);
1863        let target_profile = fork_identity.network_profile.unwrap_or_default();
1864        if self.inferred_fork_network.is_some()
1865            && !self.networks.supports_fork_source(&target_profile)
1866        {
1867            eyre::bail!(
1868                "cannot reset Anvil across network families ({} -> {}); start a new instance \
1869                 with matching network configuration",
1870                self.networks.execution_profile_name(),
1871                target_profile.execution_profile_name()
1872            );
1873        }
1874        let source_chain_id = fork_identity.source_chain_id;
1875        self.fork_source_chain_id = Some(source_chain_id);
1876        self.fork_execution_chain_id = Some(fork_identity.execution_chain_id);
1877        if !self.has_explicit_network_selection() {
1878            self.networks = self.networks.with_rpc_profile(target_profile);
1879            self.inferred_fork_network = Some(target_network);
1880            self.chain_id_network_base = None;
1881        }
1882
1883        let block = if let Some(block) = block {
1884            block
1885        } else {
1886            if let Ok(latest_block) = provider.get_block_number().await {
1887                let mut message = format!(
1888                    "Failed to get block for block number: {fork_block_number}\n\
1889latest block number: {latest_block}"
1890                );
1891                // If the `eth_getBlockByNumber` call succeeds, but returns null instead of
1892                // the block, and the block number is less than equal the latest block, then
1893                // the user is forking from a non-archive node with an older block number.
1894                if fork_block_number <= latest_block {
1895                    message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}"));
1896                }
1897                eyre::bail!("{message}");
1898            }
1899            eyre::bail!("failed to get block for block number: {fork_block_number}");
1900        };
1901
1902        if let Some(replay) = &fork_transaction_replay {
1903            let source_header = replay.source_block.header();
1904            eyre::ensure!(
1905                block.header.hash == source_header.parent_hash,
1906                "fork transaction block {} at {} has parent {}, but fetched fork block at {} has \
1907                 hash {}",
1908                source_header.hash,
1909                source_header.number,
1910                source_header.parent_hash,
1911                block.header.number,
1912                block.header.hash
1913            );
1914            eyre::ensure!(
1915                block.header.number.checked_add(1) == Some(source_header.number),
1916                "fork transaction block {} has number {}, but fetched parent {} has number {}",
1917                source_header.hash,
1918                source_header.number,
1919                block.header.hash,
1920                block.header.number
1921            );
1922        }
1923
1924        let gas_limit = self.fork_gas_limit_with_override(&block, fork_overrides.gas_limit);
1925        self.gas_limit = Some(gas_limit);
1926
1927        // Cache identity must describe the remote fork block, not local execution overrides that
1928        // can change after mining (for example, the locally advanced base fee).
1929        let cache_block_env: BlockEnv = block_env_from_header(&block.header);
1930
1931        evm_env.block_env = BlockEnv {
1932            gas_limit,
1933            // Preserve configured local overrides while replacing fork-derived block values.
1934            beneficiary: evm_env.block_env.beneficiary,
1935            basefee: fork_overrides
1936                .base_fee
1937                .or_else(|| block.header.base_fee_per_gas())
1938                .unwrap_or_default(),
1939            ..block_env_from_header(&block.header)
1940        };
1941
1942        let override_chain_id = self.chain_id;
1943        let execution_chain_id = override_chain_id.unwrap_or(fork_identity.execution_chain_id);
1944        if override_chain_id.is_none() {
1945            // Sign locally produced transactions for the chain ID exposed by the endpoint
1946            // without turning the inferred value into an explicit execution override.
1947            self.update_wallet_chain_id(fork_identity.execution_chain_id);
1948        }
1949        evm_env.cfg_env.chain_id = execution_chain_id;
1950
1951        // Resolve the fork block's hardfork without materializing it into `self.hardfork`.
1952        // That field represents the user's explicit override; keeping inference on the fork
1953        // config lets a later reset re-resolve timestamp-based activations.
1954        let effective_network =
1955            self.networks.resolved_network().unwrap_or(NetworkVariant::Ethereum);
1956        let endpoint_matches_execution = fork_identity.network == Some(effective_network);
1957        let source_hardfork = fork_identity.hardfork.or_else(|| {
1958            FoundryHardfork::from_chain_and_timestamp(source_chain_id, block.header.timestamp())
1959        });
1960        let inferred_hardfork = source_hardfork.filter(|hardfork| {
1961            endpoint_matches_execution
1962                && hardfork.namespace() == effective_network.hardfork_namespace()
1963        });
1964        let source_may_omit_blob_fields = source_hardfork
1965            .map_or(self.hardfork.is_some(), |hardfork| SpecId::from(hardfork) < SpecId::CANCUN);
1966        let fork_hardfork = self.hardfork.or(inferred_hardfork);
1967        let effective_hardfork = fork_hardfork.unwrap_or_else(|| self.get_hardfork());
1968        let effective_spec = SpecId::from(effective_hardfork);
1969        evm_env.cfg_env.set_spec_and_mainnet_gas_params(effective_spec);
1970        fees.set_execution_rules(
1971            effective_spec,
1972            self.networks.base_fee_params(block.header.timestamp()),
1973            self.networks.is_tempo().then(|| TempoHardfork::from(effective_hardfork)),
1974        );
1975        #[cfg(feature = "optimism")]
1976        if self.networks.is_optimism() {
1977            fees.set_optimism_base_fee_rules(block.header.extra_data());
1978        }
1979
1980        // if not set explicitly we use the base fee of the latest block
1981        self.base_fee = fork_overrides.base_fee.or_else(|| block.header.base_fee_per_gas());
1982        if let Some(base_fee) = fork_overrides.base_fee {
1983            fees.set_base_fee(base_fee);
1984        } else if let Some(base_fee) = block.header.base_fee_per_gas() {
1985            // This is the base fee of the current block, but we need the base fee of the next
1986            // block.
1987            fees.set_base_fee(base_fee);
1988            let next_block_base_fee = fees.get_next_block_base_fee_from_header(&block.header);
1989            fees.set_base_fee(next_block_base_fee);
1990        } else {
1991            fees.set_base_fee(self.get_base_fee());
1992        }
1993
1994        // Blob rules and fee state belong to the selected fork context even when the target block
1995        // predates Cancun. Always replace both so a reset cannot retain the previous fork's
1996        // schedule or excess gas.
1997        let blob_params = get_blob_params(source_chain_id, block.header.timestamp());
1998        fees.set_blob_params(blob_params);
1999        let blob_update_fraction = blob_params.update_fraction as u64;
2000        let blob_excess_gas = block.header.excess_blob_gas().or_else(|| {
2001            // Pre-Cancun headers, Polygon Bor headers, and Arbitrum Nitro headers omit the blob
2002            // fields. REVM still requires a valid blob environment when executing with the Cancun
2003            // spec; zero is the neutral excess-gas value. On Nitro this makes `BLOBBASEFEE` return
2004            // `1`, although Nitro rejects the opcode; matching that requires Arbitrum-specific EVM
2005            // handling.
2006            (effective_spec >= SpecId::CANCUN
2007                && ((source_may_omit_blob_fields && block.header.blob_gas_used().is_none())
2008                    || Chain::from_id(source_chain_id).is_polygon()
2009                    || Chain::from_id(source_chain_id).is_arbitrum()))
2010            .then_some(0)
2011        });
2012        evm_env.block_env.blob_excess_gas_and_price =
2013            blob_excess_gas.map(|excess| BlobExcessGasAndPrice::new(excess, blob_update_fraction));
2014        let next_block_blob_excess_gas = blob_excess_gas.map_or(0, |excess| {
2015            self.networks.next_block_blob_excess_gas(
2016                blob_params,
2017                excess,
2018                block.header.blob_gas_used().unwrap_or_default(),
2019                block.header.base_fee_per_gas().unwrap_or_default(),
2020            )
2021        });
2022        fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
2023            next_block_blob_excess_gas,
2024            blob_update_fraction,
2025        ));
2026
2027        // Use the gas price captured in the stable endpoint snapshot.
2028        self.gas_price = Some(gas_price);
2029        fees.set_gas_price(gas_price);
2030
2031        let block_hash = block.header.hash;
2032
2033        // Apply changes such as difficulty -> prevrandao for the remote source chain.
2034        apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
2035            evm_env,
2036            &block,
2037            source_chain_id,
2038            self.networks,
2039        );
2040
2041        for mirror_url in self.fork_urls.iter().skip(1) {
2042            if !self
2043                .fork_context_matches(mirror_url, fork_identity, fork_block_number, block_hash)
2044                .await?
2045            {
2046                eyre::bail!(
2047                    "fork fallback endpoint `{}` does not expose the primary endpoint's execution \
2048                     and block context",
2049                    redact_url(mirror_url)
2050                );
2051            }
2052        }
2053        if self.requires_primary_fork_revalidation(fork_identity)
2054            && !self
2055                .fork_context_matches(&eth_rpc_url, fork_identity, fork_block_number, block_hash)
2056                .await?
2057        {
2058            eyre::bail!("primary fork endpoint changed while its context was being validated");
2059        }
2060
2061        let source_id = fork_source_id(&self.fork_urls, &self.fork_headers);
2062        let meta = BlockchainDbMeta::new(cache_block_env, eth_rpc_url.clone())
2063            .with_fork_identity(block_hash, source_id);
2064        let cache_path =
2065            self.block_cache_path_for_rpc(source_chain_id, fork_block_number, &eth_rpc_url);
2066        let block_chain_db = BlockchainDb::new(meta, cache_path);
2067
2068        // After bootstrap, rebuild the provider with round-robin if multiple URLs are
2069        // configured. This ensures bootstrap used only the primary endpoint for consistency,
2070        // while ongoing requests are distributed across all endpoints.
2071        let provider = if self.fork_urls.len() > 1 {
2072            let urls = self.fork_urls.iter().map(|url| redact_url(url)).collect::<Vec<_>>();
2073            debug!(target: "node", ?urls, "using multi-endpoint round-robin provider");
2074            Arc::new(
2075                ProviderBuilder::new(&eth_rpc_url)
2076                    .timeout(self.fork_request_timeout)
2077                    .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
2078                    .compute_units_per_second(self.compute_units_per_second)
2079                    .max_retry(self.fork_request_retries)
2080                    .headers(self.fork_headers.clone())
2081                    .build_fallback(self.fork_urls.clone())
2082                    .wrap_err("failed to establish round-robin provider to fork urls")?,
2083            )
2084        } else {
2085            provider
2086        };
2087
2088        // This will spawn the background thread that will use the provider to fetch
2089        // blockchain data from the other client
2090        let anchor = ForkBlock::with_rpc_number(
2091            evm_env.block_env.number.saturating_to(),
2092            fork_block_number,
2093            block_hash,
2094        );
2095        let (backend, handler) =
2096            SharedBackend::new_with_anchor(Arc::clone(&provider), block_chain_db.clone(), anchor)?;
2097        tokio::spawn(handler);
2098
2099        let config = ClientForkConfig {
2100            fork_urls: self.fork_urls.clone(),
2101            block_number: fork_block_number,
2102            block_hash,
2103            transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()),
2104            provider,
2105            chain_id: source_chain_id,
2106            execution_chain_id,
2107            override_chain_id,
2108            fork_chain_id: self.fork_chain_id.map(|chain_id| chain_id.to()),
2109            hardfork: Some(effective_hardfork),
2110            endpoint_identity: fork_identity,
2111            timestamp: block.header.timestamp(),
2112            base_fee: block.header.base_fee_per_gas().map(|g| g as u128),
2113            timeout: self.fork_request_timeout,
2114            retries: self.fork_request_retries,
2115            backoff: self.fork_retry_backoff,
2116            compute_units_per_second: self.compute_units_per_second,
2117            headers: self.fork_headers.clone(),
2118            total_difficulty: block.header.total_difficulty.unwrap_or_default(),
2119            blob_gas_used: block.header.blob_gas_used().map(|g| g as u128),
2120            blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price,
2121        };
2122
2123        debug!(target: "node", fork_number=config.block_number, fork_hash=%config.block_hash, "set up fork db");
2124
2125        let mut db = ForkedDatabase::new(backend, block_chain_db);
2126
2127        // need to insert the forked block's hash
2128        db.insert_block_hash(U256::from(config.block_number), config.block_hash);
2129
2130        Ok((db, config, fork_transaction_replay))
2131    }
2132
2133    /// we only use the gas limit value of the block if it is non-zero and the block gas
2134    /// limit is enabled, since there are networks where this is not used and is always
2135    /// `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>
2136    fn fork_gas_limit_with_override<B: BlockResponse<Header: BlockHeader>>(
2137        &self,
2138        block: &B,
2139        gas_limit: Option<u64>,
2140    ) -> u64 {
2141        if !self.disable_block_gas_limit {
2142            if let Some(gas_limit) = gas_limit {
2143                return gas_limit;
2144            } else if block.header().gas_limit() > 0 {
2145                return block.header().gas_limit();
2146            }
2147        }
2148
2149        u64::MAX
2150    }
2151
2152    /// Restores user-provided gas settings after leaving fork mode.
2153    pub(crate) const fn restore_fork_overrides(&mut self) {
2154        if let Some(overrides) = self.fork_overrides {
2155            self.gas_limit = overrides.gas_limit;
2156            self.gas_price = overrides.gas_price;
2157            self.base_fee = overrides.base_fee;
2158        }
2159    }
2160
2161    /// Returns the gas limit for a non forked anvil instance
2162    ///
2163    /// Checks the config for the `disable_block_gas_limit` flag
2164    pub(crate) fn gas_limit(&self) -> u64 {
2165        if self.disable_block_gas_limit {
2166            return u64::MAX;
2167        }
2168
2169        self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)
2170    }
2171}
2172
2173pub(crate) const fn tempo_default_base_fee(hardfork: TempoHardfork) -> u64 {
2174    if hardfork.is_t1() { TEMPO_T1_BASE_FEE } else { TEMPO_T0_BASE_FEE }
2175}
2176
2177/// If the fork choice is a block number, simply return it with an empty list of transactions.
2178/// If the fork choice is a transaction hash, determine the block that the transaction was mined in,
2179/// and return the block number before the fork block along with all transactions in the fork block
2180/// that are before (and including) the fork transaction.
2181async fn derive_block_and_replay(
2182    fork_choice: &ForkChoice,
2183    provider: &Arc<RetryProvider>,
2184) -> eyre::Result<(BlockNumber, Option<ForkTransactionReplay>)> {
2185    match fork_choice {
2186        ForkChoice::Block(block_number) => {
2187            let block_number = *block_number;
2188            if block_number >= 0 {
2189                return Ok((block_number as u64, None));
2190            }
2191            // subtract from latest block number
2192            let latest = provider.get_block_number().await?;
2193
2194            Ok((block_number.saturating_add(latest as i128) as u64, None))
2195        }
2196        ForkChoice::Transaction(transaction_hash) => {
2197            // Determine the block that this transaction was mined in
2198            let transaction = provider
2199                .get_transaction_by_hash(transaction_hash.0.into())
2200                .await?
2201                .ok_or_else(|| eyre::eyre!("fork transaction {transaction_hash} was not found"))?;
2202            let transaction_block_number = transaction.block_number().ok_or_else(|| {
2203                eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
2204            })?;
2205            let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
2206                eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
2207            })?;
2208
2209            // Get the block pertaining to the fork transaction.
2210            let transaction_block =
2211                provider.get_block_by_hash(transaction_block_hash).full().await?.ok_or_else(
2212                    || {
2213                        eyre::eyre!(
2214                            "failed to get fork block {transaction_block_hash} for transaction \
2215                         {transaction_hash}"
2216                        )
2217                    },
2218                )?;
2219            let replay = validate_fork_transaction_replay(
2220                *transaction_hash,
2221                &transaction,
2222                transaction_block,
2223            )?;
2224            Ok((transaction_block_number.saturating_sub(1), Some(replay)))
2225        }
2226    }
2227}
2228
2229fn validate_fork_transaction_replay(
2230    transaction_hash: TxHash,
2231    transaction: &alloy_network::AnyRpcTransaction,
2232    source_block: AnyRpcBlock,
2233) -> eyre::Result<ForkTransactionReplay> {
2234    let source_hash = source_block.header.hash;
2235    let source_number = source_block.header.number;
2236    let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
2237        eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
2238    })?;
2239    let transaction_block_number = transaction.block_number().ok_or_else(|| {
2240        eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
2241    })?;
2242
2243    eyre::ensure!(
2244        source_hash == transaction_block_hash,
2245        "fork transaction {transaction_hash} reports block {transaction_block_hash}, but fetched \
2246         block hash is {source_hash}"
2247    );
2248    eyre::ensure!(
2249        source_number == transaction_block_number,
2250        "fork transaction {transaction_hash} reports block number {transaction_block_number}, but \
2251         fetched block {source_hash} has number {source_number}"
2252    );
2253    eyre::ensure!(
2254        source_number > 0,
2255        "fork transaction {transaction_hash} is in genesis block {source_hash}, which has no parent"
2256    );
2257
2258    let transactions = source_block.transactions.as_transactions().ok_or_else(|| {
2259        eyre::eyre!("fork block {source_hash} at {source_number} did not include full transactions")
2260    })?;
2261    let mut matches =
2262        transactions.iter().enumerate().filter(|(_, tx)| tx.tx_hash() == transaction_hash);
2263    let target_index = matches.next().map(|(index, _)| index).ok_or_else(|| {
2264        eyre::eyre!(
2265            "fork transaction {transaction_hash} is absent from block {source_hash} at \
2266             {source_number}"
2267        )
2268    })?;
2269    eyre::ensure!(
2270        matches.next().is_none(),
2271        "fork transaction {transaction_hash} occurs more than once in block {source_hash} at \
2272         {source_number}"
2273    );
2274    if let Some(reported_index) = transaction.transaction_index() {
2275        eyre::ensure!(
2276            reported_index == target_index as u64,
2277            "fork transaction {transaction_hash} reports index {reported_index}, but occurs at \
2278             index {target_index} in block {source_hash}"
2279        );
2280    }
2281
2282    Ok(ForkTransactionReplay { source_block, target_index })
2283}
2284
2285/// Fork delimiter used to specify which block or transaction to fork from.
2286#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2287pub enum ForkChoice {
2288    /// Block number to fork from.
2289    ///
2290    /// If negative, the given value is subtracted from the `latest` block number.
2291    Block(i128),
2292    /// Transaction hash to fork from.
2293    Transaction(TxHash),
2294}
2295
2296impl ForkChoice {
2297    /// Returns the block number to fork from
2298    pub const fn block_number(&self) -> Option<i128> {
2299        match self {
2300            Self::Block(block_number) => Some(*block_number),
2301            Self::Transaction(_) => None,
2302        }
2303    }
2304
2305    /// Returns the transaction hash to fork from
2306    pub const fn transaction_hash(&self) -> Option<TxHash> {
2307        match self {
2308            Self::Block(_) => None,
2309            Self::Transaction(transaction_hash) => Some(*transaction_hash),
2310        }
2311    }
2312}
2313
2314/// Convert a transaction hash into a ForkChoice
2315impl From<TxHash> for ForkChoice {
2316    fn from(tx_hash: TxHash) -> Self {
2317        Self::Transaction(tx_hash)
2318    }
2319}
2320
2321/// Convert a decimal block number into a ForkChoice
2322impl From<u64> for ForkChoice {
2323    fn from(block: u64) -> Self {
2324        Self::Block(block as i128)
2325    }
2326}
2327
2328#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2329pub struct PruneStateHistoryConfig {
2330    pub enabled: bool,
2331    pub max_memory_history: Option<usize>,
2332}
2333
2334impl PruneStateHistoryConfig {
2335    /// Returns `true` if writing state history is supported
2336    pub const fn is_state_history_supported(&self) -> bool {
2337        if !self.enabled {
2338            return true;
2339        }
2340
2341        match self.max_memory_history {
2342            Some(limit) => limit > 0,
2343            None => false,
2344        }
2345    }
2346
2347    /// Returns true if this setting was enabled.
2348    pub const fn is_config_enabled(&self) -> bool {
2349        self.enabled
2350    }
2351
2352    pub fn from_args(val: Option<Option<usize>>) -> Self {
2353        val.map(|max_memory_history| Self {
2354            enabled: true,
2355            max_memory_history: max_memory_history.filter(|limit| *limit > 0),
2356        })
2357        .unwrap_or_default()
2358    }
2359}
2360
2361/// Can create dev accounts
2362#[derive(Clone, Debug)]
2363pub struct AccountGenerator {
2364    chain_id: u64,
2365    amount: usize,
2366    phrase: String,
2367    derivation_path: Option<String>,
2368}
2369
2370impl AccountGenerator {
2371    pub fn new(amount: usize) -> Self {
2372        Self {
2373            chain_id: CHAIN_ID,
2374            amount,
2375            phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
2376            derivation_path: None,
2377        }
2378    }
2379
2380    #[must_use]
2381    pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
2382        self.phrase = phrase.into();
2383        self
2384    }
2385
2386    fn get_phrase(&self) -> &str {
2387        &self.phrase
2388    }
2389
2390    #[must_use]
2391    pub fn chain_id(mut self, chain_id: impl Into<u64>) -> Self {
2392        self.chain_id = chain_id.into();
2393        self
2394    }
2395
2396    #[must_use]
2397    pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
2398        let mut derivation_path = derivation_path.into();
2399        if !derivation_path.ends_with('/') {
2400            derivation_path.push('/');
2401        }
2402        self.derivation_path = Some(derivation_path);
2403        self
2404    }
2405
2406    fn get_derivation_path(&self) -> &str {
2407        self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
2408    }
2409}
2410
2411impl AccountGenerator {
2412    pub fn generate(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
2413        let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
2414
2415        // use the derivation path
2416        let derivation_path = self.get_derivation_path();
2417        foundry_common::wallet::validate_bip32_path(derivation_path).map_err(|e| eyre::eyre!(e))?;
2418
2419        let mut wallets = Vec::with_capacity(self.amount);
2420        for idx in 0..self.amount {
2421            let idx = u32::try_from(idx).map_err(|_| eyre::eyre!("account index overflows u32"))?;
2422            let full_path = foundry_common::wallet::derive_key_path_checked(derivation_path, idx)
2423                .map_err(|e| eyre::eyre!(e))?;
2424            let builder = builder.clone().derivation_path(full_path)?;
2425            let wallet = builder.build()?.with_chain_id(Some(self.chain_id));
2426            wallets.push(wallet)
2427        }
2428        Ok(wallets)
2429    }
2430}
2431
2432/// Returns the path to anvil dir `~/.foundry/anvil`
2433pub fn anvil_dir() -> Option<PathBuf> {
2434    Config::foundry_dir().map(|p| p.join("anvil"))
2435}
2436
2437/// Returns the root path to anvil's temporary storage `~/.foundry/anvil/`
2438pub fn anvil_tmp_dir() -> Option<PathBuf> {
2439    anvil_dir().map(|p| p.join("tmp"))
2440}
2441
2442/// Finds the latest appropriate block to fork
2443///
2444/// This fetches the "latest" block and checks whether the `Block` is fully populated (`hash` field
2445/// 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>
2446async fn find_latest_fork_block<P: Provider<AnyNetwork>>(
2447    provider: P,
2448) -> Result<u64, TransportError> {
2449    let mut num = provider.get_block_number().await?;
2450
2451    // walk back from the head of the chain, but at most 2 blocks, which should be more than enough
2452    // leeway
2453    for _ in 0..2 {
2454        if let Some(block) = provider.get_block(num.into()).await?
2455            && !block.header.hash.is_zero()
2456        {
2457            break;
2458        }
2459        // block not actually finalized, so we try the block before
2460        num = num.saturating_sub(1)
2461    }
2462
2463    Ok(num)
2464}
2465
2466#[cfg(test)]
2467mod tests {
2468    #[cfg(feature = "optimism")]
2469    use foundry_evm::hardfork::OpHardfork;
2470    use foundry_evm::{hardfork::EthereumHardfork, hardforks::latest_active_tempo_hardfork};
2471
2472    use super::*;
2473
2474    #[tokio::test(flavor = "multi_thread")]
2475    async fn fork_output_redacts_endpoint_credentials() {
2476        let (_api, source) = crate::spawn(NodeConfig::test()).await;
2477        let fork_url = source.http_endpoint().replacen("http://", "http://user:password@", 1)
2478            + "/?api_key=secret";
2479        let mut config = NodeConfig::test().with_eth_rpc_url(Some(fork_url.clone()));
2480        let (api, _handle) = crate::spawn(config.clone()).await;
2481        config.fork_urls.push("https://mirror.example/private-api-key?token=secret".to_string());
2482
2483        let fork = api.backend.get_fork().unwrap();
2484        let output = config.as_string(Some(&fork));
2485        let temp = tempfile::tempdir().unwrap();
2486        let config_out = temp.path().join("config.json");
2487        config.config_out = Some(config_out.clone());
2488        config.print(Some(&fork)).unwrap();
2489        let json = serde_json::from_slice::<Value>(&std::fs::read(config_out).unwrap()).unwrap();
2490
2491        assert!(output.contains(&redact_url(&fork_url)));
2492        assert!(output.contains("https://mirror.example/"));
2493        assert!(!output.contains("user"));
2494        assert!(!output.contains("password"));
2495        assert!(!output.contains("private-api-key"));
2496        assert!(!output.contains("secret"));
2497        assert_eq!(json["endpoint"], redact_url(&fork_url));
2498        assert!(!json.to_string().contains("password"));
2499        assert!(!json.to_string().contains("secret"));
2500    }
2501
2502    #[test]
2503    fn fork_source_identity_includes_all_urls_and_headers() {
2504        let urls = ["http://primary".to_string(), "http://fallback".to_string()];
2505        let headers = ["Authorization: secret".to_string()];
2506        let identity = fork_source_id(&urls, &headers);
2507
2508        assert_ne!(identity, fork_source_id(&urls[..1], &headers));
2509        assert_ne!(identity, fork_source_id(&urls, &[]));
2510        assert_ne!(identity, fork_source_id(&[urls[1].clone(), urls[0].clone()], &headers));
2511    }
2512
2513    #[test]
2514    fn test_prune_history() {
2515        let config = PruneStateHistoryConfig::default();
2516        assert!(config.is_state_history_supported());
2517        let config = PruneStateHistoryConfig::from_args(Some(None));
2518        assert!(!config.is_state_history_supported());
2519        let config = PruneStateHistoryConfig::from_args(Some(Some(0)));
2520        assert!(config.is_config_enabled());
2521        assert!(!config.is_state_history_supported());
2522        let config = PruneStateHistoryConfig::from_args(Some(Some(10)));
2523        assert!(config.is_state_history_supported());
2524    }
2525
2526    #[test]
2527    fn fork_cache_path_can_use_source_chain() {
2528        let rpc_url = "http://localhost:8545";
2529        let mut config = NodeConfig::test()
2530            .with_eth_rpc_url(Some(rpc_url.to_string()))
2531            .with_chain_id(Some(1u64));
2532        let block = 42;
2533        config.fork_source_chain_id = Some(143);
2534        let expected = Config::foundry_block_cache_file(143, block).map(|path| {
2535            path.with_file_name(format!("storage-{}.json", hex::encode(keccak256(rpc_url))))
2536        });
2537
2538        assert_eq!(config.block_cache_path(block), expected);
2539        assert_ne!(
2540            config.block_cache_path_for_rpc(143, block, rpc_url),
2541            config.block_cache_path_for_rpc(143, block, "http://localhost:8546")
2542        );
2543    }
2544
2545    #[test]
2546    fn fork_execution_and_source_chain_ids_remain_distinct() {
2547        let mut config = NodeConfig::test();
2548        config.fork_execution_chain_id = Some(1);
2549        config.fork_source_chain_id = Some(143);
2550
2551        assert_eq!(config.get_chain_id(), 1);
2552        assert_eq!(config.protocol_chain_id(), 143);
2553    }
2554
2555    #[test]
2556    fn fork_chain_id_is_only_an_offline_discovery_hint() {
2557        let mut config = NodeConfig::test()
2558            .with_chain_id(Some(31_337u64))
2559            .with_fork_chain_id(Some(U256::from(143)));
2560
2561        assert_eq!(config.protocol_chain_id(), 31_337);
2562
2563        config.fork_source_chain_id = Some(143);
2564        assert_eq!(config.protocol_chain_id(), 143);
2565
2566        config.fork_source_chain_id = None;
2567        assert_eq!(config.protocol_chain_id(), 31_337);
2568    }
2569
2570    #[test]
2571    fn fork_endpoint_revalidation_requires_authority_or_fallbacks() {
2572        let anonymous = ForkEndpointIdentity {
2573            execution_chain_id: 1,
2574            source_chain_id: 1,
2575            network: Some(NetworkVariant::Ethereum),
2576            network_profile: Some(NetworkConfigs::default()),
2577            hardfork: None,
2578            instance_id: None,
2579            source_fork_block_number: None,
2580            source_fork_block_hash: None,
2581        };
2582        let mut config =
2583            NodeConfig::test().with_eth_rpc_url(Some("http://localhost:8545".to_string()));
2584
2585        assert!(!anonymous.is_authoritative());
2586        assert!(!config.requires_primary_fork_revalidation(anonymous));
2587
2588        config.fork_urls.push("http://localhost:8546".to_string());
2589        assert!(config.requires_primary_fork_revalidation(anonymous));
2590        assert_eq!(
2591            NodeConfig::fork_urls_requiring_revalidation(&config.fork_urls, anonymous),
2592            config.fork_urls
2593        );
2594
2595        config.fork_urls.pop();
2596        let authoritative =
2597            ForkEndpointIdentity { hardfork: Some(EthereumHardfork::Prague.into()), ..anonymous };
2598        assert!(authoritative.is_authoritative());
2599        assert!(config.requires_primary_fork_revalidation(authoritative));
2600        assert_eq!(
2601            NodeConfig::fork_urls_requiring_revalidation(&config.fork_urls, authoritative),
2602            config.fork_urls
2603        );
2604    }
2605
2606    #[tokio::test]
2607    async fn fork_authoritative_identity_keeps_node_info_probe_strict() {
2608        let (_api, origin) =
2609            crate::spawn(NodeConfig::test().with_chain_id(Some(NamedChain::Mainnet as u64))).await;
2610        let fork_url = foundry_test_utils::rpc::spawn_rpc_proxy_rejecting_method_after(
2611            origin.http_endpoint(),
2612            "anvil_nodeInfo",
2613            0,
2614        )
2615        .await;
2616        let expected = ForkEndpointIdentity {
2617            execution_chain_id: NamedChain::Mainnet as u64,
2618            source_chain_id: NamedChain::Mainnet as u64,
2619            network: Some(NetworkVariant::Ethereum),
2620            network_profile: Some(NetworkConfigs::default()),
2621            hardfork: Some(EthereumHardfork::Prague.into()),
2622            instance_id: Some(B256::with_last_byte(1)),
2623            source_fork_block_number: None,
2624            source_fork_block_hash: None,
2625        };
2626
2627        let error = NodeConfig::test()
2628            .fork_context_matches(&fork_url, expected, 0, B256::ZERO)
2629            .await
2630            .unwrap_err();
2631
2632        assert!(
2633            error.to_string().contains("failed to determine network family from fork endpoint"),
2634            "{error}"
2635        );
2636    }
2637
2638    #[cfg(feature = "optimism")]
2639    #[test]
2640    fn set_chain_id_updates_network_config() {
2641        let mut config = NodeConfig::test();
2642        config.set_chain_id(Some(10u64));
2643
2644        assert!(config.networks.is_optimism());
2645    }
2646
2647    #[test]
2648    fn chain_id_network_inference_is_replaceable_and_clearable() {
2649        let mut config = NodeConfig::test();
2650        config.set_chain_id(Some(4217u64));
2651        assert!(config.networks.is_tempo());
2652
2653        config.set_chain_id(Some(NamedChain::Celo as u64));
2654        assert!(config.networks.is_celo());
2655        assert!(!config.networks.is_tempo());
2656
2657        config.set_chain_id(Some(1u64));
2658        assert!(!config.networks.has_network_selection());
2659
2660        config.set_chain_id(Some(4217u64));
2661        config.set_chain_id(None::<u64>);
2662        assert!(!config.networks.has_network_selection());
2663    }
2664
2665    #[test]
2666    fn chain_id_preserves_explicit_network_selection() {
2667        let mut config = NodeConfig::test_tempo();
2668        config.set_chain_id(Some(NamedChain::Celo as u64));
2669
2670        assert!(config.networks.is_tempo());
2671        assert!(!config.networks.is_celo());
2672    }
2673
2674    #[test]
2675    fn get_hardfork_on_tempo_never_returns_non_tempo_variant() {
2676        // Post-Shanghai timestamp on Ethereum mainnet.
2677        let shanghai_ts = 1_681_338_455u64;
2678
2679        let config = NodeConfig::test_tempo()
2680            .with_chain_id(Some(1u64))
2681            .with_genesis_timestamp(Some(shanghai_ts));
2682
2683        assert!(config.networks.is_tempo());
2684        assert!(matches!(config.get_hardfork(), FoundryHardfork::Tempo(_)));
2685    }
2686
2687    #[test]
2688    fn get_hardfork_on_ethereum_uses_genesis_timestamp() {
2689        let timestamp = EthereumHardfork::Shanghai.mainnet_activation_timestamp().unwrap();
2690        let config =
2691            NodeConfig::test().with_chain_id(Some(1u64)).with_genesis_timestamp(Some(timestamp));
2692
2693        assert_eq!(config.get_hardfork(), FoundryHardfork::Ethereum(EthereumHardfork::Shanghai));
2694    }
2695
2696    #[test]
2697    #[cfg(feature = "optimism")]
2698    fn get_hardfork_on_optimism_uses_genesis_timestamp() {
2699        // OP Mainnet Canyon activation timestamp.
2700        let timestamp = 1_704_992_401u64;
2701        let config = NodeConfig::test()
2702            .with_optimism()
2703            .with_chain_id(Some(10u64))
2704            .with_genesis_timestamp(Some(timestamp));
2705
2706        assert_eq!(config.get_hardfork(), FoundryHardfork::Optimism(OpHardfork::Canyon));
2707    }
2708
2709    #[test]
2710    fn get_hardfork_on_local_tempo_defaults_to_latest_active() {
2711        let config = NodeConfig::test_tempo();
2712
2713        assert_eq!(config.get_hardfork(), FoundryHardfork::Tempo(latest_active_tempo_hardfork()));
2714    }
2715
2716    #[test]
2717    #[cfg(feature = "monad")]
2718    fn get_hardfork_on_monad_fork_uses_source_chain_timestamp_mapping() {
2719        let mut config = NodeConfig::test_monad()
2720            .with_chain_id(Some(1u64))
2721            .with_genesis_timestamp(Some(1_763_648_999u64));
2722        config.fork_source_chain_id = Some(143);
2723
2724        assert_eq!(config.get_chain_id(), 1);
2725        assert_eq!(
2726            config.get_hardfork(),
2727            FoundryHardfork::Monad(foundry_evm::hardfork::MonadHardfork::MonadEight)
2728        );
2729    }
2730
2731    #[test]
2732    fn account_generator_rejects_harden_bit_overflow_path() {
2733        let err = AccountGenerator::new(1)
2734            .phrase("test test test test test test test test test test test junk")
2735            .derivation_path("m/44'/60'/0'/0/2147483648'")
2736            .generate()
2737            .unwrap_err()
2738            .to_string();
2739        assert!(err.contains("harden bit"), "{err}");
2740
2741        assert!(
2742            AccountGenerator::new(1)
2743                .phrase("test test test test test test test test test test test junk")
2744                .derivation_path("m/44'/60'/0'/0")
2745                .generate()
2746                .is_ok()
2747        );
2748    }
2749}