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