Skip to main content

anvil/
config.rs

1use crate::{
2    EthereumHardfork, FeeManager, PrecompileFactory,
3    eth::{
4        backend::{
5            db::{Db, SerializableState},
6            fork::{ClientFork, ClientForkConfig, ensure_fork_network_supported},
7            genesis::GenesisConfig,
8            mem::fork_db::ForkedDatabase,
9            time::duration_since_unix_epoch,
10        },
11        fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE},
12        pool::transactions::TransactionOrder,
13    },
14    mem::{self, in_memory_db::StateRootDb},
15};
16use alloy_chains::NamedChain;
17use alloy_consensus::BlockHeader;
18use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams};
19use alloy_evm::EvmEnv;
20use alloy_genesis::Genesis;
21use alloy_network::{AnyNetwork, AnyRpcBlock, BlockResponse, TransactionResponse};
22use alloy_primitives::{
23    Address, BlockNumber, TxHash, U256, hex, keccak256, map::HashMap, utils::Unit,
24};
25use alloy_provider::Provider;
26use alloy_rpc_types::BlockNumberOrTag;
27use alloy_signer::Signer;
28use alloy_signer_local::{
29    MnemonicBuilder, PrivateKeySigner,
30    coins_bip39::{English, Mnemonic},
31};
32use alloy_transport::TransportError;
33use anvil_server::ServerConfig;
34use eyre::{Context, Result};
35use foundry_common::{
36    ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT,
37    provider::{ProviderBuilder, RetryProvider},
38};
39use foundry_config::Config;
40use foundry_evm::{
41    backend::{BlockchainDb, BlockchainDbMeta, SharedBackend},
42    constants::DEFAULT_CREATE2_DEPLOYER,
43    hardfork::FoundryHardfork,
44    hardforks::latest_active_tempo_hardfork,
45    utils::{
46        apply_chain_and_block_specific_env_changes, block_env_from_header,
47        get_blob_base_fee_update_fraction,
48    },
49};
50use parking_lot::RwLock;
51use rand_08::thread_rng;
52use revm::{
53    context::{BlockEnv, CfgEnv},
54    context_interface::block::BlobExcessGasAndPrice,
55    primitives::hardfork::SpecId,
56};
57use serde_json::{Value, json};
58use std::{
59    fmt::Write as FmtWrite,
60    net::{IpAddr, Ipv4Addr},
61    path::PathBuf,
62    sync::Arc,
63    time::Duration,
64};
65use tempo_hardfork::{
66    TempoHardfork,
67    constants::gas::{TEMPO_T0_BASE_FEE, TEMPO_T1_BASE_FEE},
68};
69use tokio::sync::RwLock as TokioRwLock;
70use yansi::Paint;
71
72pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
73use foundry_evm::{
74    traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier},
75    utils::get_blob_params,
76};
77use foundry_evm_networks::NetworkConfigs;
78use tempo_precompiles::TIP_FEE_MANAGER_ADDRESS;
79
80/// Default port the rpc will open
81pub const NODE_PORT: u16 = 8545;
82/// Default chain id of the node
83pub const CHAIN_ID: u64 = 31337;
84/// The default gas limit for all transactions
85pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
86/// The default number of slots in an epoch used for safe/finalized block tags.
87pub const DEFAULT_SLOTS_IN_AN_EPOCH: u64 = 32;
88/// Default mnemonic for dev accounts
89pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
90
91/// One-shot source data for a transaction-hash fork replay.
92#[derive(Clone, Debug)]
93pub(crate) struct ForkTransactionReplay {
94    pub(crate) source_block: AnyRpcBlock,
95    pub(crate) target_index: usize,
96}
97
98/// The default IPC endpoint
99pub const DEFAULT_IPC_ENDPOINT: &str =
100    if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
101
102const BANNER: &str = r"
103                             _   _
104                            (_) | |
105      __ _   _ __   __   __  _  | |
106     / _` | | '_ \  \ \ / / | | | |
107    | (_| | | | | |  \ V /  | | | |
108     \__,_| |_| |_|   \_/   |_| |_|
109";
110
111/// Configurations of the EVM node
112#[derive(Clone, Debug)]
113pub struct NodeConfig {
114    /// Chain ID of the EVM chain
115    pub chain_id: Option<u64>,
116    /// Default gas limit for all txs
117    pub gas_limit: Option<u64>,
118    /// If set to `true`, disables the block gas limit
119    pub disable_block_gas_limit: bool,
120    /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825)
121    pub enable_tx_gas_limit: bool,
122    /// Default gas price for all txs
123    pub gas_price: Option<u128>,
124    /// Default base fee
125    pub base_fee: Option<u64>,
126    /// If set to `true`, disables the enforcement of a minimum suggested priority fee
127    pub disable_min_priority_fee: bool,
128    /// Default blob excess gas and price
129    pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
130    /// The hardfork to use
131    pub hardfork: Option<FoundryHardfork>,
132    /// Signer accounts that will be initialised with `genesis_balance` in the genesis block
133    pub genesis_accounts: Vec<PrivateKeySigner>,
134    /// Native token balance of every genesis account in the genesis block
135    pub genesis_balance: U256,
136    /// Genesis block timestamp
137    pub genesis_timestamp: Option<u64>,
138    /// Genesis block number
139    pub genesis_block_number: Option<u64>,
140    /// Signer accounts that can sign messages/transactions from the EVM node
141    pub signer_accounts: Vec<PrivateKeySigner>,
142    /// Configured block time for the EVM chain. Use `None` for instant/auto mining.
143    pub block_time: Option<Duration>,
144    /// Disable auto and interval mining mode and use `MiningMode::None` instead.
145    pub no_mining: bool,
146    /// Enables auto and interval mining mode
147    pub mixed_mining: bool,
148    /// port to use for the server
149    pub port: u16,
150    /// maximum number of transactions in a block
151    pub max_transactions: usize,
152    /// Fork URLs for RPC calls. The first entry is the primary endpoint.
153    /// When multiple URLs are provided, requests are distributed using
154    /// round-robin load balancing with retry-based failover.
155    pub fork_urls: Vec<String>,
156    /// pins the block number or transaction hash for the state fork
157    pub fork_choice: Option<ForkChoice>,
158    /// headers to use with fork RPC endpoints
159    pub fork_headers: Vec<String>,
160    /// specifies chain id for cache to skip fetching from remote in offline-start mode
161    pub fork_chain_id: Option<U256>,
162    /// The generator used to generate the dev accounts
163    pub account_generator: Option<AccountGenerator>,
164    /// whether to enable tracing
165    pub enable_tracing: bool,
166    /// Explicitly disables the use of RPC caching.
167    pub no_storage_caching: bool,
168    /// How to configure the server
169    pub server_config: ServerConfig,
170    /// The host the server will listen on
171    pub host: Vec<IpAddr>,
172    /// How transactions are sorted in the mempool
173    pub transaction_order: TransactionOrder,
174    /// Filename to write anvil output as json
175    pub config_out: Option<PathBuf>,
176    /// The genesis to use to initialize the node
177    pub genesis: Option<Genesis>,
178    /// Timeout in for requests sent to remote JSON-RPC server in forking mode
179    pub fork_request_timeout: Duration,
180    /// Number of request retries for spurious networks
181    pub fork_request_retries: u32,
182    /// The initial retry backoff
183    pub fork_retry_backoff: Duration,
184    /// available CUPS
185    pub compute_units_per_second: u64,
186    /// The ipc path
187    pub ipc_path: Option<Option<String>>,
188    /// Enable transaction/call steps tracing for debug calls returning geth-style traces
189    pub enable_steps_tracing: bool,
190    /// Enable printing of `console.log` invocations.
191    pub print_logs: bool,
192    /// Enable printing of traces.
193    pub print_traces: bool,
194    /// Enable auto impersonation of accounts on startup
195    pub enable_auto_impersonate: bool,
196    /// Configure the code size limit
197    pub code_size_limit: Option<usize>,
198    /// Configures how to remove historic state.
199    ///
200    /// If set to `Some(num)` keep latest num state in memory only.
201    pub prune_history: PruneStateHistoryConfig,
202    /// Max number of states cached on disk.
203    pub max_persisted_states: Option<usize>,
204    /// The initial state to apply and consume during startup.
205    pub init_state: Option<SerializableState>,
206    /// max number of blocks with transactions in memory
207    pub transaction_block_keeper: Option<usize>,
208    /// Disable the default CREATE2 deployer
209    pub disable_default_create2_deployer: bool,
210    /// Disable pool balance checks
211    pub disable_pool_balance_checks: bool,
212    /// Slots in an epoch
213    pub slots_in_an_epoch: u64,
214    /// The memory limit per EVM execution in bytes.
215    pub memory_limit: Option<u64>,
216    /// Factory used by `anvil` to extend the EVM's precompiles.
217    pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
218    /// Networks to enable features for.
219    pub networks: NetworkConfigs,
220    /// Do not print log messages.
221    pub silent: bool,
222    /// The path where persisted states are cached (used with `max_persisted_states`).
223    /// This does not affect the fork RPC cache location.
224    pub cache_path: Option<PathBuf>,
225    /// Accounts to fund with specific balances on startup (address -> balance in wei).
226    pub funded_accounts: HashMap<Address, U256>,
227}
228
229impl NodeConfig {
230    fn as_string(&self, fork: Option<&ClientFork>) -> String {
231        let mut s: String = String::new();
232        let _ = write!(s, "\n{}", BANNER.green());
233        let _ = write!(s, "\n    {VERSION_MESSAGE}");
234        let _ = write!(s, "\n    {}", "https://github.com/foundry-rs/foundry".green());
235
236        let _ = write!(
237            s,
238            r#"
239
240Available Accounts
241==================
242"#
243        );
244        let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
245        for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
246            write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap();
247        }
248
249        let _ = write!(
250            s,
251            r#"
252
253Private Keys
254==================
255"#
256        );
257
258        for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
259            let hex = hex::encode(wallet.credential().to_bytes());
260            let _ = write!(s, "\n({idx}) 0x{hex}");
261        }
262
263        if let Some(generator) = &self.account_generator {
264            let _ = write!(
265                s,
266                r#"
267
268Wallet
269==================
270Mnemonic:          {}
271Derivation path:   {}
272"#,
273                generator.phrase,
274                generator.get_derivation_path()
275            );
276        }
277
278        if let Some(fork) = fork {
279            let _ = write!(
280                s,
281                r#"
282
283Fork
284==================
285Endpoint:       {}
286Block number:   {}
287Block hash:     {:?}
288Chain ID:       {}
289"#,
290                fork.eth_rpc_url().as_deref().unwrap_or("none"),
291                fork.block_number(),
292                fork.block_hash(),
293                fork.chain_id()
294            );
295
296            if self.fork_urls.len() > 1 {
297                let _ = writeln!(s, "Endpoints:      {}", self.fork_urls.len());
298                for (i, url) in self.fork_urls.iter().enumerate() {
299                    let _ = writeln!(s, "  ({i}) {url}");
300                }
301            }
302
303            if let Some(tx_hash) = fork.transaction_hash() {
304                let _ = writeln!(s, "Transaction hash: {tx_hash}");
305            }
306        } else {
307            let _ = write!(
308                s,
309                r#"
310
311Chain ID
312==================
313
314{}
315"#,
316                self.get_chain_id().green()
317            );
318        }
319
320        if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) {
321            let _ = write!(
322                s,
323                r#"
324Gas Price
325==================
326
327{}
328"#,
329                self.get_gas_price().green()
330            );
331        } else {
332            let _ = write!(
333                s,
334                r#"
335Base Fee
336==================
337
338{}
339"#,
340                self.get_base_fee().green()
341            );
342        }
343
344        let _ = write!(
345            s,
346            r#"
347Gas Limit
348==================
349
350{}
351"#,
352            {
353                if self.disable_block_gas_limit {
354                    "Disabled".to_string()
355                } else {
356                    self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| {
357                        if self.fork_choice.is_some() {
358                            "Forked".to_string()
359                        } else {
360                            DEFAULT_GAS_LIMIT.to_string()
361                        }
362                    })
363                }
364            }
365            .green()
366        );
367
368        let _ = write!(
369            s,
370            r#"
371Genesis Timestamp
372==================
373
374{}
375"#,
376            self.get_genesis_timestamp().green()
377        );
378
379        let _ = write!(
380            s,
381            r#"
382Genesis Number
383==================
384
385{}
386"#,
387            self.get_genesis_number().green()
388        );
389
390        s
391    }
392
393    fn as_json(&self, fork: Option<&ClientFork>) -> Value {
394        let mut wallet_description = HashMap::new();
395        let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
396        let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
397
398        for wallet in &self.genesis_accounts {
399            available_accounts.push(format!("{:?}", wallet.address()));
400            private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes())));
401        }
402
403        if let Some(generator) = &self.account_generator {
404            let phrase = generator.get_phrase().to_string();
405            let derivation_path = generator.get_derivation_path().to_string();
406
407            wallet_description.insert("derivation_path".to_string(), derivation_path);
408            wallet_description.insert("mnemonic".to_string(), phrase);
409        };
410
411        let gas_limit = match self.gas_limit {
412            // if we have a disabled flag we should max out the limit
413            Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()),
414            Some(limit) => Some(limit.to_string()),
415            _ => None,
416        };
417
418        if let Some(fork) = fork {
419            json!({
420              "available_accounts": available_accounts,
421              "private_keys": private_keys,
422              "endpoint": fork.eth_rpc_url().unwrap_or_default(),
423              "block_number": fork.block_number(),
424              "block_hash": fork.block_hash(),
425              "chain_id": fork.chain_id(),
426              "wallet": wallet_description,
427              "base_fee": format!("{}", self.get_base_fee()),
428              "gas_price": format!("{}", self.get_gas_price()),
429              "gas_limit": gas_limit,
430            })
431        } else {
432            json!({
433              "available_accounts": available_accounts,
434              "private_keys": private_keys,
435              "wallet": wallet_description,
436              "base_fee": format!("{}", self.get_base_fee()),
437              "gas_price": format!("{}", self.get_gas_price()),
438              "gas_limit": gas_limit,
439              "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
440            })
441        }
442    }
443}
444
445impl NodeConfig {
446    /// Returns a new config intended to be used in tests, which does not print and binds to a
447    /// random, free port by setting it to `0`
448    #[doc(hidden)]
449    pub fn test() -> Self {
450        Self { enable_tracing: true, port: 0, silent: true, ..Default::default() }
451    }
452
453    /// Returns a test config with Tempo network enabled.
454    #[doc(hidden)]
455    pub fn test_tempo() -> Self {
456        Self { networks: NetworkConfigs::with_tempo(), ..Self::test() }
457    }
458
459    /// Returns a new config which does not initialize any accounts on node startup.
460    pub fn empty_state() -> Self {
461        Self {
462            genesis_accounts: vec![],
463            signer_accounts: vec![],
464            disable_default_create2_deployer: true,
465            ..Default::default()
466        }
467    }
468}
469
470impl Default for NodeConfig {
471    fn default() -> Self {
472        // generate some random wallets
473        let genesis_accounts = AccountGenerator::new(10)
474            .phrase(DEFAULT_MNEMONIC)
475            .generate()
476            .expect("Invalid mnemonic.");
477        Self {
478            chain_id: None,
479            gas_limit: None,
480            disable_block_gas_limit: false,
481            enable_tx_gas_limit: false,
482            gas_price: None,
483            hardfork: None,
484            signer_accounts: genesis_accounts.clone(),
485            genesis_timestamp: None,
486            genesis_block_number: None,
487            genesis_accounts,
488            // 100ETH default balance
489            genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
490            block_time: None,
491            no_mining: false,
492            mixed_mining: false,
493            port: NODE_PORT,
494            max_transactions: 1_000,
495            fork_urls: vec![],
496            fork_choice: None,
497            account_generator: None,
498            base_fee: None,
499            disable_min_priority_fee: false,
500            blob_excess_gas_and_price: None,
501            enable_tracing: true,
502            enable_steps_tracing: false,
503            print_logs: true,
504            print_traces: false,
505            enable_auto_impersonate: false,
506            no_storage_caching: false,
507            server_config: Default::default(),
508            host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
509            transaction_order: Default::default(),
510            config_out: None,
511            genesis: None,
512            fork_request_timeout: REQUEST_TIMEOUT,
513            fork_headers: vec![],
514            fork_request_retries: 5,
515            fork_retry_backoff: Duration::from_millis(1_000),
516            fork_chain_id: None,
517            // alchemy max cpus <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
518            compute_units_per_second: ALCHEMY_FREE_TIER_CUPS,
519            ipc_path: None,
520            code_size_limit: None,
521            prune_history: Default::default(),
522            max_persisted_states: None,
523            init_state: None,
524            transaction_block_keeper: None,
525            disable_default_create2_deployer: false,
526            disable_pool_balance_checks: false,
527            slots_in_an_epoch: DEFAULT_SLOTS_IN_AN_EPOCH,
528            memory_limit: None,
529            precompile_factory: None,
530            networks: Default::default(),
531            silent: false,
532            cache_path: None,
533            funded_accounts: HashMap::default(),
534        }
535    }
536}
537
538impl NodeConfig {
539    /// Applies Tempo's safe default beneficiary for forked nodes while preserving
540    /// explicit coinbase selections.
541    pub(crate) fn apply_tempo_fork_beneficiary_default<N>(&self, evm_env: &mut EvmEnv<N>) {
542        if self.networks.is_tempo()
543            && !self.fork_urls.is_empty()
544            && evm_env.block_env.beneficiary.is_zero()
545        {
546            // Tempo mainnet maps the zero validator token to a DONOTUSE sentinel.
547            // Forked transactions with the default zero beneficiary can therefore
548            // fail fee collection before producing a receipt. Use the same neutral
549            // fee-recipient sentinel as Tempo's simulation path so validator token
550            // lookup falls back to the default PathUSD token unless the user has
551            // explicitly supplied a non-zero coinbase.
552            evm_env.block_env.beneficiary = TIP_FEE_MANAGER_ADDRESS;
553        }
554    }
555
556    /// Returns the memory limit of the node
557    #[must_use]
558    pub const fn with_memory_limit(mut self, mems_value: Option<u64>) -> Self {
559        self.memory_limit = mems_value;
560        self
561    }
562
563    /// Returns the base fee to use.
564    ///
565    /// In Tempo mode, uses the hardfork-specific base fee (10 gwei pre-T1, 20 gwei T1+).
566    pub fn get_base_fee(&self) -> u64 {
567        let default = if self.networks.is_tempo() {
568            tempo_default_base_fee(TempoHardfork::from(self.get_hardfork()))
569        } else {
570            INITIAL_BASE_FEE
571        };
572        self.base_fee
573            .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64)))
574            .unwrap_or(default)
575    }
576
577    /// Returns the gas price to use.
578    ///
579    /// In Tempo mode, defaults to the hardfork-specific base fee.
580    pub fn get_gas_price(&self) -> u128 {
581        let default = if self.networks.is_tempo() {
582            tempo_default_base_fee(TempoHardfork::from(self.get_hardfork())) as u128
583        } else {
584            INITIAL_GAS_PRICE
585        };
586        self.gas_price.unwrap_or(default)
587    }
588
589    pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice {
590        if let Some(value) = self.blob_excess_gas_and_price {
591            value
592        } else {
593            let excess_blob_gas =
594                self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0);
595            BlobExcessGasAndPrice::new(
596                excess_blob_gas,
597                get_blob_base_fee_update_fraction(
598                    self.get_chain_id(),
599                    self.get_genesis_timestamp(),
600                ),
601            )
602        }
603    }
604
605    /// Returns the [`BlobParams`] that should be used.
606    pub fn get_blob_params(&self) -> BlobParams {
607        get_blob_params(self.get_chain_id(), self.get_genesis_timestamp())
608    }
609
610    /// Returns the hardfork to use
611    pub fn get_hardfork(&self) -> FoundryHardfork {
612        if let Some(hardfork) = self.hardfork {
613            return hardfork;
614        }
615        if self.networks.is_tempo()
616            && let Some(hardfork) = TempoHardfork::from_chain_and_timestamp(
617                self.get_chain_id(),
618                self.get_genesis_timestamp(),
619            )
620        {
621            return hardfork.into();
622        }
623        #[cfg(feature = "optimism")]
624        if self.networks.is_optimism() {
625            return foundry_evm::hardforks::OpHardfork::default().into();
626        }
627        if self.networks.is_tempo() {
628            return latest_active_tempo_hardfork().into();
629        }
630        EthereumHardfork::default().into()
631    }
632
633    /// Sets a custom code size limit
634    #[must_use]
635    pub const fn with_code_size_limit(mut self, code_size_limit: Option<usize>) -> Self {
636        self.code_size_limit = code_size_limit;
637        self
638    }
639    /// Disables  code size limit
640    #[must_use]
641    pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self {
642        if disable_code_size_limit {
643            self.code_size_limit = Some(usize::MAX);
644        }
645        self
646    }
647
648    /// Sets the init state if any
649    #[must_use]
650    pub fn with_init_state(mut self, init_state: Option<SerializableState>) -> Self {
651        self.init_state = init_state;
652        self
653    }
654
655    /// Loads the init state from a file if it exists
656    #[must_use]
657    #[cfg(feature = "cmd")]
658    pub fn with_init_state_path(mut self, path: impl AsRef<std::path::Path>) -> Self {
659        self.init_state = crate::cmd::StateFile::parse_path(path).ok().and_then(|file| file.state);
660        self
661    }
662
663    /// Sets the chain ID
664    #[must_use]
665    pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
666        self.set_chain_id(chain_id);
667        self
668    }
669
670    /// Returns the chain ID to use
671    pub fn get_chain_id(&self) -> u64 {
672        self.chain_id
673            .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
674            .unwrap_or(CHAIN_ID)
675    }
676
677    /// Sets the chain id and updates all wallets
678    pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
679        self.chain_id = chain_id.map(Into::into);
680        let chain_id = self.get_chain_id();
681        self.networks = self.networks.with_chain_id(chain_id);
682        self.genesis_accounts.iter_mut().for_each(|wallet| {
683            *wallet = wallet.clone().with_chain_id(Some(chain_id));
684        });
685        self.signer_accounts.iter_mut().for_each(|wallet| {
686            *wallet = wallet.clone().with_chain_id(Some(chain_id));
687        })
688    }
689
690    /// Sets the gas limit
691    #[must_use]
692    pub const fn with_gas_limit(mut self, gas_limit: Option<u64>) -> Self {
693        self.gas_limit = gas_limit;
694        self
695    }
696
697    /// Disable block gas limit check
698    ///
699    /// If set to `true` block gas limit will not be enforced
700    #[must_use]
701    pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self {
702        self.disable_block_gas_limit = disable_block_gas_limit;
703        self
704    }
705
706    /// Enable tx gas limit check
707    ///
708    /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825)
709    #[must_use]
710    pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self {
711        self.enable_tx_gas_limit = enable_tx_gas_limit;
712        self
713    }
714
715    /// Sets the gas price
716    #[must_use]
717    pub const fn with_gas_price(mut self, gas_price: Option<u128>) -> Self {
718        self.gas_price = gas_price;
719        self
720    }
721
722    /// Sets prune history status.
723    #[must_use]
724    pub fn set_pruned_history(mut self, prune_history: Option<Option<usize>>) -> Self {
725        self.prune_history = PruneStateHistoryConfig::from_args(prune_history);
726        self
727    }
728
729    /// Sets max number of states to cache on disk.
730    #[must_use]
731    pub fn with_max_persisted_states<U: Into<usize>>(
732        mut self,
733        max_persisted_states: Option<U>,
734    ) -> Self {
735        self.max_persisted_states = max_persisted_states.map(Into::into);
736        self
737    }
738
739    /// Sets the max number of transactions in a block
740    #[must_use]
741    pub const fn with_max_transactions(mut self, max_transactions: Option<usize>) -> Self {
742        if let Some(max_transactions) = max_transactions {
743            self.max_transactions = max_transactions;
744        }
745        self
746    }
747
748    /// Sets max number of blocks with transactions to keep in memory
749    #[must_use]
750    pub fn with_transaction_block_keeper<U: Into<usize>>(
751        mut self,
752        transaction_block_keeper: Option<U>,
753    ) -> Self {
754        self.transaction_block_keeper = transaction_block_keeper.map(Into::into);
755        self
756    }
757
758    /// Sets the base fee
759    #[must_use]
760    pub const fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
761        self.base_fee = base_fee;
762        self
763    }
764
765    /// Disable the enforcement of a minimum suggested priority fee
766    #[must_use]
767    pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self {
768        self.disable_min_priority_fee = disable_min_priority_fee;
769        self
770    }
771
772    /// Sets the init genesis (genesis.json)
773    #[must_use]
774    pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
775        self.genesis = genesis;
776        self
777    }
778
779    /// Returns the genesis timestamp to use
780    pub fn get_genesis_timestamp(&self) -> u64 {
781        self.genesis_timestamp
782            .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
783            .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
784    }
785
786    /// Sets the genesis timestamp
787    #[must_use]
788    pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
789        if let Some(timestamp) = timestamp {
790            self.genesis_timestamp = Some(timestamp.into());
791        }
792        self
793    }
794
795    /// Sets the genesis number
796    #[must_use]
797    pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
798        if let Some(number) = number {
799            self.genesis_block_number = Some(number.into());
800        }
801        self
802    }
803
804    /// Returns the genesis number
805    pub fn get_genesis_number(&self) -> u64 {
806        self.genesis_block_number
807            .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
808            .unwrap_or(0)
809    }
810
811    /// Sets the hardfork
812    #[must_use]
813    pub const fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
814        self.hardfork = hardfork;
815        self
816    }
817
818    /// Sets the genesis accounts
819    #[must_use]
820    pub fn with_genesis_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
821        self.genesis_accounts = accounts;
822        self
823    }
824
825    /// Sets the signer accounts
826    #[must_use]
827    pub fn with_signer_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
828        self.signer_accounts = accounts;
829        self
830    }
831
832    /// Sets both the genesis accounts and the signer accounts
833    /// so that `genesis_accounts == accounts`
834    pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
835        let accounts = generator.generate()?;
836        self.account_generator = Some(generator);
837        Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
838    }
839
840    /// Sets the balance of the genesis accounts in the genesis block
841    #[must_use]
842    pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
843        self.genesis_balance = balance.into();
844        self
845    }
846
847    /// Sets the block time to automine blocks
848    #[must_use]
849    pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
850        self.block_time = block_time.map(Into::into);
851        self
852    }
853
854    #[must_use]
855    pub fn with_mixed_mining<D: Into<Duration>>(
856        mut self,
857        mixed_mining: bool,
858        block_time: Option<D>,
859    ) -> Self {
860        self.block_time = block_time.map(Into::into);
861        self.mixed_mining = mixed_mining;
862        self
863    }
864
865    /// If set to `true` auto mining will be disabled
866    #[must_use]
867    pub const fn with_no_mining(mut self, no_mining: bool) -> Self {
868        self.no_mining = no_mining;
869        self
870    }
871
872    /// Sets the slots in an epoch
873    #[must_use]
874    pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self {
875        self.slots_in_an_epoch = slots_in_an_epoch;
876        self
877    }
878
879    /// Sets the port to use
880    #[must_use]
881    pub const fn with_port(mut self, port: u16) -> Self {
882        self.port = port;
883        self
884    }
885
886    /// Sets the ipc path to use
887    ///
888    /// Note: this is a double Option for
889    ///     - `None` -> no ipc
890    ///     - `Some(None)` -> use default path
891    ///     - `Some(Some(path))` -> use custom path
892    #[must_use]
893    pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
894        self.ipc_path = ipc_path;
895        self
896    }
897
898    /// Sets the file path to write the Anvil node's config info to.
899    #[must_use]
900    pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
901        self.config_out = config_out;
902        self
903    }
904
905    #[must_use]
906    pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self {
907        self.no_storage_caching = no_storage_caching;
908        self
909    }
910
911    /// Sets the `eth_rpc_url` to use when forking (single endpoint convenience).
912    #[must_use]
913    pub fn with_eth_rpc_url<U: Into<String>>(mut self, eth_rpc_url: Option<U>) -> Self {
914        if let Some(url) = eth_rpc_url {
915            self.fork_urls = vec![url.into()];
916        }
917        self
918    }
919
920    /// Sets the fork URLs for load-balanced multi-endpoint forking.
921    #[must_use]
922    pub fn with_fork_urls(mut self, fork_urls: Vec<String>) -> Self {
923        self.fork_urls = fork_urls;
924        self
925    }
926
927    /// Sets the `fork_choice` to use to fork off from based on a block number
928    #[must_use]
929    pub fn with_fork_block_number<U: Into<u64>>(self, fork_block_number: Option<U>) -> Self {
930        self.with_fork_choice(fork_block_number.map(Into::into))
931    }
932
933    /// Sets the `fork_choice` to use to fork off from based on a transaction hash
934    #[must_use]
935    pub fn with_fork_transaction_hash<U: Into<TxHash>>(
936        self,
937        fork_transaction_hash: Option<U>,
938    ) -> Self {
939        self.with_fork_choice(fork_transaction_hash.map(Into::into))
940    }
941
942    /// Sets the `fork_choice` to use to fork off from
943    #[must_use]
944    pub fn with_fork_choice<U: Into<ForkChoice>>(mut self, fork_choice: Option<U>) -> Self {
945        self.fork_choice = fork_choice.map(Into::into);
946        self
947    }
948
949    /// Sets the `fork_chain_id` to use to fork off local cache from
950    #[must_use]
951    pub const fn with_fork_chain_id(mut self, fork_chain_id: Option<U256>) -> Self {
952        self.fork_chain_id = fork_chain_id;
953        self
954    }
955
956    /// Sets the `fork_headers` to use with fork RPC endpoints
957    #[must_use]
958    pub fn with_fork_headers(mut self, headers: Vec<String>) -> Self {
959        self.fork_headers = headers;
960        self
961    }
962
963    /// Sets the `fork_request_timeout` to use for requests
964    #[must_use]
965    pub const fn fork_request_timeout(mut self, fork_request_timeout: Option<Duration>) -> Self {
966        if let Some(fork_request_timeout) = fork_request_timeout {
967            self.fork_request_timeout = fork_request_timeout;
968        }
969        self
970    }
971
972    /// Sets the `fork_request_retries` to use for spurious networks
973    #[must_use]
974    pub const fn fork_request_retries(mut self, fork_request_retries: Option<u32>) -> Self {
975        if let Some(fork_request_retries) = fork_request_retries {
976            self.fork_request_retries = fork_request_retries;
977        }
978        self
979    }
980
981    /// Sets the initial `fork_retry_backoff` for rate limits
982    #[must_use]
983    pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option<Duration>) -> Self {
984        if let Some(fork_retry_backoff) = fork_retry_backoff {
985            self.fork_retry_backoff = fork_retry_backoff;
986        }
987        self
988    }
989
990    /// Sets the number of assumed available compute units per second
991    ///
992    /// See also, <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
993    #[must_use]
994    pub const fn fork_compute_units_per_second(
995        mut self,
996        compute_units_per_second: Option<u64>,
997    ) -> Self {
998        if let Some(compute_units_per_second) = compute_units_per_second {
999            self.compute_units_per_second = compute_units_per_second;
1000        }
1001        self
1002    }
1003
1004    /// Sets whether to enable tracing
1005    #[must_use]
1006    pub const fn with_tracing(mut self, enable_tracing: bool) -> Self {
1007        self.enable_tracing = enable_tracing;
1008        self
1009    }
1010
1011    /// Sets whether to enable steps tracing
1012    #[must_use]
1013    pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self {
1014        self.enable_steps_tracing = enable_steps_tracing;
1015        self
1016    }
1017
1018    /// Sets whether to print `console.log` invocations to stdout.
1019    #[must_use]
1020    pub const fn with_print_logs(mut self, print_logs: bool) -> Self {
1021        self.print_logs = print_logs;
1022        self
1023    }
1024
1025    /// Sets whether to print traces to stdout.
1026    #[must_use]
1027    pub const fn with_print_traces(mut self, print_traces: bool) -> Self {
1028        self.print_traces = print_traces;
1029        self
1030    }
1031
1032    /// Sets whether to enable autoImpersonate
1033    #[must_use]
1034    pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
1035        self.enable_auto_impersonate = enable_auto_impersonate;
1036        self
1037    }
1038
1039    #[must_use]
1040    pub fn with_server_config(mut self, config: ServerConfig) -> Self {
1041        self.server_config = config;
1042        self
1043    }
1044
1045    /// Sets the host the server will listen on
1046    #[must_use]
1047    pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
1048        self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
1049        self
1050    }
1051
1052    #[must_use]
1053    pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self {
1054        self.transaction_order = transaction_order;
1055        self
1056    }
1057
1058    /// Returns the ipc path for the ipc endpoint if any
1059    pub fn get_ipc_path(&self) -> Option<String> {
1060        match &self.ipc_path {
1061            Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
1062            None => None,
1063        }
1064    }
1065
1066    /// Prints the config info
1067    pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
1068        if let Some(path) = &self.config_out {
1069            let value = self.as_json(fork);
1070            foundry_common::fs::write_json_file(path, &value).wrap_err("failed writing JSON")?;
1071        }
1072        if !self.silent {
1073            sh_println!("{}", self.as_string(fork))?;
1074        }
1075        Ok(())
1076    }
1077
1078    /// Returns the endpoint-specific path where the cache file should be stored.
1079    ///
1080    /// See also [`Config::foundry_block_cache_file`].
1081    pub fn block_cache_path(&self, block: u64) -> Option<PathBuf> {
1082        self.block_cache_path_for_rpc(block, self.fork_urls.first()?)
1083    }
1084
1085    fn block_cache_path_for_rpc(&self, block: u64, rpc_url: &str) -> Option<PathBuf> {
1086        if self.no_storage_caching || self.fork_urls.is_empty() {
1087            return None;
1088        }
1089        let chain_id = self.get_chain_id();
1090        let rpc_url_hash = hex::encode(keccak256(rpc_url));
1091        Some(
1092            Config::foundry_block_cache_file(chain_id, block)?
1093                .with_file_name(format!("storage-{rpc_url_hash}.json")),
1094        )
1095    }
1096
1097    /// Sets whether to disable the default create2 deployer
1098    #[must_use]
1099    pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self {
1100        self.disable_default_create2_deployer = yes;
1101        self
1102    }
1103
1104    /// Sets whether to disable pool balance checks
1105    #[must_use]
1106    pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self {
1107        self.disable_pool_balance_checks = yes;
1108        self
1109    }
1110
1111    /// Injects precompiles to `anvil`'s EVM.
1112    #[must_use]
1113    pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
1114        self.precompile_factory = Some(Arc::new(factory));
1115        self
1116    }
1117
1118    /// Enable features for provided networks.
1119    #[must_use]
1120    pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self {
1121        self.networks = networks;
1122        self
1123    }
1124
1125    /// Enable Tempo network features.
1126    #[must_use]
1127    pub fn with_tempo(mut self) -> Self {
1128        self.networks = NetworkConfigs::with_tempo();
1129        self
1130    }
1131
1132    /// Enable Optimism network features.
1133    #[cfg(feature = "optimism")]
1134    #[must_use]
1135    pub fn with_optimism(mut self) -> Self {
1136        self.networks = NetworkConfigs::with_optimism();
1137        self
1138    }
1139
1140    /// Makes the node silent to not emit anything on stdout
1141    #[must_use]
1142    pub const fn silent(self) -> Self {
1143        self.set_silent(true)
1144    }
1145
1146    #[must_use]
1147    pub const fn set_silent(mut self, silent: bool) -> Self {
1148        self.silent = silent;
1149        self
1150    }
1151
1152    /// Sets the path where persisted states are cached (used with `max_persisted_states`).
1153    ///
1154    /// Note: This does not control the fork RPC cache location, which uses endpoint-specific files
1155    /// under `~/.foundry/cache/rpc/<chain>/<block>/`.
1156    #[must_use]
1157    pub fn with_cache_path(mut self, cache_path: Option<PathBuf>) -> Self {
1158        self.cache_path = cache_path;
1159        self
1160    }
1161
1162    /// Sets accounts to fund with custom balances on startup.
1163    #[must_use]
1164    pub fn with_funded_accounts(mut self, accounts: HashMap<Address, U256>) -> Self {
1165        self.funded_accounts = accounts;
1166        self
1167    }
1168
1169    /// Configures everything related to env, backend and database and returns the
1170    /// [Backend](mem::Backend)
1171    ///
1172    /// *Note*: only memory based backend for now
1173    pub(crate) async fn setup<N>(
1174        &mut self,
1175    ) -> Result<(mem::Backend<N>, Option<ForkTransactionReplay>)>
1176    where
1177        N: alloy_network::Network<
1178                TxEnvelope = foundry_primitives::FoundryTxEnvelope,
1179                ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope,
1180            >,
1181    {
1182        // configure the revm environment
1183
1184        let mut cfg = CfgEnv::default();
1185        cfg.spec = self.get_hardfork().into();
1186
1187        cfg.chain_id = self.get_chain_id();
1188        cfg.limit_contract_code_size = self.code_size_limit;
1189        // EIP-3607 rejects transactions from senders with deployed code.
1190        // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the
1191        // caller is a contract. So we disable the check by default.
1192        cfg.disable_eip3607 = true;
1193        cfg.disable_block_gas_limit = self.disable_block_gas_limit;
1194
1195        if !self.enable_tx_gas_limit {
1196            cfg.tx_gas_limit_cap = Some(u64::MAX);
1197        }
1198
1199        if let Some(value) = self.memory_limit {
1200            cfg.memory_limit = value;
1201        }
1202
1203        let spec_id = cfg.spec;
1204        let mut evm_env = EvmEnv::new(
1205            cfg,
1206            BlockEnv {
1207                gas_limit: self.gas_limit(),
1208                basefee: self.get_base_fee(),
1209                ..Default::default()
1210            },
1211        );
1212
1213        self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1214
1215        let genesis_timestamp = self.get_genesis_timestamp();
1216        let base_fee_params: BaseFeeParams = self.networks.base_fee_params(genesis_timestamp);
1217
1218        // On Tempo, the base fee follows the chain's hardfork rules instead of EIP-1559.
1219        let tempo_hardfork =
1220            self.networks.is_tempo().then(|| TempoHardfork::from(self.get_hardfork()));
1221
1222        let fees = FeeManager::new(
1223            spec_id,
1224            self.get_base_fee(),
1225            !self.disable_min_priority_fee,
1226            self.get_gas_price(),
1227            self.get_blob_excess_gas_and_price(),
1228            self.get_blob_params(),
1229            base_fee_params,
1230            tempo_hardfork,
1231        );
1232
1233        let (db, fork, fork_transaction_replay) =
1234            if let Some(eth_rpc_url) = self.fork_urls.first().cloned() {
1235                self.setup_fork_db_with_replay(eth_rpc_url, &mut evm_env, &fees).await?
1236            } else {
1237                let track_history = self.prune_history.is_state_history_supported();
1238                let db: Arc<TokioRwLock<Box<dyn Db>>> =
1239                    Arc::new(TokioRwLock::new(Box::new(StateRootDb::new(track_history))));
1240                (db, None, None)
1241            };
1242
1243        // if provided use all settings of `genesis.json`
1244        if let Some(ref genesis) = self.genesis {
1245            // --chain-id flag gets precedence over the genesis.json chain id
1246            // <https://github.com/foundry-rs/foundry/issues/10059>
1247            if self.chain_id.is_none() {
1248                evm_env.cfg_env.chain_id = genesis.config.chain_id;
1249            }
1250            evm_env.block_env.timestamp = U256::from(genesis.timestamp);
1251            if let Some(base_fee) = genesis.base_fee_per_gas {
1252                evm_env.block_env.basefee = base_fee.try_into()?;
1253            }
1254            if let Some(number) = genesis.number {
1255                evm_env.block_env.number = U256::from(number);
1256            }
1257            evm_env.block_env.beneficiary = genesis.coinbase;
1258        }
1259
1260        // Fork setup initializes its own timestamp. For a local BSC chain, keep the initial EVM
1261        // and genesis block on the same resolved timestamp so chain precompiles are available
1262        // immediately. Preserve the default timestamp behavior for all other local chains.
1263        let is_bsc = matches!(
1264            NamedChain::try_from(evm_env.cfg_env.chain_id),
1265            Ok(NamedChain::BinanceSmartChain | NamedChain::BinanceSmartChainTestnet)
1266        );
1267        if fork.is_none() && (self.genesis_timestamp.is_some() || is_bsc) {
1268            evm_env.block_env.timestamp = U256::from(genesis_timestamp);
1269        }
1270
1271        self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1272
1273        let genesis = GenesisConfig {
1274            number: self.get_genesis_number(),
1275            timestamp: genesis_timestamp,
1276            balance: self.genesis_balance,
1277            accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(),
1278            genesis_init: self.genesis.clone(),
1279        };
1280
1281        let mut decoder_builder = CallTraceDecoderBuilder::new().with_tempo_hardfork(
1282            self.networks.is_tempo().then(|| TempoHardfork::from(self.get_hardfork())),
1283        );
1284        if self.print_traces {
1285            // if traces should get printed we configure the decoder with the signatures cache
1286            if let Ok(identifier) = SignaturesIdentifier::new(false) {
1287                debug!(target: "node", "using signature identifier");
1288                decoder_builder = decoder_builder.with_signature_identifier(identifier);
1289            }
1290        }
1291
1292        // only memory based backend for now
1293        let backend = mem::Backend::with_genesis(
1294            db,
1295            Arc::new(RwLock::new(evm_env)),
1296            self.networks,
1297            genesis,
1298            fees,
1299            Arc::new(RwLock::new(fork)),
1300            self.enable_steps_tracing,
1301            self.print_logs,
1302            self.print_traces,
1303            Arc::new(decoder_builder.build()),
1304            self.prune_history,
1305            self.max_persisted_states,
1306            self.transaction_block_keeper,
1307            self.block_time,
1308            self.cache_path.clone(),
1309            Arc::new(TokioRwLock::new(self.clone())),
1310        )
1311        .await?;
1312
1313        // Writes the default create2 deployer to the backend,
1314        // if the option is not disabled and we are not forking.
1315        if !self.disable_default_create2_deployer && self.fork_urls.is_empty() {
1316            backend
1317                .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER)
1318                .await
1319                .wrap_err("failed to create default create2 deployer")?;
1320        }
1321
1322        if !self.funded_accounts.is_empty() {
1323            for (address, balance) in &self.funded_accounts {
1324                backend
1325                    .set_balance(*address, *balance)
1326                    .await
1327                    .wrap_err_with(|| format!("failed to fund account {address}"))?;
1328            }
1329        }
1330
1331        Ok((backend, fork_transaction_replay))
1332    }
1333
1334    /// Configures everything related to forking based on the passed `eth_rpc_url`:
1335    ///  - returning a tuple of a [ForkedDatabase] wrapped in an [Arc] [RwLock](TokioRwLock) and
1336    ///    [ClientFork] wrapped in an [Option] which can be used in a [Backend](mem::Backend) to
1337    ///    fork from.
1338    ///  - modifying some parameters of the passed `env`
1339    ///  - mutating some members of `self`
1340    pub async fn setup_fork_db(
1341        &mut self,
1342        eth_rpc_url: String,
1343        evm_env: &mut EvmEnv,
1344        fees: &FeeManager,
1345    ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>)> {
1346        let (db, fork, replay) = self.setup_fork_db_with_replay(eth_rpc_url, evm_env, fees).await?;
1347        eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1348        Ok((db, fork))
1349    }
1350
1351    async fn setup_fork_db_with_replay(
1352        &mut self,
1353        eth_rpc_url: String,
1354        evm_env: &mut EvmEnv,
1355        fees: &FeeManager,
1356    ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>, Option<ForkTransactionReplay>)>
1357    {
1358        let (db, config, replay) =
1359            self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees).await?;
1360        let db: Arc<TokioRwLock<Box<dyn Db>>> = Arc::new(TokioRwLock::new(Box::new(db)));
1361        let fork = ClientFork::new(config, Arc::clone(&db));
1362        Ok((db, Some(fork), replay))
1363    }
1364
1365    /// Configures everything related to forking based on the passed `eth_rpc_url`:
1366    ///  - returning a tuple of a [ForkedDatabase] and [ClientForkConfig] which can be used to build
1367    ///    a [ClientFork] to fork from.
1368    ///  - modifying some parameters of the passed `env`
1369    ///  - mutating some members of `self`
1370    pub async fn setup_fork_db_config(
1371        &mut self,
1372        eth_rpc_url: String,
1373        evm_env: &mut EvmEnv,
1374        fees: &FeeManager,
1375    ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig)> {
1376        let (db, config, replay) =
1377            self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees).await?;
1378        eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1379        Ok((db, config))
1380    }
1381
1382    pub(crate) async fn setup_fork_db_config_with_replay(
1383        &mut self,
1384        eth_rpc_url: String,
1385        evm_env: &mut EvmEnv,
1386        fees: &FeeManager,
1387    ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig, Option<ForkTransactionReplay>)> {
1388        debug!(target: "node", ?eth_rpc_url, "setting up fork db");
1389        let override_chain_id = self.chain_id;
1390
1391        // Always bootstrap with the primary URL only to avoid race conditions
1392        // where discovery calls (get_chain_id, find_latest_fork_block, get_block)
1393        // hit different endpoints that may be at different chain tips.
1394        let provider = Arc::new(
1395            ProviderBuilder::new(&eth_rpc_url)
1396                .timeout(self.fork_request_timeout)
1397                .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1398                .compute_units_per_second(self.compute_units_per_second)
1399                .max_retry(self.fork_request_retries)
1400                .headers(self.fork_headers.clone())
1401                .build()
1402                .wrap_err("failed to establish provider to fork url")?,
1403        );
1404
1405        let source_chain_id = if let Some(chain_id) = self.fork_chain_id {
1406            eyre::ensure!(
1407                self.fork_urls.len() == 1,
1408                "multiple fork URLs cannot be validated with --fork-chain-id; remove \
1409                 --fork-chain-id to validate every endpoint"
1410            );
1411            chain_id.to()
1412        } else {
1413            let chain_id = provider
1414                .get_chain_id()
1415                .await
1416                .wrap_err_with(|| format!("failed to fetch network chain ID from {eth_rpc_url}"))?;
1417            ensure_fork_network_supported(chain_id)?;
1418
1419            for url in self.fork_urls.iter().skip(1) {
1420                let endpoint_provider = ProviderBuilder::<AnyNetwork>::new(url)
1421                    .timeout(self.fork_request_timeout)
1422                    .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1423                    .compute_units_per_second(self.compute_units_per_second)
1424                    .max_retry(self.fork_request_retries)
1425                    .headers(self.fork_headers.clone())
1426                    .build()
1427                    .wrap_err_with(|| format!("failed to establish provider to fork url {url}"))?;
1428                let endpoint_chain_id = endpoint_provider
1429                    .get_chain_id()
1430                    .await
1431                    .wrap_err_with(|| format!("failed to fetch network chain ID from {url}"))?;
1432                ensure_fork_network_supported(endpoint_chain_id)?;
1433                if endpoint_chain_id != chain_id {
1434                    eyre::bail!(
1435                        "fork endpoints must use the same chain ID: expected {chain_id}, got \
1436                         {endpoint_chain_id} from {url}"
1437                    );
1438                }
1439            }
1440
1441            chain_id
1442        };
1443        ensure_fork_network_supported(source_chain_id)?;
1444
1445        let (fork_block_number, fork_transaction_replay) =
1446            if let Some(fork_choice) = &self.fork_choice {
1447                let (fork_block_number, fork_transaction_replay) =
1448                    derive_block_and_replay(fork_choice, &provider).await.wrap_err(
1449                        "failed to derive fork block and transaction replay from fork choice",
1450                    )?;
1451                (fork_block_number, fork_transaction_replay)
1452            } else {
1453                // pick the last block number but also ensure it's not pending anymore
1454                let bn = find_latest_fork_block(&provider)
1455                    .await
1456                    .wrap_err("failed to get fork block number")?;
1457                (bn, None)
1458            };
1459
1460        let block = provider
1461            .get_block(BlockNumberOrTag::Number(fork_block_number).into())
1462            .await
1463            .wrap_err("failed to get fork block")?;
1464
1465        let block = if let Some(block) = block {
1466            block
1467        } else {
1468            if let Ok(latest_block) = provider.get_block_number().await {
1469                let mut message = format!(
1470                    "Failed to get block for block number: {fork_block_number}\n\
1471latest block number: {latest_block}"
1472                );
1473                // If the `eth_getBlockByNumber` call succeeds, but returns null instead of
1474                // the block, and the block number is less than equal the latest block, then
1475                // the user is forking from a non-archive node with an older block number.
1476                if fork_block_number <= latest_block {
1477                    message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}"));
1478                }
1479                eyre::bail!("{message}");
1480            }
1481            eyre::bail!("failed to get block for block number: {fork_block_number}");
1482        };
1483
1484        if let Some(replay) = &fork_transaction_replay {
1485            let source_header = replay.source_block.header();
1486            eyre::ensure!(
1487                block.header.hash == source_header.parent_hash,
1488                "fork transaction block {} at {} has parent {}, but fetched fork block at {} has \
1489                 hash {}",
1490                source_header.hash,
1491                source_header.number,
1492                source_header.parent_hash,
1493                block.header.number,
1494                block.header.hash
1495            );
1496            eyre::ensure!(
1497                block.header.number.checked_add(1) == Some(source_header.number),
1498                "fork transaction block {} has number {}, but fetched parent {} has number {}",
1499                source_header.hash,
1500                source_header.number,
1501                block.header.hash,
1502                block.header.number
1503            );
1504        }
1505
1506        let gas_limit = self.fork_gas_limit(&block);
1507        self.gas_limit = Some(gas_limit);
1508
1509        // Cache identity must describe the remote fork block, not local execution overrides that
1510        // can change after mining (for example, the locally advanced base fee).
1511        let cache_block_env: BlockEnv = block_env_from_header(&block.header);
1512
1513        evm_env.block_env = BlockEnv {
1514            gas_limit,
1515            // Keep previous `coinbase` and `basefee` value
1516            beneficiary: evm_env.block_env.beneficiary,
1517            basefee: evm_env.block_env.basefee,
1518            ..block_env_from_header(&block.header)
1519        };
1520
1521        // Determine chain_id early so we can use it consistently
1522        let chain_id = if let Some(chain_id) = self.chain_id {
1523            chain_id
1524        } else {
1525            // need to update the dev signers and env with the chain id
1526            self.set_chain_id(Some(source_chain_id));
1527            evm_env.cfg_env.chain_id = source_chain_id;
1528            source_chain_id
1529        };
1530
1531        // Auto-detect hardfork from chain activation data if not explicitly set.
1532        if self.hardfork.is_none()
1533            && let Some(hardfork) =
1534                FoundryHardfork::from_chain_and_timestamp(chain_id, block.header.timestamp())
1535        {
1536            evm_env.cfg_env.spec = SpecId::from(hardfork);
1537            self.hardfork = Some(hardfork);
1538        }
1539
1540        // The fee manager was built before the fork hardfork was known, so refresh the Tempo
1541        // hardfork it uses for base fee calculations.
1542        if self.networks.is_tempo() {
1543            fees.set_tempo_hardfork(Some(TempoHardfork::from(self.get_hardfork())));
1544        }
1545
1546        // if not set explicitly we use the base fee of the latest block
1547        if self.base_fee.is_none()
1548            && let Some(base_fee) = block.header.base_fee_per_gas()
1549        {
1550            self.base_fee = Some(base_fee);
1551            evm_env.block_env.basefee = base_fee;
1552            // this is the base fee of the current block, but we need the base fee of
1553            // the next block
1554            let next_block_base_fee = fees.get_next_block_base_fee_per_gas(
1555                block.header.gas_used(),
1556                gas_limit,
1557                block.header.base_fee_per_gas().unwrap_or_default(),
1558            );
1559
1560            // update next base fee
1561            fees.set_base_fee(next_block_base_fee);
1562        }
1563
1564        if let (Some(blob_excess_gas), Some(blob_gas_used)) =
1565            (block.header.excess_blob_gas(), block.header.blob_gas_used())
1566        {
1567            // Derive blob params using the fork block timestamp regardless of explicit base fee.
1568            let blob_params = get_blob_params(chain_id, block.header.timestamp());
1569
1570            evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
1571                blob_excess_gas,
1572                blob_params.update_fraction as u64,
1573            ));
1574
1575            fees.set_blob_params(blob_params);
1576
1577            let next_block_blob_excess_gas =
1578                fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used);
1579            fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
1580                next_block_blob_excess_gas,
1581                blob_params.update_fraction as u64,
1582            ));
1583        }
1584
1585        // use remote gas price
1586        if self.gas_price.is_none()
1587            && let Ok(gas_price) = provider.get_gas_price().await
1588        {
1589            self.gas_price = Some(gas_price);
1590            fees.set_gas_price(gas_price);
1591        }
1592
1593        let block_hash = block.header.hash;
1594
1595        // apply changes such as difficulty -> prevrandao and chain specifics for current chain id
1596        apply_chain_and_block_specific_env_changes::<AnyNetwork, _, _>(
1597            evm_env,
1598            &block,
1599            self.networks,
1600        );
1601
1602        let meta = BlockchainDbMeta::new(cache_block_env, eth_rpc_url.clone());
1603        let block_chain_db = if self.fork_chain_id.is_some() {
1604            BlockchainDb::new_skip_check(
1605                meta,
1606                self.block_cache_path_for_rpc(fork_block_number, &eth_rpc_url),
1607            )
1608        } else {
1609            BlockchainDb::new(meta, self.block_cache_path_for_rpc(fork_block_number, &eth_rpc_url))
1610        };
1611
1612        // After bootstrap, rebuild the provider with round-robin if multiple URLs are
1613        // configured. This ensures bootstrap used only the primary endpoint for consistency,
1614        // while ongoing requests are distributed across all endpoints.
1615        let provider = if self.fork_urls.len() > 1 {
1616            debug!(target: "node", urls=?self.fork_urls, "using multi-endpoint round-robin provider");
1617            Arc::new(
1618                ProviderBuilder::new(&eth_rpc_url)
1619                    .timeout(self.fork_request_timeout)
1620                    .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1621                    .compute_units_per_second(self.compute_units_per_second)
1622                    .max_retry(self.fork_request_retries)
1623                    .headers(self.fork_headers.clone())
1624                    .build_fallback(self.fork_urls.clone())
1625                    .wrap_err("failed to establish round-robin provider to fork urls")?,
1626            )
1627        } else {
1628            provider
1629        };
1630
1631        // This will spawn the background thread that will use the provider to fetch
1632        // blockchain data from the other client
1633        let backend = SharedBackend::spawn_backend(
1634            Arc::clone(&provider),
1635            block_chain_db.clone(),
1636            Some(fork_block_number.into()),
1637        )
1638        .await;
1639
1640        let config = ClientForkConfig {
1641            fork_urls: self.fork_urls.clone(),
1642            block_number: fork_block_number,
1643            block_hash,
1644            transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()),
1645            provider,
1646            chain_id,
1647            override_chain_id,
1648            hardfork: self.hardfork,
1649            timestamp: block.header.timestamp(),
1650            base_fee: block.header.base_fee_per_gas().map(|g| g as u128),
1651            timeout: self.fork_request_timeout,
1652            retries: self.fork_request_retries,
1653            backoff: self.fork_retry_backoff,
1654            compute_units_per_second: self.compute_units_per_second,
1655            headers: self.fork_headers.clone(),
1656            total_difficulty: block.header.total_difficulty.unwrap_or_default(),
1657            blob_gas_used: block.header.blob_gas_used().map(|g| g as u128),
1658            blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price,
1659        };
1660
1661        debug!(target: "node", fork_number=config.block_number, fork_hash=%config.block_hash, "set up fork db");
1662
1663        let mut db = ForkedDatabase::new(backend, block_chain_db);
1664
1665        // need to insert the forked block's hash
1666        db.insert_block_hash(U256::from(config.block_number), config.block_hash);
1667
1668        Ok((db, config, fork_transaction_replay))
1669    }
1670
1671    /// we only use the gas limit value of the block if it is non-zero and the block gas
1672    /// limit is enabled, since there are networks where this is not used and is always
1673    /// `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>
1674    pub(crate) fn fork_gas_limit<B: BlockResponse<Header: BlockHeader>>(&self, block: &B) -> u64 {
1675        if !self.disable_block_gas_limit {
1676            if let Some(gas_limit) = self.gas_limit {
1677                return gas_limit;
1678            } else if block.header().gas_limit() > 0 {
1679                return block.header().gas_limit();
1680            }
1681        }
1682
1683        u64::MAX
1684    }
1685
1686    /// Returns the gas limit for a non forked anvil instance
1687    ///
1688    /// Checks the config for the `disable_block_gas_limit` flag
1689    pub(crate) fn gas_limit(&self) -> u64 {
1690        if self.disable_block_gas_limit {
1691            return u64::MAX;
1692        }
1693
1694        self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)
1695    }
1696}
1697
1698pub(crate) const fn tempo_default_base_fee(hardfork: TempoHardfork) -> u64 {
1699    if hardfork.is_t1() { TEMPO_T1_BASE_FEE } else { TEMPO_T0_BASE_FEE }
1700}
1701
1702/// If the fork choice is a block number, simply return it with an empty list of transactions.
1703/// If the fork choice is a transaction hash, determine the block that the transaction was mined in,
1704/// and return the block number before the fork block along with all transactions in the fork block
1705/// that are before (and including) the fork transaction.
1706async fn derive_block_and_replay(
1707    fork_choice: &ForkChoice,
1708    provider: &Arc<RetryProvider>,
1709) -> eyre::Result<(BlockNumber, Option<ForkTransactionReplay>)> {
1710    match fork_choice {
1711        ForkChoice::Block(block_number) => {
1712            let block_number = *block_number;
1713            if block_number >= 0 {
1714                return Ok((block_number as u64, None));
1715            }
1716            // subtract from latest block number
1717            let latest = provider.get_block_number().await?;
1718
1719            Ok((block_number.saturating_add(latest as i128) as u64, None))
1720        }
1721        ForkChoice::Transaction(transaction_hash) => {
1722            // Determine the block that this transaction was mined in
1723            let transaction = provider
1724                .get_transaction_by_hash(transaction_hash.0.into())
1725                .await?
1726                .ok_or_else(|| eyre::eyre!("fork transaction {transaction_hash} was not found"))?;
1727            let transaction_block_number = transaction.block_number().ok_or_else(|| {
1728                eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
1729            })?;
1730            let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
1731                eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
1732            })?;
1733
1734            // Get the block pertaining to the fork transaction
1735            let transaction_block =
1736                provider.get_block_by_hash(transaction_block_hash).full().await?.ok_or_else(
1737                    || {
1738                        eyre::eyre!(
1739                            "failed to get fork block {transaction_block_hash} for transaction \
1740                         {transaction_hash}"
1741                        )
1742                    },
1743                )?;
1744            let replay = validate_fork_transaction_replay(
1745                *transaction_hash,
1746                &transaction,
1747                transaction_block,
1748            )?;
1749            Ok((transaction_block_number.saturating_sub(1), Some(replay)))
1750        }
1751    }
1752}
1753
1754fn validate_fork_transaction_replay(
1755    transaction_hash: TxHash,
1756    transaction: &alloy_network::AnyRpcTransaction,
1757    source_block: AnyRpcBlock,
1758) -> eyre::Result<ForkTransactionReplay> {
1759    let source_hash = source_block.header.hash;
1760    let source_number = source_block.header.number;
1761    let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
1762        eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
1763    })?;
1764    let transaction_block_number = transaction.block_number().ok_or_else(|| {
1765        eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
1766    })?;
1767
1768    eyre::ensure!(
1769        source_hash == transaction_block_hash,
1770        "fork transaction {transaction_hash} reports block {transaction_block_hash}, but fetched \
1771         block hash is {source_hash}"
1772    );
1773    eyre::ensure!(
1774        source_number == transaction_block_number,
1775        "fork transaction {transaction_hash} reports block number {transaction_block_number}, but \
1776         fetched block {source_hash} has number {source_number}"
1777    );
1778    eyre::ensure!(
1779        source_number > 0,
1780        "fork transaction {transaction_hash} is in genesis block {source_hash}, which has no parent"
1781    );
1782
1783    let transactions = source_block.transactions.as_transactions().ok_or_else(|| {
1784        eyre::eyre!("fork block {source_hash} at {source_number} did not include full transactions")
1785    })?;
1786    let mut matches =
1787        transactions.iter().enumerate().filter(|(_, tx)| tx.tx_hash() == transaction_hash);
1788    let target_index = matches.next().map(|(index, _)| index).ok_or_else(|| {
1789        eyre::eyre!(
1790            "fork transaction {transaction_hash} is absent from block {source_hash} at \
1791             {source_number}"
1792        )
1793    })?;
1794    eyre::ensure!(
1795        matches.next().is_none(),
1796        "fork transaction {transaction_hash} occurs more than once in block {source_hash} at \
1797         {source_number}"
1798    );
1799    if let Some(reported_index) = transaction.transaction_index() {
1800        eyre::ensure!(
1801            reported_index == target_index as u64,
1802            "fork transaction {transaction_hash} reports index {reported_index}, but occurs at \
1803             index {target_index} in block {source_hash}"
1804        );
1805    }
1806
1807    Ok(ForkTransactionReplay { source_block, target_index })
1808}
1809
1810/// Fork delimiter used to specify which block or transaction to fork from.
1811#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1812pub enum ForkChoice {
1813    /// Block number to fork from.
1814    ///
1815    /// If negative, the given value is subtracted from the `latest` block number.
1816    Block(i128),
1817    /// Transaction hash to fork from.
1818    Transaction(TxHash),
1819}
1820
1821impl ForkChoice {
1822    /// Returns the block number to fork from
1823    pub const fn block_number(&self) -> Option<i128> {
1824        match self {
1825            Self::Block(block_number) => Some(*block_number),
1826            Self::Transaction(_) => None,
1827        }
1828    }
1829
1830    /// Returns the transaction hash to fork from
1831    pub const fn transaction_hash(&self) -> Option<TxHash> {
1832        match self {
1833            Self::Block(_) => None,
1834            Self::Transaction(transaction_hash) => Some(*transaction_hash),
1835        }
1836    }
1837}
1838
1839/// Convert a transaction hash into a ForkChoice
1840impl From<TxHash> for ForkChoice {
1841    fn from(tx_hash: TxHash) -> Self {
1842        Self::Transaction(tx_hash)
1843    }
1844}
1845
1846/// Convert a decimal block number into a ForkChoice
1847impl From<u64> for ForkChoice {
1848    fn from(block: u64) -> Self {
1849        Self::Block(block as i128)
1850    }
1851}
1852
1853#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1854pub struct PruneStateHistoryConfig {
1855    pub enabled: bool,
1856    pub max_memory_history: Option<usize>,
1857}
1858
1859impl PruneStateHistoryConfig {
1860    /// Returns `true` if writing state history is supported
1861    pub const fn is_state_history_supported(&self) -> bool {
1862        if !self.enabled {
1863            return true;
1864        }
1865
1866        match self.max_memory_history {
1867            Some(limit) => limit > 0,
1868            None => false,
1869        }
1870    }
1871
1872    /// Returns true if this setting was enabled.
1873    pub const fn is_config_enabled(&self) -> bool {
1874        self.enabled
1875    }
1876
1877    pub fn from_args(val: Option<Option<usize>>) -> Self {
1878        val.map(|max_memory_history| Self {
1879            enabled: true,
1880            max_memory_history: max_memory_history.filter(|limit| *limit > 0),
1881        })
1882        .unwrap_or_default()
1883    }
1884}
1885
1886/// Can create dev accounts
1887#[derive(Clone, Debug)]
1888pub struct AccountGenerator {
1889    chain_id: u64,
1890    amount: usize,
1891    phrase: String,
1892    derivation_path: Option<String>,
1893}
1894
1895impl AccountGenerator {
1896    pub fn new(amount: usize) -> Self {
1897        Self {
1898            chain_id: CHAIN_ID,
1899            amount,
1900            phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
1901            derivation_path: None,
1902        }
1903    }
1904
1905    #[must_use]
1906    pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
1907        self.phrase = phrase.into();
1908        self
1909    }
1910
1911    fn get_phrase(&self) -> &str {
1912        &self.phrase
1913    }
1914
1915    #[must_use]
1916    pub fn chain_id(mut self, chain_id: impl Into<u64>) -> Self {
1917        self.chain_id = chain_id.into();
1918        self
1919    }
1920
1921    #[must_use]
1922    pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
1923        let mut derivation_path = derivation_path.into();
1924        if !derivation_path.ends_with('/') {
1925            derivation_path.push('/');
1926        }
1927        self.derivation_path = Some(derivation_path);
1928        self
1929    }
1930
1931    fn get_derivation_path(&self) -> &str {
1932        self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
1933    }
1934}
1935
1936impl AccountGenerator {
1937    pub fn generate(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
1938        let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
1939
1940        // use the derivation path
1941        let derivation_path = self.get_derivation_path();
1942
1943        let mut wallets = Vec::with_capacity(self.amount);
1944        for idx in 0..self.amount {
1945            let builder =
1946                builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap();
1947            let wallet = builder.build()?.with_chain_id(Some(self.chain_id));
1948            wallets.push(wallet)
1949        }
1950        Ok(wallets)
1951    }
1952}
1953
1954/// Returns the path to anvil dir `~/.foundry/anvil`
1955pub fn anvil_dir() -> Option<PathBuf> {
1956    Config::foundry_dir().map(|p| p.join("anvil"))
1957}
1958
1959/// Returns the root path to anvil's temporary storage `~/.foundry/anvil/`
1960pub fn anvil_tmp_dir() -> Option<PathBuf> {
1961    anvil_dir().map(|p| p.join("tmp"))
1962}
1963
1964/// Finds the latest appropriate block to fork
1965///
1966/// This fetches the "latest" block and checks whether the `Block` is fully populated (`hash` field
1967/// 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>
1968async fn find_latest_fork_block<P: Provider<AnyNetwork>>(
1969    provider: P,
1970) -> Result<u64, TransportError> {
1971    let mut num = provider.get_block_number().await?;
1972
1973    // walk back from the head of the chain, but at most 2 blocks, which should be more than enough
1974    // leeway
1975    for _ in 0..2 {
1976        if let Some(block) = provider.get_block(num.into()).await?
1977            && !block.header.hash.is_zero()
1978        {
1979            break;
1980        }
1981        // block not actually finalized, so we try the block before
1982        num = num.saturating_sub(1)
1983    }
1984
1985    Ok(num)
1986}
1987
1988#[cfg(test)]
1989mod tests {
1990    use super::*;
1991
1992    #[test]
1993    fn test_prune_history() {
1994        let config = PruneStateHistoryConfig::default();
1995        assert!(config.is_state_history_supported());
1996        let config = PruneStateHistoryConfig::from_args(Some(None));
1997        assert!(!config.is_state_history_supported());
1998        let config = PruneStateHistoryConfig::from_args(Some(Some(0)));
1999        assert!(config.is_config_enabled());
2000        assert!(!config.is_state_history_supported());
2001        let config = PruneStateHistoryConfig::from_args(Some(Some(10)));
2002        assert!(config.is_state_history_supported());
2003    }
2004
2005    #[cfg(feature = "optimism")]
2006    #[test]
2007    fn set_chain_id_updates_network_config() {
2008        let mut config = NodeConfig::test();
2009        config.set_chain_id(Some(10u64));
2010
2011        assert!(config.networks.is_optimism());
2012    }
2013
2014    #[test]
2015    fn get_hardfork_on_tempo_never_returns_non_tempo_variant() {
2016        // Post-Shanghai timestamp on Ethereum mainnet.
2017        let shanghai_ts = 1_681_338_455u64;
2018
2019        let config = NodeConfig::test_tempo()
2020            .with_chain_id(Some(1u64))
2021            .with_genesis_timestamp(Some(shanghai_ts));
2022
2023        assert!(config.networks.is_tempo());
2024        assert!(matches!(config.get_hardfork(), FoundryHardfork::Tempo(_)));
2025    }
2026
2027    #[test]
2028    fn get_hardfork_on_local_tempo_defaults_to_latest_active() {
2029        let config = NodeConfig::test_tempo();
2030
2031        assert_eq!(config.get_hardfork(), FoundryHardfork::Tempo(latest_active_tempo_hardfork()));
2032    }
2033}