1use crate::{
2 config::{ForkChoice, DEFAULT_MNEMONIC},
3 eth::{backend::db::SerializableState, pool::transactions::TransactionOrder, EthApi},
4 hardfork::OptimismHardfork,
5 AccountGenerator, EthereumHardfork, NodeConfig, CHAIN_ID,
6};
7use alloy_genesis::Genesis;
8use alloy_primitives::{utils::Unit, B256, U256};
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 futures::FutureExt;
16use rand::{rngs::StdRng, SeedableRng};
17use std::{
18 future::Future,
19 net::IpAddr,
20 path::{Path, PathBuf},
21 pin::Pin,
22 str::FromStr,
23 sync::{
24 atomic::{AtomicUsize, Ordering},
25 Arc,
26 },
27 task::{Context, Poll},
28 time::Duration,
29};
30use tokio::time::{Instant, Interval};
31
32#[derive(Clone, Debug, Parser)]
33pub struct NodeArgs {
34 #[arg(long, short, default_value = "8545", value_name = "NUM")]
36 pub port: u16,
37
38 #[arg(long, short, default_value = "10", value_name = "NUM")]
40 pub accounts: u64,
41
42 #[arg(long, default_value = "10000", value_name = "NUM")]
44 pub balance: u64,
45
46 #[arg(long, value_name = "NUM")]
48 pub timestamp: Option<u64>,
49
50 #[arg(long, value_name = "NUM")]
52 pub number: Option<u64>,
53
54 #[arg(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])]
57 pub mnemonic: Option<String>,
58
59 #[arg(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))]
64 pub mnemonic_random: Option<usize>,
65
66 #[arg(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])]
72 pub mnemonic_seed: Option<u64>,
73
74 #[arg(long)]
78 pub derivation_path: Option<String>,
79
80 #[arg(long)]
85 pub hardfork: Option<String>,
86
87 #[arg(short, long, visible_alias = "blockTime", value_name = "SECONDS", value_parser = duration_from_secs_f64)]
89 pub block_time: Option<Duration>,
90
91 #[arg(long, value_name = "SLOTS_IN_AN_EPOCH", default_value_t = 32)]
93 pub slots_in_an_epoch: u64,
94
95 #[arg(long, value_name = "FILE", value_hint = clap::ValueHint::FilePath)]
97 pub config_out: Option<PathBuf>,
98
99 #[arg(long, visible_alias = "no-mine", conflicts_with = "block_time")]
101 pub no_mining: bool,
102
103 #[arg(long, visible_alias = "mixed-mining", requires = "block_time")]
104 pub mixed_mining: bool,
105
106 #[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 #[arg(long, default_value = "fees")]
119 pub order: TransactionOrder,
120
121 #[arg(long, value_name = "PATH", value_parser= read_genesis_file)]
123 pub init: Option<Genesis>,
124
125 #[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 #[arg(short, long, value_name = "SECONDS")]
145 pub state_interval: Option<u64>,
146
147 #[arg(long, value_name = "PATH", conflicts_with = "init")]
151 pub dump_state: Option<PathBuf>,
152
153 #[arg(long, conflicts_with = "init", default_value = "false")]
160 pub preserve_historical_states: bool,
161
162 #[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 #[arg(long)]
179 pub prune_history: Option<Option<usize>>,
180
181 #[arg(long, conflicts_with = "prune_history")]
185 pub max_persisted_states: Option<usize>,
186
187 #[arg(long)]
189 pub transaction_block_keeper: Option<usize>,
190
191 #[command(flatten)]
192 pub evm: AnvilEvmArgs,
193
194 #[command(flatten)]
195 pub server_config: ServerConfig,
196
197 #[arg(long, value_name = "PATH")]
199 pub cache_path: Option<PathBuf>,
200}
201
202#[cfg(windows)]
203const IPC_HELP: &str =
204 "Launch an ipc server at the given path or default path = `\\.\\pipe\\anvil.ipc`";
205
206#[cfg(not(windows))]
208const IPC_HELP: &str = "Launch an ipc server at the given path or default path = `/tmp/anvil.ipc`";
209
210const DEFAULT_DUMP_INTERVAL: Duration = Duration::from_secs(60);
212
213impl NodeArgs {
214 pub fn into_node_config(self) -> eyre::Result<NodeConfig> {
215 let genesis_balance = Unit::ETHER.wei().saturating_mul(U256::from(self.balance));
216 let compute_units_per_second =
217 if self.evm.no_rate_limit { Some(u64::MAX) } else { self.evm.compute_units_per_second };
218
219 let hardfork = match &self.hardfork {
220 Some(hf) => {
221 if self.evm.optimism {
222 Some(OptimismHardfork::from_str(hf)?.into())
223 } else {
224 Some(EthereumHardfork::from_str(hf)?.into())
225 }
226 }
227 None => None,
228 };
229
230 Ok(NodeConfig::default()
231 .with_gas_limit(self.evm.gas_limit)
232 .disable_block_gas_limit(self.evm.disable_block_gas_limit)
233 .with_gas_price(self.evm.gas_price)
234 .with_hardfork(hardfork)
235 .with_blocktime(self.block_time)
236 .with_no_mining(self.no_mining)
237 .with_mixed_mining(self.mixed_mining, self.block_time)
238 .with_account_generator(self.account_generator())?
239 .with_genesis_balance(genesis_balance)
240 .with_genesis_timestamp(self.timestamp)
241 .with_genesis_block_number(self.number)
242 .with_port(self.port)
243 .with_fork_choice(match (self.evm.fork_block_number, self.evm.fork_transaction_hash) {
244 (Some(block), None) => Some(ForkChoice::Block(block)),
245 (None, Some(hash)) => Some(ForkChoice::Transaction(hash)),
246 _ => self
247 .evm
248 .fork_url
249 .as_ref()
250 .and_then(|f| f.block)
251 .map(|num| ForkChoice::Block(num as i128)),
252 })
253 .with_fork_headers(self.evm.fork_headers)
254 .with_fork_chain_id(self.evm.fork_chain_id.map(u64::from).map(U256::from))
255 .fork_request_timeout(self.evm.fork_request_timeout.map(Duration::from_millis))
256 .fork_request_retries(self.evm.fork_request_retries)
257 .fork_retry_backoff(self.evm.fork_retry_backoff.map(Duration::from_millis))
258 .fork_compute_units_per_second(compute_units_per_second)
259 .with_eth_rpc_url(self.evm.fork_url.map(|fork| fork.url))
260 .with_base_fee(self.evm.block_base_fee_per_gas)
261 .disable_min_priority_fee(self.evm.disable_min_priority_fee)
262 .with_storage_caching(self.evm.no_storage_caching)
263 .with_server_config(self.server_config)
264 .with_host(self.host)
265 .set_silent(shell::is_quiet())
266 .set_config_out(self.config_out)
267 .with_chain_id(self.evm.chain_id)
268 .with_transaction_order(self.order)
269 .with_genesis(self.init)
270 .with_steps_tracing(self.evm.steps_tracing)
271 .with_print_logs(!self.evm.disable_console_log)
272 .with_print_traces(self.evm.print_traces)
273 .with_auto_impersonate(self.evm.auto_impersonate)
274 .with_ipc(self.ipc)
275 .with_code_size_limit(self.evm.code_size_limit)
276 .disable_code_size_limit(self.evm.disable_code_size_limit)
277 .set_pruned_history(self.prune_history)
278 .with_init_state(self.load_state.or_else(|| self.state.and_then(|s| s.state)))
279 .with_transaction_block_keeper(self.transaction_block_keeper)
280 .with_max_persisted_states(self.max_persisted_states)
281 .with_optimism(self.evm.optimism)
282 .with_odyssey(self.evm.odyssey)
283 .with_disable_default_create2_deployer(self.evm.disable_default_create2_deployer)
284 .with_slots_in_an_epoch(self.slots_in_an_epoch)
285 .with_memory_limit(self.evm.memory_limit)
286 .with_cache_path(self.cache_path))
287 }
288
289 fn account_generator(&self) -> AccountGenerator {
290 let mut gen = AccountGenerator::new(self.accounts as usize)
291 .phrase(DEFAULT_MNEMONIC)
292 .chain_id(self.evm.chain_id.unwrap_or(CHAIN_ID.into()));
293 if let Some(ref mnemonic) = self.mnemonic {
294 gen = gen.phrase(mnemonic);
295 } else if let Some(count) = self.mnemonic_random {
296 let mut rng = rand::thread_rng();
297 let mnemonic = match Mnemonic::<English>::new_with_count(&mut rng, count) {
298 Ok(mnemonic) => mnemonic.to_phrase(),
299 Err(_) => DEFAULT_MNEMONIC.to_string(),
300 };
301 gen = gen.phrase(mnemonic);
302 } else if let Some(seed) = self.mnemonic_seed {
303 let mut seed = StdRng::seed_from_u64(seed);
304 let mnemonic = Mnemonic::<English>::new(&mut seed).to_phrase();
305 gen = gen.phrase(mnemonic);
306 }
307 if let Some(ref derivation) = self.derivation_path {
308 gen = gen.derivation_path(derivation);
309 }
310 gen
311 }
312
313 fn dump_state_path(&self) -> Option<PathBuf> {
315 self.dump_state.as_ref().or_else(|| self.state.as_ref().map(|s| &s.path)).cloned()
316 }
317
318 pub async fn run(self) -> eyre::Result<()> {
322 let dump_state = self.dump_state_path();
323 let dump_interval =
324 self.state_interval.map(Duration::from_secs).unwrap_or(DEFAULT_DUMP_INTERVAL);
325 let preserve_historical_states = self.preserve_historical_states;
326
327 let (api, mut handle) = crate::try_spawn(self.into_node_config()?).await?;
328
329 let mut fork = api.get_fork();
331 let running = Arc::new(AtomicUsize::new(0));
332
333 let mut signal = handle.shutdown_signal_mut().take();
336
337 let task_manager = handle.task_manager();
338 let mut on_shutdown = task_manager.on_shutdown();
339
340 let mut state_dumper =
341 PeriodicStateDumper::new(api, dump_state, dump_interval, preserve_historical_states);
342
343 task_manager.spawn(async move {
344 #[cfg(unix)]
346 let mut sigterm = Box::pin(async {
347 if let Ok(mut stream) =
348 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
349 {
350 stream.recv().await;
351 } else {
352 futures::future::pending::<()>().await;
353 }
354 });
355
356 #[cfg(not(unix))]
358 let mut sigterm = Box::pin(futures::future::pending::<()>());
359
360 tokio::select! {
362 _ = &mut sigterm => {
363 trace!("received sigterm signal, shutting down");
364 }
365 _ = &mut on_shutdown => {}
366 _ = &mut state_dumper => {}
367 }
368
369 state_dumper.dump().await;
371
372 if let Some(fork) = fork.take() {
375 trace!("flushing cache on shutdown");
376 fork.database
377 .read()
378 .await
379 .maybe_flush_cache()
380 .expect("Could not flush cache on fork DB");
381 }
384 std::process::exit(0);
385 });
386
387 ctrlc::set_handler(move || {
388 let prev = running.fetch_add(1, Ordering::SeqCst);
389 if prev == 0 {
390 trace!("received shutdown signal, shutting down");
391 let _ = signal.take();
392 }
393 })
394 .expect("Error setting Ctrl-C handler");
395
396 Ok(handle.await??)
397 }
398}
399
400#[derive(Clone, Debug, Parser)]
402#[command(next_help_heading = "EVM options")]
403pub struct AnvilEvmArgs {
404 #[arg(
408 long,
409 short,
410 visible_alias = "rpc-url",
411 value_name = "URL",
412 help_heading = "Fork config"
413 )]
414 pub fork_url: Option<ForkUrl>,
415
416 #[arg(
420 long = "fork-header",
421 value_name = "HEADERS",
422 help_heading = "Fork config",
423 requires = "fork_url"
424 )]
425 pub fork_headers: Vec<String>,
426
427 #[arg(id = "timeout", long = "timeout", help_heading = "Fork config", requires = "fork_url")]
431 pub fork_request_timeout: Option<u64>,
432
433 #[arg(id = "retries", long = "retries", help_heading = "Fork config", requires = "fork_url")]
437 pub fork_request_retries: Option<u32>,
438
439 #[arg(
445 long,
446 requires = "fork_url",
447 value_name = "BLOCK",
448 help_heading = "Fork config",
449 allow_hyphen_values = true
450 )]
451 pub fork_block_number: Option<i128>,
452
453 #[arg(
457 long,
458 requires = "fork_url",
459 value_name = "TRANSACTION",
460 help_heading = "Fork config",
461 conflicts_with = "fork_block_number"
462 )]
463 pub fork_transaction_hash: Option<B256>,
464
465 #[arg(long, requires = "fork_url", value_name = "BACKOFF", help_heading = "Fork config")]
469 pub fork_retry_backoff: Option<u64>,
470
471 #[arg(
477 long,
478 help_heading = "Fork config",
479 value_name = "CHAIN",
480 requires = "fork_block_number"
481 )]
482 pub fork_chain_id: Option<Chain>,
483
484 #[arg(
490 long,
491 requires = "fork_url",
492 alias = "cups",
493 value_name = "CUPS",
494 help_heading = "Fork config"
495 )]
496 pub compute_units_per_second: Option<u64>,
497
498 #[arg(
504 long,
505 requires = "fork_url",
506 value_name = "NO_RATE_LIMITS",
507 help_heading = "Fork config",
508 visible_alias = "no-rpc-rate-limit"
509 )]
510 pub no_rate_limit: bool,
511
512 #[arg(long, requires = "fork_url", help_heading = "Fork config")]
520 pub no_storage_caching: bool,
521
522 #[arg(long, alias = "block-gas-limit", help_heading = "Environment config")]
524 pub gas_limit: Option<u128>,
525
526 #[arg(
528 long,
529 value_name = "DISABLE_GAS_LIMIT",
530 help_heading = "Environment config",
531 alias = "disable-gas-limit",
532 conflicts_with = "gas_limit"
533 )]
534 pub disable_block_gas_limit: bool,
535
536 #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")]
539 pub code_size_limit: Option<usize>,
540
541 #[arg(
543 long,
544 value_name = "DISABLE_CODE_SIZE_LIMIT",
545 conflicts_with = "code_size_limit",
546 help_heading = "Environment config"
547 )]
548 pub disable_code_size_limit: bool,
549
550 #[arg(long, help_heading = "Environment config")]
552 pub gas_price: Option<u128>,
553
554 #[arg(
556 long,
557 visible_alias = "base-fee",
558 value_name = "FEE",
559 help_heading = "Environment config"
560 )]
561 pub block_base_fee_per_gas: Option<u64>,
562
563 #[arg(long, visible_alias = "no-priority-fee", help_heading = "Environment config")]
565 pub disable_min_priority_fee: bool,
566
567 #[arg(long, alias = "chain", help_heading = "Environment config")]
569 pub chain_id: Option<Chain>,
570
571 #[arg(long, visible_alias = "tracing")]
573 pub steps_tracing: bool,
574
575 #[arg(long, visible_alias = "no-console-log")]
577 pub disable_console_log: bool,
578
579 #[arg(long, visible_alias = "enable-trace-printing")]
581 pub print_traces: bool,
582
583 #[arg(long, visible_alias = "auto-unlock")]
586 pub auto_impersonate: bool,
587
588 #[arg(long, visible_alias = "optimism")]
590 pub optimism: bool,
591
592 #[arg(long, visible_alias = "no-create2")]
594 pub disable_default_create2_deployer: bool,
595
596 #[arg(long)]
598 pub memory_limit: Option<u64>,
599
600 #[arg(long, alias = "alphanet")]
602 pub odyssey: bool,
603}
604
605impl AnvilEvmArgs {
609 pub fn resolve_rpc_alias(&mut self) {
610 if let Some(fork_url) = &self.fork_url {
611 if let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) {
612 if let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) {
613 self.fork_url = Some(ForkUrl { url: url.to_string(), block: fork_url.block });
614 }
615 }
616 }
617 }
618}
619
620struct PeriodicStateDumper {
622 in_progress_dump: Option<Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>>>,
623 api: EthApi,
624 dump_state: Option<PathBuf>,
625 preserve_historical_states: bool,
626 interval: Interval,
627}
628
629impl PeriodicStateDumper {
630 fn new(
631 api: EthApi,
632 dump_state: Option<PathBuf>,
633 interval: Duration,
634 preserve_historical_states: bool,
635 ) -> Self {
636 let dump_state = dump_state.map(|mut dump_state| {
637 if dump_state.is_dir() {
638 dump_state = dump_state.join("state.json");
639 }
640 dump_state
641 });
642
643 let interval = tokio::time::interval_at(Instant::now() + interval, interval);
645 Self { in_progress_dump: None, api, dump_state, preserve_historical_states, interval }
646 }
647
648 async fn dump(&self) {
649 if let Some(state) = self.dump_state.clone() {
650 Self::dump_state(self.api.clone(), state, self.preserve_historical_states).await
651 }
652 }
653
654 async fn dump_state(api: EthApi, dump_state: PathBuf, preserve_historical_states: bool) {
656 trace!(path=?dump_state, "Dumping state on shutdown");
657 match api.serialized_state(preserve_historical_states).await {
658 Ok(state) => {
659 if let Err(err) = foundry_common::fs::write_json_file(&dump_state, &state) {
660 error!(?err, "Failed to dump state");
661 } else {
662 trace!(path=?dump_state, "Dumped state on shutdown");
663 }
664 }
665 Err(err) => {
666 error!(?err, "Failed to extract state");
667 }
668 }
669 }
670}
671
672impl Future for PeriodicStateDumper {
674 type Output = ();
675
676 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
677 let this = self.get_mut();
678 if this.dump_state.is_none() {
679 return Poll::Pending
680 }
681
682 loop {
683 if let Some(mut flush) = this.in_progress_dump.take() {
684 match flush.poll_unpin(cx) {
685 Poll::Ready(_) => {
686 this.interval.reset();
687 }
688 Poll::Pending => {
689 this.in_progress_dump = Some(flush);
690 return Poll::Pending
691 }
692 }
693 }
694
695 if this.interval.poll_tick(cx).is_ready() {
696 let api = this.api.clone();
697 let path = this.dump_state.clone().expect("exists; see above");
698 this.in_progress_dump =
699 Some(Box::pin(Self::dump_state(api, path, this.preserve_historical_states)));
700 } else {
701 break
702 }
703 }
704
705 Poll::Pending
706 }
707}
708
709#[derive(Clone, Debug)]
711pub struct StateFile {
712 pub path: PathBuf,
713 pub state: Option<SerializableState>,
714}
715
716impl StateFile {
717 fn parse(path: &str) -> Result<Self, String> {
720 Self::parse_path(path)
721 }
722
723 pub fn parse_path(path: impl AsRef<Path>) -> Result<Self, String> {
725 let mut path = path.as_ref().to_path_buf();
726 if path.is_dir() {
727 path = path.join("state.json");
728 }
729 let mut state = Self { path, state: None };
730 if !state.path.exists() {
731 return Ok(state)
732 }
733
734 state.state = Some(SerializableState::load(&state.path).map_err(|err| err.to_string())?);
735
736 Ok(state)
737 }
738}
739
740#[derive(Clone, Debug, PartialEq, Eq)]
743pub struct ForkUrl {
744 pub url: String,
746 pub block: Option<u64>,
748}
749
750impl fmt::Display for ForkUrl {
751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752 self.url.fmt(f)?;
753 if let Some(block) = self.block {
754 write!(f, "@{block}")?;
755 }
756 Ok(())
757 }
758}
759
760impl FromStr for ForkUrl {
761 type Err = String;
762
763 fn from_str(s: &str) -> Result<Self, Self::Err> {
764 if let Some((url, block)) = s.rsplit_once('@') {
765 if block == "latest" {
766 return Ok(Self { url: url.to_string(), block: None })
767 }
768 if !block.is_empty() && !block.contains(':') && !block.contains('.') {
770 let block: u64 = block
771 .parse()
772 .map_err(|_| format!("Failed to parse block number: `{block}`"))?;
773 return Ok(Self { url: url.to_string(), block: Some(block) })
774 }
775 }
776 Ok(Self { url: s.to_string(), block: None })
777 }
778}
779
780fn read_genesis_file(path: &str) -> Result<Genesis, String> {
782 foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string())
783}
784
785fn duration_from_secs_f64(s: &str) -> Result<Duration, String> {
786 let s = s.parse::<f64>().map_err(|e| e.to_string())?;
787 if s == 0.0 {
788 return Err("Duration must be greater than 0".to_string());
789 }
790 Duration::try_from_secs_f64(s).map_err(|e| e.to_string())
791}
792
793#[cfg(test)]
794mod tests {
795 use crate::EthereumHardfork;
796
797 use super::*;
798 use std::{env, net::Ipv4Addr};
799
800 #[test]
801 fn test_parse_fork_url() {
802 let fork: ForkUrl = "http://localhost:8545@1000000".parse().unwrap();
803 assert_eq!(
804 fork,
805 ForkUrl { url: "http://localhost:8545".to_string(), block: Some(1000000) }
806 );
807
808 let fork: ForkUrl = "http://localhost:8545".parse().unwrap();
809 assert_eq!(fork, ForkUrl { url: "http://localhost:8545".to_string(), block: None });
810
811 let fork: ForkUrl = "wss://user:password@example.com/".parse().unwrap();
812 assert_eq!(
813 fork,
814 ForkUrl { url: "wss://user:password@example.com/".to_string(), block: None }
815 );
816
817 let fork: ForkUrl = "wss://user:password@example.com/@latest".parse().unwrap();
818 assert_eq!(
819 fork,
820 ForkUrl { url: "wss://user:password@example.com/".to_string(), block: None }
821 );
822
823 let fork: ForkUrl = "wss://user:password@example.com/@100000".parse().unwrap();
824 assert_eq!(
825 fork,
826 ForkUrl { url: "wss://user:password@example.com/".to_string(), block: Some(100000) }
827 );
828 }
829
830 #[test]
831 fn can_parse_ethereum_hardfork() {
832 let args: NodeArgs = NodeArgs::parse_from(["anvil", "--hardfork", "berlin"]);
833 let config = args.into_node_config().unwrap();
834 assert_eq!(config.hardfork, Some(EthereumHardfork::Berlin.into()));
835 }
836
837 #[test]
838 fn can_parse_optimism_hardfork() {
839 let args: NodeArgs =
840 NodeArgs::parse_from(["anvil", "--optimism", "--hardfork", "Regolith"]);
841 let config = args.into_node_config().unwrap();
842 assert_eq!(config.hardfork, Some(OptimismHardfork::Regolith.into()));
843 }
844
845 #[test]
846 fn cant_parse_invalid_hardfork() {
847 let args: NodeArgs = NodeArgs::parse_from(["anvil", "--hardfork", "Regolith"]);
848 let config = args.into_node_config();
849 assert!(config.is_err());
850 }
851
852 #[test]
853 fn can_parse_fork_headers() {
854 let args: NodeArgs = NodeArgs::parse_from([
855 "anvil",
856 "--fork-url",
857 "http,://localhost:8545",
858 "--fork-header",
859 "User-Agent: test-agent",
860 "--fork-header",
861 "Referrer: example.com",
862 ]);
863 assert_eq!(args.evm.fork_headers, vec!["User-Agent: test-agent", "Referrer: example.com"]);
864 }
865
866 #[test]
867 fn can_parse_prune_config() {
868 let args: NodeArgs = NodeArgs::parse_from(["anvil", "--prune-history"]);
869 assert!(args.prune_history.is_some());
870
871 let args: NodeArgs = NodeArgs::parse_from(["anvil", "--prune-history", "100"]);
872 assert_eq!(args.prune_history, Some(Some(100)));
873 }
874
875 #[test]
876 fn can_parse_max_persisted_states_config() {
877 let args: NodeArgs = NodeArgs::parse_from(["anvil", "--max-persisted-states", "500"]);
878 assert_eq!(args.max_persisted_states, (Some(500)));
879 }
880
881 #[test]
882 fn can_parse_disable_block_gas_limit() {
883 let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-block-gas-limit"]);
884 assert!(args.evm.disable_block_gas_limit);
885
886 let args =
887 NodeArgs::try_parse_from(["anvil", "--disable-block-gas-limit", "--gas-limit", "100"]);
888 assert!(args.is_err());
889 }
890
891 #[test]
892 fn can_parse_disable_code_size_limit() {
893 let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-code-size-limit"]);
894 assert!(args.evm.disable_code_size_limit);
895
896 let args = NodeArgs::try_parse_from([
897 "anvil",
898 "--disable-code-size-limit",
899 "--code-size-limit",
900 "100",
901 ]);
902 assert!(args.is_err());
904 }
905
906 #[test]
907 fn can_parse_host() {
908 let args = NodeArgs::parse_from(["anvil"]);
909 assert_eq!(args.host, vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]);
910
911 let args = NodeArgs::parse_from([
912 "anvil", "--host", "::1", "--host", "1.1.1.1", "--host", "2.2.2.2",
913 ]);
914 assert_eq!(
915 args.host,
916 ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
917 );
918
919 let args = NodeArgs::parse_from(["anvil", "--host", "::1,1.1.1.1,2.2.2.2"]);
920 assert_eq!(
921 args.host,
922 ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
923 );
924
925 env::set_var("ANVIL_IP_ADDR", "1.1.1.1");
926 let args = NodeArgs::parse_from(["anvil"]);
927 assert_eq!(args.host, vec!["1.1.1.1".parse::<IpAddr>().unwrap()]);
928
929 env::set_var("ANVIL_IP_ADDR", "::1,1.1.1.1,2.2.2.2");
930 let args = NodeArgs::parse_from(["anvil"]);
931 assert_eq!(
932 args.host,
933 ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
934 );
935 }
936}