1use crate::config::{AccountGenerator, AnvilNodeConfig, DEFAULT_MNEMONIC, SubstrateNodeConfig};
2use alloy_genesis::Genesis;
3use alloy_primitives::{U256, utils::Unit};
4use alloy_signer_local::coins_bip39::{English, Mnemonic};
5use anvil_server::ServerConfig;
6use clap::Parser;
7use foundry_common::shell;
8use foundry_config::Chain;
9use rand_08::{SeedableRng, rngs::StdRng};
10use std::{net::IpAddr, path::PathBuf, time::Duration};
11
12#[derive(Clone, Debug, Parser)]
13pub struct NodeArgs {
14 #[arg(long, short, default_value = "8545", value_name = "NUM")]
16 pub port: u16,
17
18 #[arg(long, short, default_value = "10", value_name = "NUM")]
20 pub accounts: u64,
21
22 #[arg(long, default_value = "10000", value_name = "NUM")]
24 pub balance: u64,
25
26 #[arg(long, value_name = "NUM")]
28 pub timestamp: Option<u64>,
29
30 #[arg(long, value_name = "NUM")]
32 pub number: Option<u64>,
33
34 #[arg(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])]
37 pub mnemonic: Option<String>,
38
39 #[arg(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))]
44 pub mnemonic_random: Option<usize>,
45
46 #[arg(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])]
52 pub mnemonic_seed: Option<u64>,
53
54 #[arg(long)]
58 pub derivation_path: Option<String>,
59
60 #[arg(short, long, visible_alias = "blockTime", value_name = "SECONDS", value_parser = duration_from_secs_f64)]
62 pub block_time: Option<Duration>,
63
64 #[arg(long, value_name = "FILE", value_hint = clap::ValueHint::FilePath)]
66 pub config_out: Option<PathBuf>,
67
68 #[arg(long, visible_alias = "no-mine", conflicts_with = "block_time")]
70 pub no_mining: bool,
71
72 #[arg(long, visible_alias = "mixed-mining", requires = "block_time")]
73 pub mixed_mining: bool,
74
75 #[arg(
77 long,
78 value_name = "IP_ADDR",
79 env = "ANVIL_IP_ADDR",
80 default_value = "127.0.0.1",
81 help_heading = "Server options",
82 value_delimiter = ','
83 )]
84 pub host: Vec<IpAddr>,
85
86 #[arg(long, value_name = "PATH", value_parser= read_genesis_file)]
88 pub init: Option<Genesis>,
89
90 #[arg(long, help = IPC_HELP, value_name = "PATH", visible_alias = "ipcpath")]
91 pub ipc: Option<Option<String>>,
92
93 #[arg(long, visible_alias = "transaction-block-keeper")]
95 pub revive_rpc_block_limit: Option<usize>,
96
97 #[command(flatten)]
98 pub evm: AnvilEvmArgs,
99
100 #[command(flatten)]
101 pub server_config: ServerConfig,
102}
103
104const IPC_HELP: &str = "Launch an ipc server at the given path or default path = `/tmp/anvil.ipc`";
106
107impl NodeArgs {
108 pub fn into_node_config(self) -> eyre::Result<(AnvilNodeConfig, SubstrateNodeConfig)> {
109 let genesis_balance = Unit::ETHER.wei().saturating_mul(U256::from(self.balance));
110
111 let anvil_config = AnvilNodeConfig::default()
112 .with_blocktime(self.block_time)
113 .with_no_mining(self.no_mining)
114 .with_mixed_mining(self.mixed_mining, self.block_time)
115 .with_account_generator(self.account_generator())?
116 .with_genesis_balance(genesis_balance)
117 .with_genesis_timestamp(self.timestamp)
118 .with_genesis_block_number(self.number)
119 .with_port(self.port)
120 .with_base_fee(self.evm.block_base_fee_per_gas)
121 .with_server_config(self.server_config)
122 .with_host(self.host)
123 .set_silent(shell::is_quiet())
124 .set_config_out(self.config_out)
125 .with_chain_id(self.evm.chain_id)
126 .with_genesis(self.init)
127 .with_auto_impersonate(self.evm.auto_impersonate)
128 .with_ipc(self.ipc)
129 .with_revive_rpc_block_limit(self.revive_rpc_block_limit);
130
131 let substrate_node_config = SubstrateNodeConfig::new(&anvil_config);
132
133 Ok((anvil_config, substrate_node_config))
134 }
135
136 fn account_generator(&self) -> AccountGenerator {
137 let mut rng_gen = AccountGenerator::new(self.accounts as usize).phrase(DEFAULT_MNEMONIC);
138
139 if let Some(ref mnemonic) = self.mnemonic {
140 rng_gen = rng_gen.phrase(mnemonic);
141 } else if let Some(count) = self.mnemonic_random {
142 let mut rng = rand_08::thread_rng();
143 let mnemonic = match Mnemonic::<English>::new_with_count(&mut rng, count) {
144 Ok(mnemonic) => mnemonic.to_phrase(),
145 Err(_) => DEFAULT_MNEMONIC.to_string(),
146 };
147 rng_gen = rng_gen.phrase(mnemonic);
148 } else if let Some(seed) = self.mnemonic_seed {
149 let mut seed = StdRng::seed_from_u64(seed);
150 let mnemonic = Mnemonic::<English>::new(&mut seed).to_phrase();
151 rng_gen = rng_gen.phrase(mnemonic);
152 }
153 if let Some(ref derivation) = self.derivation_path {
154 rng_gen = rng_gen.derivation_path(derivation);
155 }
156 rng_gen
157 }
158}
159
160#[derive(Clone, Debug, Parser)]
162#[command(next_help_heading = "EVM options")]
163pub struct AnvilEvmArgs {
164 #[arg(
166 long,
167 visible_alias = "base-fee",
168 value_name = "FEE",
169 help_heading = "Environment config"
170 )]
171 pub block_base_fee_per_gas: Option<u64>,
172
173 #[arg(long, alias = "chain", help_heading = "Environment config")]
175 pub chain_id: Option<Chain>,
176
177 #[arg(long, visible_alias = "auto-unlock")]
180 pub auto_impersonate: bool,
181}
182
183fn read_genesis_file(path: &str) -> Result<Genesis, String> {
185 foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string())
186}
187
188fn duration_from_secs_f64(s: &str) -> Result<Duration, String> {
189 let s = s.parse::<f64>().map_err(|e| e.to_string())?;
190 if s == 0.0 {
191 return Err("Duration must be greater than 0".to_string());
192 }
193 Duration::try_from_secs_f64(s).map_err(|e| e.to_string())
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use std::{env, net::Ipv4Addr};
200
201 #[test]
202 fn can_parse_host() {
203 let args = NodeArgs::parse_from(["anvil"]);
204 assert_eq!(args.host, vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]);
205
206 let args = NodeArgs::parse_from([
207 "anvil", "--host", "::1", "--host", "1.1.1.1", "--host", "2.2.2.2",
208 ]);
209 assert_eq!(
210 args.host,
211 ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
212 );
213
214 let args = NodeArgs::parse_from(["anvil", "--host", "::1,1.1.1.1,2.2.2.2"]);
215 assert_eq!(
216 args.host,
217 ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
218 );
219
220 unsafe {
221 env::set_var("ANVIL_IP_ADDR", "1.1.1.1");
222 };
223 let args = NodeArgs::parse_from(["anvil"]);
224 assert_eq!(args.host, vec!["1.1.1.1".parse::<IpAddr>().unwrap()]);
225 unsafe {
226 env::set_var("ANVIL_IP_ADDR", "::1,1.1.1.1,2.2.2.2");
227 };
228 let args = NodeArgs::parse_from(["anvil"]);
229 assert_eq!(
230 args.host,
231 ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
232 );
233 }
234}