Skip to main content

anvil_polkadot/
cmd.rs

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    /// Port number to listen on.
15    #[arg(long, short, default_value = "8545", value_name = "NUM")]
16    pub port: u16,
17
18    /// Number of dev accounts to generate and configure.
19    #[arg(long, short, default_value = "10", value_name = "NUM")]
20    pub accounts: u64,
21
22    /// The balance of every dev account in Ether.
23    #[arg(long, default_value = "10000", value_name = "NUM")]
24    pub balance: u64,
25
26    /// The timestamp of the genesis block.
27    #[arg(long, value_name = "NUM")]
28    pub timestamp: Option<u64>,
29
30    /// The number of the genesis block.
31    #[arg(long, value_name = "NUM")]
32    pub number: Option<u64>,
33
34    /// BIP39 mnemonic phrase used for generating accounts.
35    /// Cannot be used if `mnemonic_random` or `mnemonic_seed` are used.
36    #[arg(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])]
37    pub mnemonic: Option<String>,
38
39    /// Automatically generates a BIP39 mnemonic phrase, and derives accounts from it.
40    /// Cannot be used with other `mnemonic` options.
41    /// You can specify the number of words you want in the mnemonic.
42    /// [default: 12]
43    #[arg(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))]
44    pub mnemonic_random: Option<usize>,
45
46    /// Generates a BIP39 mnemonic phrase from a given seed
47    /// Cannot be used with other `mnemonic` options.
48    ///
49    /// CAREFUL: This is NOT SAFE and should only be used for testing.
50    /// Never use the private keys generated in production.
51    #[arg(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])]
52    pub mnemonic_seed: Option<u64>,
53
54    /// Sets the derivation path of the child key to be derived.
55    ///
56    /// [default: m/44'/60'/0'/0/]
57    #[arg(long)]
58    pub derivation_path: Option<String>,
59
60    /// Block time in seconds for interval mining.
61    #[arg(short, long, visible_alias = "blockTime", value_name = "SECONDS", value_parser = duration_from_secs_f64)]
62    pub block_time: Option<Duration>,
63
64    /// Writes output of `anvil` as json to user-specified file.
65    #[arg(long, value_name = "FILE", value_hint = clap::ValueHint::FilePath)]
66    pub config_out: Option<PathBuf>,
67
68    /// Disable auto and interval mining, and mine on demand instead.
69    #[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    /// The hosts the server will listen on.
76    #[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    /// Initialize the genesis block with the given `genesis.json` file.
87    #[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    /// Max number of blocks to keep in memory for the eth revive rpc
94    #[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
104/// The default IPC endpoint
105const 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/// Anvil's EVM related arguments.
161#[derive(Clone, Debug, Parser)]
162#[command(next_help_heading = "EVM options")]
163pub struct AnvilEvmArgs {
164    /// The base fee in a block.
165    #[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    /// The chain ID.
174    #[arg(long, alias = "chain", help_heading = "Environment config")]
175    pub chain_id: Option<Chain>,
176
177    /// Enables automatic impersonation on startup. This allows any transaction sender to be
178    /// simulated as different accounts, which is useful for testing contract behavior.
179    #[arg(long, visible_alias = "auto-unlock")]
180    pub auto_impersonate: bool,
181}
182
183/// Clap's value parser for genesis. Loads a genesis.json file.
184fn 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}