Skip to main content

anvil/
cmd.rs

1use crate::{
2    AccountGenerator, CHAIN_ID, NodeConfig,
3    config::{DEFAULT_MNEMONIC, ForkChoice},
4    eth::{EthApi, backend::db::SerializableState, pool::transactions::TransactionOrder},
5};
6use alloy_genesis::Genesis;
7use alloy_primitives::{B256, U256, utils::Unit};
8use alloy_signer_local::coins_bip39::{English, Mnemonic};
9use anvil_server::ServerConfig;
10use clap::Parser;
11use core::fmt;
12use foundry_common::shell;
13use foundry_config::{Chain, Config, FigmentProviders};
14use foundry_evm::hardfork::{EthereumHardfork, OpHardfork};
15use foundry_evm_networks::NetworkConfigs;
16use futures::FutureExt;
17use rand_08::{SeedableRng, rngs::StdRng};
18use std::{
19    net::IpAddr,
20    path::{Path, PathBuf},
21    pin::Pin,
22    str::FromStr,
23    sync::{
24        Arc,
25        atomic::{AtomicUsize, Ordering},
26    },
27    task::{Context, Poll},
28    time::Duration,
29};
30use tokio::time::{Instant, Interval};
31
32#[derive(Clone, Debug, Parser)]
33pub struct NodeArgs {
34    /// Port number to listen on.
35    #[arg(long, short, default_value = "8545", value_name = "NUM")]
36    pub port: u16,
37
38    /// Number of dev accounts to generate and configure.
39    #[arg(long, short, default_value = "10", value_name = "NUM")]
40    pub accounts: u64,
41
42    /// The balance of every dev account in Ether.
43    #[arg(long, default_value = "10000", value_name = "NUM")]
44    pub balance: u64,
45
46    /// The timestamp of the genesis block.
47    #[arg(long, value_name = "NUM")]
48    pub timestamp: Option<u64>,
49
50    /// The number of the genesis block.
51    #[arg(long, value_name = "NUM")]
52    pub number: Option<u64>,
53
54    /// BIP39 mnemonic phrase used for generating accounts.
55    /// Cannot be used if `mnemonic_random` or `mnemonic_seed` are used.
56    #[arg(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])]
57    pub mnemonic: Option<String>,
58
59    /// Automatically generates a BIP39 mnemonic phrase, and derives accounts from it.
60    /// Cannot be used with other `mnemonic` options.
61    /// You can specify the number of words you want in the mnemonic.
62    /// [default: 12]
63    #[arg(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))]
64    pub mnemonic_random: Option<usize>,
65
66    /// Generates a BIP39 mnemonic phrase from a given seed
67    /// Cannot be used with other `mnemonic` options.
68    ///
69    /// CAREFUL: This is NOT SAFE and should only be used for testing.
70    /// Never use the private keys generated in production.
71    #[arg(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])]
72    pub mnemonic_seed: Option<u64>,
73
74    /// Sets the derivation path of the child key to be derived.
75    ///
76    /// [default: m/44'/60'/0'/0/]
77    #[arg(long)]
78    pub derivation_path: Option<String>,
79
80    /// The EVM hardfork to use.
81    ///
82    /// Choose the hardfork by name, e.g. `prague`, `cancun`, `shanghai`, `paris`, `london`, etc...
83    /// [default: latest]
84    #[arg(long)]
85    pub hardfork: Option<String>,
86
87    /// Block time in seconds for interval mining.
88    #[arg(short, long, visible_alias = "blockTime", value_name = "SECONDS", value_parser = duration_from_secs_f64)]
89    pub block_time: Option<Duration>,
90
91    /// Slots in an epoch
92    #[arg(long, value_name = "SLOTS_IN_AN_EPOCH", default_value_t = 32)]
93    pub slots_in_an_epoch: u64,
94
95    /// Writes output of `anvil` as json to user-specified file.
96    #[arg(long, value_name = "FILE", value_hint = clap::ValueHint::FilePath)]
97    pub config_out: Option<PathBuf>,
98
99    /// Disable auto and interval mining, and mine on demand instead.
100    #[arg(long, visible_alias = "no-mine", conflicts_with = "block_time")]
101    pub no_mining: bool,
102
103    #[arg(long, requires = "block_time")]
104    pub mixed_mining: bool,
105
106    /// The hosts the server will listen on.
107    #[arg(
108        long,
109        value_name = "IP_ADDR",
110        env = "ANVIL_IP_ADDR",
111        default_value = "127.0.0.1",
112        help_heading = "Server options",
113        value_delimiter = ','
114    )]
115    pub host: Vec<IpAddr>,
116
117    /// How transactions are sorted in the mempool.
118    #[arg(long, default_value = "fees")]
119    pub order: TransactionOrder,
120
121    /// Initialize the genesis block with the given `genesis.json` file.
122    #[arg(long, value_name = "PATH", value_parser= read_genesis_file)]
123    pub init: Option<Genesis>,
124
125    /// This is an alias for both --load-state and --dump-state.
126    ///
127    /// It initializes the chain with the state and block environment stored at the file, if it
128    /// exists, and dumps the chain's state on exit.
129    #[arg(
130        long,
131        value_name = "PATH",
132        value_parser = StateFile::parse,
133        conflicts_with_all = &[
134            "init",
135            "dump_state",
136            "load_state"
137        ]
138    )]
139    pub state: Option<StateFile>,
140
141    /// Interval in seconds at which the state and block environment is to be dumped to disk.
142    ///
143    /// See --state and --dump-state
144    #[arg(short, long, value_name = "SECONDS")]
145    pub state_interval: Option<u64>,
146
147    /// Dump the state and block environment of chain on exit to the given file.
148    ///
149    /// If the value is a directory, the state will be written to `<VALUE>/state.json`.
150    #[arg(long, value_name = "PATH", conflicts_with = "init")]
151    pub dump_state: Option<PathBuf>,
152
153    /// Preserve historical state snapshots when dumping the state.
154    ///
155    /// This will save the in-memory states of the chain at particular block hashes.
156    ///
157    /// These historical states will be loaded into the memory when `--load-state` / `--state`, and
158    /// aids in RPC calls beyond the block at which state was dumped.
159    #[arg(long, conflicts_with = "init", default_value = "false")]
160    pub preserve_historical_states: bool,
161
162    /// Initialize the chain from a previously saved state snapshot.
163    #[arg(
164        long,
165        value_name = "PATH",
166        value_parser = SerializableState::parse,
167        conflicts_with = "init"
168    )]
169    pub load_state: Option<SerializableState>,
170
171    #[arg(long, help = IPC_HELP, value_name = "PATH", visible_alias = "ipcpath")]
172    pub ipc: Option<Option<String>>,
173
174    /// Don't keep full chain history.
175    /// If a number argument is specified, at most this number of states is kept in memory.
176    ///
177    /// If enabled, no state will be persisted on disk, so `max_persisted_states` will be 0.
178    #[arg(long)]
179    pub prune_history: Option<Option<usize>>,
180
181    /// Max number of states to persist on disk.
182    ///
183    /// Note that `prune_history` will overwrite `max_persisted_states` to 0.
184    #[arg(long, conflicts_with = "prune_history")]
185    pub max_persisted_states: Option<usize>,
186
187    /// Number of blocks with transactions to keep in memory.
188    #[arg(long)]
189    pub transaction_block_keeper: Option<usize>,
190
191    /// Maximum number of transactions in a block.
192    #[arg(long)]
193    pub max_transactions: Option<usize>,
194
195    #[command(flatten)]
196    pub evm: AnvilEvmArgs,
197
198    #[command(flatten)]
199    pub server_config: ServerConfig,
200
201    /// Path to the cache directory where persisted states are stored (see
202    /// `--max-persisted-states`).
203    ///
204    /// Note: This does not affect the fork RPC cache location (`storage.json`), which is stored in
205    /// `~/.foundry/cache/rpc/<chain>/<block>/`.
206    #[arg(long, value_name = "PATH")]
207    pub cache_path: Option<PathBuf>,
208}
209
210#[cfg(windows)]
211const IPC_HELP: &str =
212    "Launch an ipc server at the given path or default path = `\\.\\pipe\\anvil.ipc`";
213
214/// The default IPC endpoint
215#[cfg(not(windows))]
216const IPC_HELP: &str = "Launch an ipc server at the given path or default path = `/tmp/anvil.ipc`";
217
218/// Default interval for periodically dumping the state.
219const DEFAULT_DUMP_INTERVAL: Duration = Duration::from_secs(60);
220
221impl NodeArgs {
222    pub fn into_node_config(self) -> eyre::Result<NodeConfig> {
223        let genesis_balance = Unit::ETHER.wei().saturating_mul(U256::from(self.balance));
224        let compute_units_per_second =
225            if self.evm.no_rate_limit { Some(u64::MAX) } else { self.evm.compute_units_per_second };
226
227        let hardfork = match &self.hardfork {
228            Some(hf) => {
229                if self.evm.networks.is_optimism() {
230                    Some(OpHardfork::from_str(hf)?.into())
231                } else {
232                    Some(EthereumHardfork::from_str(hf)?.into())
233                }
234            }
235            None => None,
236        };
237
238        Ok(NodeConfig::default()
239            .with_gas_limit(self.evm.gas_limit)
240            .disable_block_gas_limit(self.evm.disable_block_gas_limit)
241            .enable_tx_gas_limit(self.evm.enable_tx_gas_limit)
242            .with_gas_price(self.evm.gas_price)
243            .with_hardfork(hardfork)
244            .with_blocktime(self.block_time)
245            .with_no_mining(self.no_mining)
246            .with_mixed_mining(self.mixed_mining, self.block_time)
247            .with_account_generator(self.account_generator())?
248            .with_genesis_balance(genesis_balance)
249            .with_genesis_timestamp(self.timestamp)
250            .with_genesis_block_number(self.number)
251            .with_port(self.port)
252            .with_fork_choice(match (self.evm.fork_block_number, self.evm.fork_transaction_hash) {
253                (Some(block), None) => Some(ForkChoice::Block(block)),
254                (None, Some(hash)) => Some(ForkChoice::Transaction(hash)),
255                _ => self
256                    .evm
257                    .fork_url
258                    .as_ref()
259                    .and_then(|f| f.block)
260                    .map(|num| ForkChoice::Block(num as i128)),
261            })
262            .with_fork_headers(self.evm.fork_headers)
263            .with_fork_chain_id(self.evm.fork_chain_id.map(u64::from).map(U256::from))
264            .fork_request_timeout(self.evm.fork_request_timeout.map(Duration::from_millis))
265            .fork_request_retries(self.evm.fork_request_retries)
266            .fork_retry_backoff(self.evm.fork_retry_backoff.map(Duration::from_millis))
267            .fork_compute_units_per_second(compute_units_per_second)
268            .with_eth_rpc_url(self.evm.fork_url.map(|fork| fork.url))
269            .with_base_fee(self.evm.block_base_fee_per_gas)
270            .disable_min_priority_fee(self.evm.disable_min_priority_fee)
271            .with_no_storage_caching(self.evm.no_storage_caching)
272            .with_server_config(self.server_config)
273            .with_host(self.host)
274            .set_silent(shell::is_quiet())
275            .set_config_out(self.config_out)
276            .with_chain_id(self.evm.chain_id)
277            .with_transaction_order(self.order)
278            .with_genesis(self.init)
279            .with_steps_tracing(self.evm.steps_tracing)
280            .with_print_logs(!self.evm.disable_console_log)
281            .with_print_traces(self.evm.print_traces)
282            .with_auto_impersonate(self.evm.auto_impersonate)
283            .with_ipc(self.ipc)
284            .with_code_size_limit(self.evm.code_size_limit)
285            .disable_code_size_limit(self.evm.disable_code_size_limit)
286            .set_pruned_history(self.prune_history)
287            .with_init_state(self.load_state.or_else(|| self.state.and_then(|s| s.state)))
288            .with_transaction_block_keeper(self.transaction_block_keeper)
289            .with_max_transactions(self.max_transactions)
290            .with_max_persisted_states(self.max_persisted_states)
291            .with_networks(self.evm.networks)
292            .with_disable_default_create2_deployer(self.evm.disable_default_create2_deployer)
293            .with_disable_pool_balance_checks(self.evm.disable_pool_balance_checks)
294            .with_slots_in_an_epoch(self.slots_in_an_epoch)
295            .with_memory_limit(self.evm.memory_limit)
296            .with_cache_path(self.cache_path))
297    }
298
299    fn account_generator(&self) -> AccountGenerator {
300        let mut generator = AccountGenerator::new(self.accounts as usize)
301            .phrase(DEFAULT_MNEMONIC)
302            .chain_id(self.evm.chain_id.unwrap_or(CHAIN_ID.into()));
303        if let Some(ref mnemonic) = self.mnemonic {
304            generator = generator.phrase(mnemonic);
305        } else if let Some(count) = self.mnemonic_random {
306            let mut rng = rand_08::thread_rng();
307            let mnemonic = match Mnemonic::<English>::new_with_count(&mut rng, count) {
308                Ok(mnemonic) => mnemonic.to_phrase(),
309                Err(err) => {
310                    warn!(target: "node", ?count, %err, "failed to generate mnemonic, falling back to 12-word random mnemonic");
311                    // Fallback: generate a valid 12-word random mnemonic instead of using
312                    // DEFAULT_MNEMONIC
313                    Mnemonic::<English>::new_with_count(&mut rng, 12)
314                        .expect("valid default word count")
315                        .to_phrase()
316                }
317            };
318            generator = generator.phrase(mnemonic);
319        } else if let Some(seed) = self.mnemonic_seed {
320            let mut seed = StdRng::seed_from_u64(seed);
321            let mnemonic = Mnemonic::<English>::new(&mut seed).to_phrase();
322            generator = generator.phrase(mnemonic);
323        }
324        if let Some(ref derivation) = self.derivation_path {
325            generator = generator.derivation_path(derivation);
326        }
327        generator
328    }
329
330    /// Returns the location where to dump the state to.
331    fn dump_state_path(&self) -> Option<PathBuf> {
332        self.dump_state.as_ref().or_else(|| self.state.as_ref().map(|s| &s.path)).cloned()
333    }
334
335    /// Starts the node
336    ///
337    /// See also [crate::spawn()]
338    pub async fn run(self) -> eyre::Result<()> {
339        let dump_state = self.dump_state_path();
340        let dump_interval =
341            self.state_interval.map(Duration::from_secs).unwrap_or(DEFAULT_DUMP_INTERVAL);
342        let preserve_historical_states = self.preserve_historical_states;
343
344        let (api, mut handle) = crate::try_spawn(self.into_node_config()?).await?;
345
346        // sets the signal handler to gracefully shutdown.
347        let mut fork = api.get_fork();
348        let running = Arc::new(AtomicUsize::new(0));
349
350        // handle for the currently running rt, this must be obtained before setting the crtlc
351        // handler, See [Handle::current]
352        let mut signal = handle.shutdown_signal_mut().take();
353
354        let task_manager = handle.task_manager();
355        let mut on_shutdown = task_manager.on_shutdown();
356
357        let mut state_dumper =
358            PeriodicStateDumper::new(api, dump_state, dump_interval, preserve_historical_states);
359
360        task_manager.spawn(async move {
361            // wait for the SIGTERM signal on unix systems
362            #[cfg(unix)]
363            let mut sigterm = Box::pin(async {
364                if let Ok(mut stream) =
365                    tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
366                {
367                    stream.recv().await;
368                } else {
369                    futures::future::pending::<()>().await;
370                }
371            });
372
373            // On windows, this will never fire.
374            #[cfg(not(unix))]
375            let mut sigterm = Box::pin(futures::future::pending::<()>());
376
377            // await shutdown signal but also periodically flush state
378            tokio::select! {
379                 _ = &mut sigterm => {
380                    trace!("received sigterm signal, shutting down");
381                }
382                _ = &mut on_shutdown => {}
383                _ = &mut state_dumper => {}
384            }
385
386            // shutdown received
387            state_dumper.dump().await;
388
389            // cleaning up and shutting down
390            // this will make sure that the fork RPC cache is flushed if caching is configured
391            if let Some(fork) = fork.take() {
392                trace!("flushing cache on shutdown");
393                fork.database
394                    .read()
395                    .await
396                    .maybe_flush_cache()
397                    .expect("Could not flush cache on fork DB");
398                // cleaning up and shutting down
399                // this will make sure that the fork RPC cache is flushed if caching is configured
400            }
401            std::process::exit(0);
402        });
403
404        ctrlc::set_handler(move || {
405            let prev = running.fetch_add(1, Ordering::SeqCst);
406            if prev == 0 {
407                trace!("received shutdown signal, shutting down");
408                let _ = signal.take();
409            }
410        })
411        .expect("Error setting Ctrl-C handler");
412
413        Ok(handle.await??)
414    }
415}
416
417/// Anvil's EVM related arguments.
418#[derive(Clone, Debug, Parser)]
419#[command(next_help_heading = "EVM options")]
420pub struct AnvilEvmArgs {
421    /// Fetch state over a remote endpoint instead of starting from an empty state.
422    ///
423    /// If you want to fetch state from a specific block number, add a block number like `http://localhost:8545@1400000` or use the `--fork-block-number` argument.
424    #[arg(
425        long,
426        short,
427        visible_alias = "rpc-url",
428        value_name = "URL",
429        help_heading = "Fork config"
430    )]
431    pub fork_url: Option<ForkUrl>,
432
433    /// Headers to use for the rpc client, e.g. "User-Agent: test-agent"
434    ///
435    /// See --fork-url.
436    #[arg(
437        long = "fork-header",
438        value_name = "HEADERS",
439        help_heading = "Fork config",
440        requires = "fork_url"
441    )]
442    pub fork_headers: Vec<String>,
443
444    /// Timeout in ms for requests sent to remote JSON-RPC server in forking mode.
445    ///
446    /// Default value 45000
447    #[arg(id = "timeout", long = "timeout", help_heading = "Fork config", requires = "fork_url")]
448    pub fork_request_timeout: Option<u64>,
449
450    /// Number of retry requests for spurious networks (timed out requests)
451    ///
452    /// Default value 5
453    #[arg(id = "retries", long = "retries", help_heading = "Fork config", requires = "fork_url")]
454    pub fork_request_retries: Option<u32>,
455
456    /// Fetch state from a specific block number over a remote endpoint.
457    ///
458    /// If negative, the given value is subtracted from the `latest` block number.
459    ///
460    /// See --fork-url.
461    #[arg(
462        long,
463        requires = "fork_url",
464        value_name = "BLOCK",
465        help_heading = "Fork config",
466        allow_hyphen_values = true
467    )]
468    pub fork_block_number: Option<i128>,
469
470    /// Fetch state from after a specific transaction hash has been applied over a remote endpoint.
471    ///
472    /// See --fork-url.
473    #[arg(
474        long,
475        requires = "fork_url",
476        value_name = "TRANSACTION",
477        help_heading = "Fork config",
478        conflicts_with = "fork_block_number"
479    )]
480    pub fork_transaction_hash: Option<B256>,
481
482    /// Initial retry backoff on encountering errors.
483    ///
484    /// See --fork-url.
485    #[arg(long, requires = "fork_url", value_name = "BACKOFF", help_heading = "Fork config")]
486    pub fork_retry_backoff: Option<u64>,
487
488    /// Specify chain id to skip fetching it from remote endpoint. This enables offline-start mode.
489    ///
490    /// You still must pass both `--fork-url` and `--fork-block-number`, and already have your
491    /// required state cached on disk, anything missing locally would be fetched from the
492    /// remote.
493    #[arg(
494        long,
495        help_heading = "Fork config",
496        value_name = "CHAIN",
497        requires = "fork_block_number"
498    )]
499    pub fork_chain_id: Option<Chain>,
500
501    /// Sets the number of assumed available compute units per second for this provider
502    ///
503    /// default value: 330
504    ///
505    /// See also --fork-url and <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
506    #[arg(
507        long,
508        requires = "fork_url",
509        alias = "cups",
510        value_name = "CUPS",
511        help_heading = "Fork config"
512    )]
513    pub compute_units_per_second: Option<u64>,
514
515    /// Disables rate limiting for this node's provider.
516    ///
517    /// default value: false
518    ///
519    /// See also --fork-url and <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
520    #[arg(
521        long,
522        requires = "fork_url",
523        value_name = "NO_RATE_LIMITS",
524        help_heading = "Fork config",
525        visible_alias = "no-rpc-rate-limit"
526    )]
527    pub no_rate_limit: bool,
528
529    /// Explicitly disables the use of RPC caching.
530    ///
531    /// All storage slots are read entirely from the endpoint.
532    ///
533    /// This flag overrides the project's configuration file.
534    ///
535    /// See --fork-url.
536    #[arg(long, requires = "fork_url", help_heading = "Fork config")]
537    pub no_storage_caching: bool,
538
539    /// The block gas limit.
540    #[arg(long, alias = "block-gas-limit", help_heading = "Environment config")]
541    pub gas_limit: Option<u64>,
542
543    /// Disable the `call.gas_limit <= block.gas_limit` constraint.
544    #[arg(
545        long,
546        value_name = "DISABLE_GAS_LIMIT",
547        help_heading = "Environment config",
548        alias = "disable-gas-limit",
549        conflicts_with = "gas_limit"
550    )]
551    pub disable_block_gas_limit: bool,
552
553    /// Enable the transaction gas limit check as imposed by EIP-7825 (Osaka hardfork).
554    #[arg(long, visible_alias = "tx-gas-limit", help_heading = "Environment config")]
555    pub enable_tx_gas_limit: bool,
556
557    /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. To
558    /// disable entirely, use `--disable-code-size-limit`. By default, it is 0x6000 (~25kb).
559    #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")]
560    pub code_size_limit: Option<usize>,
561
562    /// Disable EIP-170: Contract code size limit.
563    #[arg(
564        long,
565        value_name = "DISABLE_CODE_SIZE_LIMIT",
566        conflicts_with = "code_size_limit",
567        help_heading = "Environment config"
568    )]
569    pub disable_code_size_limit: bool,
570
571    /// The gas price.
572    #[arg(long, help_heading = "Environment config")]
573    pub gas_price: Option<u128>,
574
575    /// The base fee in a block.
576    #[arg(
577        long,
578        visible_alias = "base-fee",
579        value_name = "FEE",
580        help_heading = "Environment config"
581    )]
582    pub block_base_fee_per_gas: Option<u64>,
583
584    /// Disable the enforcement of a minimum suggested priority fee.
585    #[arg(long, visible_alias = "no-priority-fee", help_heading = "Environment config")]
586    pub disable_min_priority_fee: bool,
587
588    /// The chain ID.
589    #[arg(long, alias = "chain", help_heading = "Environment config")]
590    pub chain_id: Option<Chain>,
591
592    /// Enable steps tracing used for debug calls returning geth-style traces
593    #[arg(long, visible_alias = "tracing")]
594    pub steps_tracing: bool,
595
596    /// Disable printing of `console.log` invocations to stdout.
597    #[arg(long, visible_alias = "no-console-log")]
598    pub disable_console_log: bool,
599
600    /// Enable printing of traces for executed transactions and `eth_call` to stdout.
601    #[arg(long, visible_alias = "enable-trace-printing")]
602    pub print_traces: bool,
603
604    /// Enables automatic impersonation on startup. This allows any transaction sender to be
605    /// simulated as different accounts, which is useful for testing contract behavior.
606    #[arg(long, visible_alias = "auto-unlock")]
607    pub auto_impersonate: bool,
608
609    /// Disable the default create2 deployer
610    #[arg(long, visible_alias = "no-create2")]
611    pub disable_default_create2_deployer: bool,
612
613    /// Disable pool balance checks
614    #[arg(long)]
615    pub disable_pool_balance_checks: bool,
616
617    /// The memory limit per EVM execution in bytes.
618    #[arg(long)]
619    pub memory_limit: Option<u64>,
620
621    #[command(flatten)]
622    pub networks: NetworkConfigs,
623}
624
625/// Resolves an alias passed as fork-url to the matching url defined in the rpc_endpoints section
626/// of the project configuration file.
627/// Does nothing if the fork-url is not a configured alias.
628impl AnvilEvmArgs {
629    pub fn resolve_rpc_alias(&mut self) {
630        if let Some(fork_url) = &self.fork_url
631            && let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil)
632            && let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url)
633        {
634            self.fork_url = Some(ForkUrl { url: url.to_string(), block: fork_url.block });
635        }
636    }
637}
638
639/// Helper type to periodically dump the state of the chain to disk
640struct PeriodicStateDumper {
641    in_progress_dump: Option<Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>>>,
642    api: EthApi,
643    dump_state: Option<PathBuf>,
644    preserve_historical_states: bool,
645    interval: Interval,
646}
647
648impl PeriodicStateDumper {
649    fn new(
650        api: EthApi,
651        dump_state: Option<PathBuf>,
652        interval: Duration,
653        preserve_historical_states: bool,
654    ) -> Self {
655        let dump_state = dump_state.map(|mut dump_state| {
656            if dump_state.is_dir() {
657                dump_state = dump_state.join("state.json");
658            }
659            dump_state
660        });
661
662        // periodically flush the state
663        let interval = tokio::time::interval_at(Instant::now() + interval, interval);
664        Self { in_progress_dump: None, api, dump_state, preserve_historical_states, interval }
665    }
666
667    async fn dump(&self) {
668        if let Some(state) = self.dump_state.clone() {
669            Self::dump_state(self.api.clone(), state, self.preserve_historical_states).await
670        }
671    }
672
673    /// Infallible state dump
674    async fn dump_state(api: EthApi, dump_state: PathBuf, preserve_historical_states: bool) {
675        trace!(path=?dump_state, "Dumping state on shutdown");
676        match api.serialized_state(preserve_historical_states).await {
677            Ok(state) => {
678                if let Err(err) = foundry_common::fs::write_json_file(&dump_state, &state) {
679                    error!(?err, "Failed to dump state");
680                } else {
681                    trace!(path=?dump_state, "Dumped state on shutdown");
682                }
683            }
684            Err(err) => {
685                error!(?err, "Failed to extract state");
686            }
687        }
688    }
689}
690
691// An endless future that periodically dumps the state to disk if configured.
692impl Future for PeriodicStateDumper {
693    type Output = ();
694
695    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
696        let this = self.get_mut();
697        if this.dump_state.is_none() {
698            return Poll::Pending;
699        }
700
701        loop {
702            if let Some(mut flush) = this.in_progress_dump.take() {
703                match flush.poll_unpin(cx) {
704                    Poll::Ready(_) => {
705                        this.interval.reset();
706                    }
707                    Poll::Pending => {
708                        this.in_progress_dump = Some(flush);
709                        return Poll::Pending;
710                    }
711                }
712            }
713
714            if this.interval.poll_tick(cx).is_ready() {
715                let api = this.api.clone();
716                let path = this.dump_state.clone().expect("exists; see above");
717                this.in_progress_dump =
718                    Some(Box::pin(Self::dump_state(api, path, this.preserve_historical_states)));
719            } else {
720                break;
721            }
722        }
723
724        Poll::Pending
725    }
726}
727
728/// Represents the --state flag and where to load from, or dump the state to
729#[derive(Clone, Debug)]
730pub struct StateFile {
731    pub path: PathBuf,
732    pub state: Option<SerializableState>,
733}
734
735impl StateFile {
736    /// This is used as the clap `value_parser` implementation to parse from file but only if it
737    /// exists
738    fn parse(path: &str) -> Result<Self, String> {
739        Self::parse_path(path)
740    }
741
742    /// Parse from file but only if it exists
743    pub fn parse_path(path: impl AsRef<Path>) -> Result<Self, String> {
744        let mut path = path.as_ref().to_path_buf();
745        if path.is_dir() {
746            path = path.join("state.json");
747        }
748        let mut state = Self { path, state: None };
749        if !state.path.exists() {
750            return Ok(state);
751        }
752
753        state.state = Some(SerializableState::load(&state.path).map_err(|err| err.to_string())?);
754
755        Ok(state)
756    }
757}
758
759/// Represents the input URL for a fork with an optional trailing block number:
760/// `http://localhost:8545@1000000`
761#[derive(Clone, Debug, PartialEq, Eq)]
762pub struct ForkUrl {
763    /// The endpoint url
764    pub url: String,
765    /// Optional trailing block
766    pub block: Option<u64>,
767}
768
769impl fmt::Display for ForkUrl {
770    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771        self.url.fmt(f)?;
772        if let Some(block) = self.block {
773            write!(f, "@{block}")?;
774        }
775        Ok(())
776    }
777}
778
779impl FromStr for ForkUrl {
780    type Err = String;
781
782    fn from_str(s: &str) -> Result<Self, Self::Err> {
783        if let Some((url, block)) = s.rsplit_once('@') {
784            if block == "latest" {
785                return Ok(Self { url: url.to_string(), block: None });
786            }
787            // this will prevent false positives for auths `user:password@example.com`
788            if !block.is_empty() && !block.contains(':') && !block.contains('.') {
789                let block: u64 = block
790                    .parse()
791                    .map_err(|_| format!("Failed to parse block number: `{block}`"))?;
792                return Ok(Self { url: url.to_string(), block: Some(block) });
793            }
794        }
795        Ok(Self { url: s.to_string(), block: None })
796    }
797}
798
799/// Clap's value parser for genesis. Loads a genesis.json file.
800fn read_genesis_file(path: &str) -> Result<Genesis, String> {
801    foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string())
802}
803
804fn duration_from_secs_f64(s: &str) -> Result<Duration, String> {
805    let s = s.parse::<f64>().map_err(|e| e.to_string())?;
806    if s == 0.0 {
807        return Err("Duration must be greater than 0".to_string());
808    }
809    Duration::try_from_secs_f64(s).map_err(|e| e.to_string())
810}
811
812#[cfg(test)]
813mod tests {
814    use super::*;
815    use std::{env, net::Ipv4Addr};
816
817    #[test]
818    fn test_parse_fork_url() {
819        let fork: ForkUrl = "http://localhost:8545@1000000".parse().unwrap();
820        assert_eq!(
821            fork,
822            ForkUrl { url: "http://localhost:8545".to_string(), block: Some(1000000) }
823        );
824
825        let fork: ForkUrl = "http://localhost:8545".parse().unwrap();
826        assert_eq!(fork, ForkUrl { url: "http://localhost:8545".to_string(), block: None });
827
828        let fork: ForkUrl = "wss://user:password@example.com/".parse().unwrap();
829        assert_eq!(
830            fork,
831            ForkUrl { url: "wss://user:password@example.com/".to_string(), block: None }
832        );
833
834        let fork: ForkUrl = "wss://user:password@example.com/@latest".parse().unwrap();
835        assert_eq!(
836            fork,
837            ForkUrl { url: "wss://user:password@example.com/".to_string(), block: None }
838        );
839
840        let fork: ForkUrl = "wss://user:password@example.com/@100000".parse().unwrap();
841        assert_eq!(
842            fork,
843            ForkUrl { url: "wss://user:password@example.com/".to_string(), block: Some(100000) }
844        );
845    }
846
847    #[test]
848    fn can_parse_ethereum_hardfork() {
849        let args: NodeArgs = NodeArgs::parse_from(["anvil", "--hardfork", "berlin"]);
850        let config = args.into_node_config().unwrap();
851        assert_eq!(config.hardfork, Some(EthereumHardfork::Berlin.into()));
852    }
853
854    #[test]
855    fn can_parse_optimism_hardfork() {
856        let args: NodeArgs =
857            NodeArgs::parse_from(["anvil", "--optimism", "--hardfork", "Regolith"]);
858        let config = args.into_node_config().unwrap();
859        assert_eq!(config.hardfork, Some(OpHardfork::Regolith.into()));
860    }
861
862    #[test]
863    fn cant_parse_invalid_hardfork() {
864        let args: NodeArgs = NodeArgs::parse_from(["anvil", "--hardfork", "Regolith"]);
865        let config = args.into_node_config();
866        assert!(config.is_err());
867    }
868
869    #[test]
870    fn can_parse_fork_headers() {
871        let args: NodeArgs = NodeArgs::parse_from([
872            "anvil",
873            "--fork-url",
874            "http,://localhost:8545",
875            "--fork-header",
876            "User-Agent: test-agent",
877            "--fork-header",
878            "Referrer: example.com",
879        ]);
880        assert_eq!(args.evm.fork_headers, vec!["User-Agent: test-agent", "Referrer: example.com"]);
881    }
882
883    #[test]
884    fn can_parse_prune_config() {
885        let args: NodeArgs = NodeArgs::parse_from(["anvil", "--prune-history"]);
886        assert!(args.prune_history.is_some());
887
888        let args: NodeArgs = NodeArgs::parse_from(["anvil", "--prune-history", "100"]);
889        assert_eq!(args.prune_history, Some(Some(100)));
890    }
891
892    #[test]
893    fn can_parse_max_persisted_states_config() {
894        let args: NodeArgs = NodeArgs::parse_from(["anvil", "--max-persisted-states", "500"]);
895        assert_eq!(args.max_persisted_states, (Some(500)));
896    }
897
898    #[test]
899    fn can_parse_disable_block_gas_limit() {
900        let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-block-gas-limit"]);
901        assert!(args.evm.disable_block_gas_limit);
902
903        let args =
904            NodeArgs::try_parse_from(["anvil", "--disable-block-gas-limit", "--gas-limit", "100"]);
905        assert!(args.is_err());
906    }
907
908    #[test]
909    fn can_parse_enable_tx_gas_limit() {
910        let args: NodeArgs = NodeArgs::parse_from(["anvil", "--enable-tx-gas-limit"]);
911        assert!(args.evm.enable_tx_gas_limit);
912
913        // Also test the alias
914        let args: NodeArgs = NodeArgs::parse_from(["anvil", "--tx-gas-limit"]);
915        assert!(args.evm.enable_tx_gas_limit);
916    }
917
918    #[test]
919    fn can_parse_disable_code_size_limit() {
920        let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-code-size-limit"]);
921        assert!(args.evm.disable_code_size_limit);
922
923        let args = NodeArgs::try_parse_from([
924            "anvil",
925            "--disable-code-size-limit",
926            "--code-size-limit",
927            "100",
928        ]);
929        // can't be used together
930        assert!(args.is_err());
931    }
932
933    #[test]
934    fn can_parse_host() {
935        let args = NodeArgs::parse_from(["anvil"]);
936        assert_eq!(args.host, vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]);
937
938        let args = NodeArgs::parse_from([
939            "anvil", "--host", "::1", "--host", "1.1.1.1", "--host", "2.2.2.2",
940        ]);
941        assert_eq!(
942            args.host,
943            ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
944        );
945
946        let args = NodeArgs::parse_from(["anvil", "--host", "::1,1.1.1.1,2.2.2.2"]);
947        assert_eq!(
948            args.host,
949            ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
950        );
951
952        unsafe { env::set_var("ANVIL_IP_ADDR", "1.1.1.1") };
953        let args = NodeArgs::parse_from(["anvil"]);
954        assert_eq!(args.host, vec!["1.1.1.1".parse::<IpAddr>().unwrap()]);
955
956        unsafe { env::set_var("ANVIL_IP_ADDR", "::1,1.1.1.1,2.2.2.2") };
957        let args = NodeArgs::parse_from(["anvil"]);
958        assert_eq!(
959            args.host,
960            ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
961        );
962    }
963}