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 #[arg(long, short, default_value = "8545", value_name = "NUM")]
39 pub port: u16,
40
41 #[arg(long, short, default_value = "10", value_name = "NUM")]
43 pub accounts: u64,
44
45 #[arg(long, default_value = "10000", value_name = "NUM")]
47 pub balance: u64,
48
49 #[arg(long, value_name = "NUM")]
51 pub timestamp: Option<u64>,
52
53 #[arg(long, value_name = "NUM")]
55 pub number: Option<u64>,
56
57 #[arg(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])]
60 pub mnemonic: Option<String>,
61
62 #[arg(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))]
67 pub mnemonic_random: Option<usize>,
68
69 #[arg(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])]
75 pub mnemonic_seed: Option<u64>,
76
77 #[arg(long)]
81 pub derivation_path: Option<String>,
82
83 #[arg(long)]
88 pub hardfork: Option<String>,
89
90 #[arg(short, long, visible_alias = "blockTime", value_name = "SECONDS", value_parser = duration_from_secs_f64)]
92 pub block_time: Option<Duration>,
93
94 #[arg(long, value_name = "SLOTS_IN_AN_EPOCH", default_value_t = 32)]
96 pub slots_in_an_epoch: u64,
97
98 #[arg(long, value_name = "FILE", value_hint = clap::ValueHint::FilePath)]
100 pub config_out: Option<PathBuf>,
101
102 #[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 #[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 #[arg(long, default_value = "fees")]
122 pub order: TransactionOrder,
123
124 #[arg(long, value_name = "PATH", value_parser= read_genesis_file)]
126 pub init: Option<Genesis>,
127
128 #[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 #[arg(short, long, value_name = "SECONDS")]
148 pub state_interval: Option<u64>,
149
150 #[arg(long, value_name = "PATH", conflicts_with = "init")]
154 pub dump_state: Option<PathBuf>,
155
156 #[arg(long, conflicts_with = "init", default_value = "false")]
163 pub preserve_historical_states: bool,
164
165 #[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 #[arg(long)]
182 pub prune_history: Option<Option<usize>>,
183
184 #[arg(long, conflicts_with = "prune_history")]
188 pub max_persisted_states: Option<usize>,
189
190 #[arg(long)]
192 pub transaction_block_keeper: Option<usize>,
193
194 #[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 #[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#[cfg(not(windows))]
219const IPC_HELP: &str = "Launch an ipc server at the given path or default path = `/tmp/anvil.ipc`";
220
221const 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 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 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 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 let mut fork = api.get_fork();
353 let running = Arc::new(AtomicUsize::new(0));
354
355 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 #[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 #[cfg(not(unix))]
380 let mut sigterm = Box::pin(futures::future::pending::<()>());
381
382 tokio::select! {
384 _ = &mut sigterm => {
385 trace!("received sigterm signal, shutting down");
386 }
387 _ = &mut on_shutdown => {}
388 _ = &mut state_dumper => {}
389 }
390
391 state_dumper.dump().await;
393
394 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 }
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#[derive(Clone, Debug, Parser)]
424#[command(next_help_heading = "EVM options")]
425pub struct AnvilEvmArgs {
426 #[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 #[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 #[arg(id = "timeout", long = "timeout", help_heading = "Fork config", requires = "fork_url")]
453 pub fork_request_timeout: Option<u64>,
454
455 #[arg(id = "retries", long = "retries", help_heading = "Fork config", requires = "fork_url")]
459 pub fork_request_retries: Option<u32>,
460
461 #[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 #[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 #[arg(long, requires = "fork_url", value_name = "BACKOFF", help_heading = "Fork config")]
491 pub fork_retry_backoff: Option<u64>,
492
493 #[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 #[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 #[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 #[arg(long, requires = "fork_url", help_heading = "Fork config")]
542 pub no_storage_caching: bool,
543
544 #[arg(long, alias = "block-gas-limit", help_heading = "Environment config")]
546 pub gas_limit: Option<u64>,
547
548 #[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 #[arg(long, visible_alias = "tx-gas-limit", help_heading = "Environment config")]
560 pub enable_tx_gas_limit: bool,
561
562 #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")]
565 pub code_size_limit: Option<usize>,
566
567 #[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 #[arg(long, help_heading = "Environment config")]
578 pub gas_price: Option<u128>,
579
580 #[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 #[arg(long, visible_alias = "no-priority-fee", help_heading = "Environment config")]
591 pub disable_min_priority_fee: bool,
592
593 #[arg(long, alias = "chain", help_heading = "Environment config")]
595 pub chain_id: Option<Chain>,
596
597 #[arg(long, visible_alias = "tracing")]
599 pub steps_tracing: bool,
600
601 #[arg(long, visible_alias = "no-console-log")]
603 pub disable_console_log: bool,
604
605 #[arg(long, visible_alias = "enable-trace-printing")]
607 pub print_traces: bool,
608
609 #[arg(long, visible_alias = "auto-unlock")]
612 pub auto_impersonate: bool,
613
614 #[arg(long, visible_alias = "no-create2")]
616 pub disable_default_create2_deployer: bool,
617
618 #[arg(long)]
620 pub disable_pool_balance_checks: bool,
621
622 #[arg(long)]
624 pub memory_limit: Option<u64>,
625
626 #[command(flatten)]
627 pub networks: NetworkConfigs,
628}
629
630impl 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
644struct 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 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 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
696impl<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#[derive(Clone, Debug)]
735pub struct StateFile {
736 pub path: PathBuf,
737 pub state: Option<SerializableState>,
738}
739
740impl StateFile {
741 fn parse(path: &str) -> Result<Self, String> {
744 Self::parse_path(path)
745 }
746
747 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#[derive(Clone, Debug, PartialEq, Eq)]
767pub struct ForkUrl {
768 pub url: String,
770 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 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
804fn 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 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 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}