ethers_core/utils/
anvil.rs

1use crate::{
2    types::{Address, Chain},
3    utils::{secret_key_to_address, unused_port},
4};
5use generic_array::GenericArray;
6use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
7use std::{
8    io::{BufRead, BufReader},
9    path::PathBuf,
10    process::{Child, Command},
11    time::{Duration, Instant},
12};
13
14/// How long we will wait for anvil to indicate that it is ready.
15const ANVIL_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
16
17/// An anvil CLI instance. Will close the instance when dropped.
18///
19/// Construct this using [`Anvil`].
20pub struct AnvilInstance {
21    pid: Child,
22    private_keys: Vec<K256SecretKey>,
23    addresses: Vec<Address>,
24    port: u16,
25    chain_id: Option<u64>,
26}
27
28impl AnvilInstance {
29    /// Returns the private keys used to instantiate this instance
30    pub fn keys(&self) -> &[K256SecretKey] {
31        &self.private_keys
32    }
33
34    /// Returns the addresses used to instantiate this instance
35    pub fn addresses(&self) -> &[Address] {
36        &self.addresses
37    }
38
39    /// Returns the port of this instance
40    pub fn port(&self) -> u16 {
41        self.port
42    }
43
44    /// Returns the chain of the anvil instance
45    pub fn chain_id(&self) -> u64 {
46        self.chain_id.unwrap_or_else(|| Chain::AnvilHardhat.into())
47    }
48
49    /// Returns the HTTP endpoint of this instance
50    pub fn endpoint(&self) -> String {
51        format!("http://localhost:{}", self.port)
52    }
53
54    /// Returns the Websocket endpoint of this instance
55    pub fn ws_endpoint(&self) -> String {
56        format!("ws://localhost:{}", self.port)
57    }
58}
59
60impl Drop for AnvilInstance {
61    fn drop(&mut self) {
62        self.pid.kill().expect("could not kill anvil");
63    }
64}
65
66/// Builder for launching `anvil`.
67///
68/// # Panics
69///
70/// If `spawn` is called without `anvil` being available in the user's $PATH
71///
72/// # Example
73///
74/// ```no_run
75/// use ethers_core::utils::Anvil;
76///
77/// let port = 8545u16;
78/// let url = format!("http://localhost:{}", port).to_string();
79///
80/// let anvil = Anvil::new()
81///     .port(port)
82///     .mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
83///     .spawn();
84///
85/// drop(anvil); // this will kill the instance
86/// ```
87#[derive(Debug, Clone, Default)]
88#[must_use = "This Builder struct does nothing unless it is `spawn`ed"]
89pub struct Anvil {
90    program: Option<PathBuf>,
91    port: Option<u16>,
92    block_time: Option<u64>,
93    chain_id: Option<u64>,
94    mnemonic: Option<String>,
95    fork: Option<String>,
96    fork_block_number: Option<u64>,
97    args: Vec<String>,
98    timeout: Option<u64>,
99}
100
101impl Anvil {
102    /// Creates an empty Anvil builder.
103    /// The default port is 8545. The mnemonic is chosen randomly.
104    ///
105    /// # Example
106    ///
107    /// ```
108    /// # use ethers_core::utils::Anvil;
109    /// fn a() {
110    ///  let anvil = Anvil::default().spawn();
111    ///
112    ///  println!("Anvil running at `{}`", anvil.endpoint());
113    /// # }
114    /// ```
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// Creates an Anvil builder which will execute `anvil` at the given path.
120    ///
121    /// # Example
122    ///
123    /// ```
124    /// # use ethers_core::utils::Anvil;
125    /// fn a() {
126    ///  let anvil = Anvil::at("~/.foundry/bin/anvil").spawn();
127    ///
128    ///  println!("Anvil running at `{}`", anvil.endpoint());
129    /// # }
130    /// ```
131    pub fn at(path: impl Into<PathBuf>) -> Self {
132        Self::new().path(path)
133    }
134
135    /// Sets the `path` to the `anvil` cli
136    ///
137    /// By default, it's expected that `anvil` is in `$PATH`, see also
138    /// [`std::process::Command::new()`]
139    pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
140        self.program = Some(path.into());
141        self
142    }
143
144    /// Sets the port which will be used when the `anvil` instance is launched.
145    pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
146        self.port = Some(port.into());
147        self
148    }
149
150    /// Sets the chain_id the `anvil` instance will use.
151    pub fn chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
152        self.chain_id = Some(chain_id.into());
153        self
154    }
155
156    /// Sets the mnemonic which will be used when the `anvil` instance is launched.
157    pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
158        self.mnemonic = Some(mnemonic.into());
159        self
160    }
161
162    /// Sets the block-time in seconds which will be used when the `anvil` instance is launched.
163    pub fn block_time<T: Into<u64>>(mut self, block_time: T) -> Self {
164        self.block_time = Some(block_time.into());
165        self
166    }
167
168    /// Sets the `fork-block-number` which will be used in addition to [`Self::fork`].
169    ///
170    /// **Note:** if set, then this requires `fork` to be set as well
171    pub fn fork_block_number<T: Into<u64>>(mut self, fork_block_number: T) -> Self {
172        self.fork_block_number = Some(fork_block_number.into());
173        self
174    }
175
176    /// Sets the `fork` argument to fork from another currently running Ethereum client
177    /// at a given block. Input should be the HTTP location and port of the other client,
178    /// e.g. `http://localhost:8545`. You can optionally specify the block to fork from
179    /// using an @ sign: `http://localhost:8545@1599200`
180    pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
181        self.fork = Some(fork.into());
182        self
183    }
184
185    /// Adds an argument to pass to the `anvil`.
186    pub fn arg<T: Into<String>>(mut self, arg: T) -> Self {
187        self.args.push(arg.into());
188        self
189    }
190
191    /// Adds multiple arguments to pass to the `anvil`.
192    pub fn args<I, S>(mut self, args: I) -> Self
193    where
194        I: IntoIterator<Item = S>,
195        S: Into<String>,
196    {
197        for arg in args {
198            self = self.arg(arg);
199        }
200        self
201    }
202
203    /// Sets the timeout which will be used when the `anvil` instance is launched.
204    pub fn timeout<T: Into<u64>>(mut self, timeout: T) -> Self {
205        self.timeout = Some(timeout.into());
206        self
207    }
208
209    /// Consumes the builder and spawns `anvil`.
210    ///
211    /// # Panics
212    ///
213    /// If spawning the instance fails at any point.
214    #[track_caller]
215    pub fn spawn(self) -> AnvilInstance {
216        let mut cmd = if let Some(ref prg) = self.program {
217            Command::new(prg)
218        } else {
219            Command::new("anvil")
220        };
221        cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
222        let port = if let Some(port) = self.port { port } else { unused_port() };
223        cmd.arg("-p").arg(port.to_string());
224
225        if let Some(mnemonic) = self.mnemonic {
226            cmd.arg("-m").arg(mnemonic);
227        }
228
229        if let Some(chain_id) = self.chain_id {
230            cmd.arg("--chain-id").arg(chain_id.to_string());
231        }
232
233        if let Some(block_time) = self.block_time {
234            cmd.arg("-b").arg(block_time.to_string());
235        }
236
237        if let Some(fork) = self.fork {
238            cmd.arg("-f").arg(fork);
239        }
240
241        if let Some(fork_block_number) = self.fork_block_number {
242            cmd.arg("--fork-block-number").arg(fork_block_number.to_string());
243        }
244
245        cmd.args(self.args);
246
247        let mut child = cmd.spawn().expect("couldnt start anvil");
248
249        let stdout = child.stdout.take().expect("Unable to get stdout for anvil child process");
250
251        let start = Instant::now();
252        let mut reader = BufReader::new(stdout);
253
254        let mut private_keys = Vec::new();
255        let mut addresses = Vec::new();
256        let mut is_private_key = false;
257        let mut chain_id = None;
258        loop {
259            if start + Duration::from_millis(self.timeout.unwrap_or(ANVIL_STARTUP_TIMEOUT_MILLIS)) <=
260                Instant::now()
261            {
262                panic!("Timed out waiting for anvil to start. Is anvil installed?")
263            }
264
265            let mut line = String::new();
266            reader.read_line(&mut line).expect("Failed to read line from anvil process");
267            if line.contains("Listening on") {
268                break
269            }
270
271            if line.starts_with("Private Keys") {
272                is_private_key = true;
273            }
274
275            if is_private_key && line.starts_with('(') {
276                let key_str = line
277                    .split("0x")
278                    .last()
279                    .unwrap_or_else(|| panic!("could not parse private key: {}", line))
280                    .trim();
281                let key_hex = hex::decode(key_str).expect("could not parse as hex");
282                let key = K256SecretKey::from_bytes(&GenericArray::clone_from_slice(&key_hex))
283                    .expect("did not get private key");
284                addresses.push(secret_key_to_address(&SigningKey::from(&key)));
285                private_keys.push(key);
286            }
287
288            if let Some(start_chain_id) = line.find("Chain ID:") {
289                let rest = &line[start_chain_id + "Chain ID:".len()..];
290                if let Ok(chain) = rest.split_whitespace().next().unwrap_or("").parse::<u64>() {
291                    chain_id = Some(chain);
292                };
293            }
294        }
295
296        AnvilInstance {
297            pid: child,
298            private_keys,
299            addresses,
300            port,
301            chain_id: self.chain_id.or(chain_id),
302        }
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn can_launch_anvil() {
312        let _ = Anvil::new().spawn();
313    }
314
315    #[test]
316    fn can_launch_anvil_with_more_accounts() {
317        let _ = Anvil::new().arg("--accounts").arg("20").spawn();
318    }
319
320    #[test]
321    fn assert_chain_id() {
322        let anvil = Anvil::new().fork("https://rpc.ankr.com/eth").spawn();
323        assert_eq!(anvil.chain_id(), 1);
324    }
325
326    #[test]
327    fn assert_chain_id_without_rpc() {
328        let anvil = Anvil::new().spawn();
329        assert_eq!(anvil.chain_id(), 31337);
330    }
331}