alloy_node_bindings/nodes/
anvil.rs

1//! Utilities for launching an Anvil instance.
2
3use crate::NodeError;
4use alloy_hardforks::EthereumHardfork;
5use alloy_network::EthereumWallet;
6use alloy_primitives::{hex, Address, ChainId};
7use alloy_signer::Signer;
8use alloy_signer_local::LocalSigner;
9use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
10use std::{
11    ffi::OsString,
12    io::{BufRead, BufReader},
13    net::SocketAddr,
14    path::PathBuf,
15    process::{Child, Command},
16    str::FromStr,
17    time::{Duration, Instant},
18};
19use url::Url;
20
21/// anvil's default ipc path
22pub const DEFAULT_IPC_ENDPOINT: &str =
23    if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
24
25/// How long we will wait for anvil to indicate that it is ready.
26const ANVIL_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
27
28/// An anvil CLI instance. Will close the instance when dropped.
29///
30/// Construct this using [`Anvil`].
31#[derive(Debug)]
32pub struct AnvilInstance {
33    child: Child,
34    private_keys: Vec<K256SecretKey>,
35    addresses: Vec<Address>,
36    wallet: Option<EthereumWallet>,
37    ipc_path: Option<String>,
38    port: u16,
39    chain_id: Option<ChainId>,
40}
41
42impl AnvilInstance {
43    /// Returns a reference to the child process.
44    pub const fn child(&self) -> &Child {
45        &self.child
46    }
47
48    /// Returns a mutable reference to the child process.
49    pub const fn child_mut(&mut self) -> &mut Child {
50        &mut self.child
51    }
52
53    /// Returns the private keys used to instantiate this instance
54    pub fn keys(&self) -> &[K256SecretKey] {
55        &self.private_keys
56    }
57
58    /// Convenience function that returns the first key.
59    ///
60    /// # Panics
61    ///
62    /// If this instance does not contain any keys
63    #[track_caller]
64    pub fn first_key(&self) -> &K256SecretKey {
65        self.private_keys.first().unwrap()
66    }
67
68    /// Returns the private key for the given index.
69    pub fn nth_key(&self, idx: usize) -> Option<&K256SecretKey> {
70        self.private_keys.get(idx)
71    }
72
73    /// Returns the addresses used to instantiate this instance
74    pub fn addresses(&self) -> &[Address] {
75        &self.addresses
76    }
77
78    /// Returns the port of this instance
79    pub const fn port(&self) -> u16 {
80        self.port
81    }
82
83    /// Returns the chain of the anvil instance
84    pub fn chain_id(&self) -> ChainId {
85        const ANVIL_HARDHAT_CHAIN_ID: ChainId = 31_337;
86        self.chain_id.unwrap_or(ANVIL_HARDHAT_CHAIN_ID)
87    }
88
89    /// Returns the HTTP endpoint of this instance
90    #[doc(alias = "http_endpoint")]
91    pub fn endpoint(&self) -> String {
92        format!("http://localhost:{}", self.port)
93    }
94
95    /// Returns the Websocket endpoint of this instance
96    pub fn ws_endpoint(&self) -> String {
97        format!("ws://localhost:{}", self.port)
98    }
99
100    /// Returns the IPC path
101    pub fn ipc_path(&self) -> &str {
102        self.ipc_path.as_deref().unwrap_or(DEFAULT_IPC_ENDPOINT)
103    }
104
105    /// Returns the HTTP endpoint url of this instance
106    #[doc(alias = "http_endpoint_url")]
107    pub fn endpoint_url(&self) -> Url {
108        Url::parse(&self.endpoint()).unwrap()
109    }
110
111    /// Returns the Websocket endpoint url of this instance
112    pub fn ws_endpoint_url(&self) -> Url {
113        Url::parse(&self.ws_endpoint()).unwrap()
114    }
115
116    /// Returns the [`EthereumWallet`] of this instance generated from anvil dev accounts.
117    pub fn wallet(&self) -> Option<EthereumWallet> {
118        self.wallet.clone()
119    }
120}
121
122impl Drop for AnvilInstance {
123    fn drop(&mut self) {
124        #[cfg(unix)]
125        {
126            // anvil has settings for dumping thing the state,cache on SIGTERM, so we try to kill it
127            // with sigterm
128            if let Ok(out) =
129                Command::new("kill").arg("-SIGTERM").arg(self.child.id().to_string()).output()
130            {
131                if out.status.success() {
132                    return;
133                }
134            }
135        }
136        if let Err(err) = self.child.kill() {
137            eprintln!("alloy-node-bindings: failed to kill anvil process: {}", err);
138        }
139    }
140}
141
142/// Builder for launching `anvil`.
143///
144/// # Panics
145///
146/// If `spawn` is called without `anvil` being available in the user's $PATH
147///
148/// # Example
149///
150/// ```no_run
151/// use alloy_node_bindings::Anvil;
152///
153/// let port = 8545u16;
154/// let url = format!("http://localhost:{}", port).to_string();
155///
156/// let anvil = Anvil::new()
157///     .port(port)
158///     .mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
159///     .spawn();
160///
161/// drop(anvil); // this will kill the instance
162/// ```
163#[derive(Clone, Debug, Default)]
164#[must_use = "This Builder struct does nothing unless it is `spawn`ed"]
165pub struct Anvil {
166    program: Option<PathBuf>,
167    port: Option<u16>,
168    // If the block_time is an integer, f64::to_string() will output without a decimal point
169    // which allows this to be backwards compatible.
170    block_time: Option<f64>,
171    chain_id: Option<ChainId>,
172    mnemonic: Option<String>,
173    ipc_path: Option<String>,
174    fork: Option<String>,
175    fork_block_number: Option<u64>,
176    args: Vec<OsString>,
177    envs: Vec<(OsString, OsString)>,
178    timeout: Option<u64>,
179    keep_stdout: bool,
180}
181
182impl Anvil {
183    /// Creates an empty Anvil builder.
184    /// The default port and the mnemonic are chosen randomly.
185    ///
186    /// # Example
187    ///
188    /// ```
189    /// # use alloy_node_bindings::Anvil;
190    /// fn a() {
191    ///  let anvil = Anvil::default().spawn();
192    ///
193    ///  println!("Anvil running at `{}`", anvil.endpoint());
194    /// # }
195    /// ```
196    pub fn new() -> Self {
197        Self::default()
198    }
199
200    /// Creates an Anvil builder which will execute `anvil` at the given path.
201    ///
202    /// # Example
203    ///
204    /// ```
205    /// # use alloy_node_bindings::Anvil;
206    /// fn a() {
207    ///  let anvil = Anvil::at("~/.foundry/bin/anvil").spawn();
208    ///
209    ///  println!("Anvil running at `{}`", anvil.endpoint());
210    /// # }
211    /// ```
212    pub fn at(path: impl Into<PathBuf>) -> Self {
213        Self::new().path(path)
214    }
215
216    /// Sets the `path` to the `anvil` cli
217    ///
218    /// By default, it's expected that `anvil` is in `$PATH`, see also
219    /// [`std::process::Command::new()`]
220    pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
221        self.program = Some(path.into());
222        self
223    }
224
225    /// Sets the port which will be used when the `anvil` instance is launched.
226    pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
227        self.port = Some(port.into());
228        self
229    }
230
231    /// Sets the path for the ipc server
232    pub fn ipc_path(mut self, path: impl Into<String>) -> Self {
233        self.ipc_path = Some(path.into());
234        self
235    }
236
237    /// Sets the chain_id the `anvil` instance will use.
238    ///
239    /// If not set, the instance defaults to chain id `31337`.
240    pub const fn chain_id(mut self, chain_id: u64) -> Self {
241        self.chain_id = Some(chain_id);
242        self
243    }
244
245    /// Sets the mnemonic which will be used when the `anvil` instance is launched.
246    pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
247        self.mnemonic = Some(mnemonic.into());
248        self
249    }
250
251    /// Sets the block-time in seconds which will be used when the `anvil` instance is launched.
252    pub const fn block_time(mut self, block_time: u64) -> Self {
253        self.block_time = Some(block_time as f64);
254        self
255    }
256
257    /// Sets the block-time in sub-seconds which will be used when the `anvil` instance is launched.
258    /// Older versions of `anvil` do not support sub-second block times.
259    pub const fn block_time_f64(mut self, block_time: f64) -> Self {
260        self.block_time = Some(block_time);
261        self
262    }
263
264    /// Sets the `fork-block-number` which will be used in addition to [`Self::fork`].
265    ///
266    /// **Note:** if set, then this requires `fork` to be set as well
267    pub const fn fork_block_number(mut self, fork_block_number: u64) -> Self {
268        self.fork_block_number = Some(fork_block_number);
269        self
270    }
271
272    /// Sets the `fork` argument to fork from another currently running Ethereum client
273    /// at a given block. Input should be the HTTP location and port of the other client,
274    /// e.g. `http://localhost:8545`. You can optionally specify the block to fork from
275    /// using an @ sign: `http://localhost:8545@1599200`
276    pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
277        self.fork = Some(fork.into());
278        self
279    }
280
281    /// Select the [`EthereumHardfork`] to start anvil with.
282    pub fn hardfork(mut self, hardfork: EthereumHardfork) -> Self {
283        self = self.args(["--hardfork", hardfork.to_string().as_str()]);
284        self
285    }
286
287    /// Set the [`EthereumHardfork`] to [`EthereumHardfork::Paris`].
288    pub fn paris(mut self) -> Self {
289        self = self.hardfork(EthereumHardfork::Paris);
290        self
291    }
292
293    /// Set the [`EthereumHardfork`] to [`EthereumHardfork::Cancun`].
294    pub fn cancun(mut self) -> Self {
295        self = self.hardfork(EthereumHardfork::Cancun);
296        self
297    }
298
299    /// Set the [`EthereumHardfork`] to [`EthereumHardfork::Shanghai`].
300    pub fn shanghai(mut self) -> Self {
301        self = self.hardfork(EthereumHardfork::Shanghai);
302        self
303    }
304
305    /// Set the [`EthereumHardfork`] to [`EthereumHardfork::Prague`].
306    pub fn prague(mut self) -> Self {
307        self = self.hardfork(EthereumHardfork::Prague);
308        self
309    }
310
311    /// Instantiate `anvil` with the `--odyssey` flag.
312    pub fn odyssey(mut self) -> Self {
313        self = self.arg("--odyssey");
314        self
315    }
316
317    /// Instantiate `anvil` with the `--auto-impersonate` flag.
318    pub fn auto_impersonate(mut self) -> Self {
319        self = self.arg("--auto-impersonate");
320        self
321    }
322
323    /// Adds an argument to pass to the `anvil`.
324    pub fn push_arg<T: Into<OsString>>(&mut self, arg: T) {
325        self.args.push(arg.into());
326    }
327
328    /// Adds multiple arguments to pass to the `anvil`.
329    pub fn extend_args<I, S>(&mut self, args: I)
330    where
331        I: IntoIterator<Item = S>,
332        S: Into<OsString>,
333    {
334        for arg in args {
335            self.push_arg(arg);
336        }
337    }
338
339    /// Adds an argument to pass to the `anvil`.
340    pub fn arg<T: Into<OsString>>(mut self, arg: T) -> Self {
341        self.args.push(arg.into());
342        self
343    }
344
345    /// Adds multiple arguments to pass to the `anvil`.
346    pub fn args<I, S>(mut self, args: I) -> Self
347    where
348        I: IntoIterator<Item = S>,
349        S: Into<OsString>,
350    {
351        for arg in args {
352            self = self.arg(arg);
353        }
354        self
355    }
356
357    /// Adds an environment variable to pass to the `anvil`.
358    pub fn env<K, V>(mut self, key: K, value: V) -> Self
359    where
360        K: Into<OsString>,
361        V: Into<OsString>,
362    {
363        self.envs.push((key.into(), value.into()));
364        self
365    }
366
367    /// Adds multiple environment variables to pass to the `anvil`.
368    pub fn envs<I, K, V>(mut self, envs: I) -> Self
369    where
370        I: IntoIterator<Item = (K, V)>,
371        K: Into<OsString>,
372        V: Into<OsString>,
373    {
374        for (key, value) in envs {
375            self = self.env(key, value);
376        }
377        self
378    }
379
380    /// Sets the timeout which will be used when the `anvil` instance is launched.
381    pub const fn timeout(mut self, timeout: u64) -> Self {
382        self.timeout = Some(timeout);
383        self
384    }
385
386    /// Keep the handle to anvil's stdout in order to read from it.
387    ///
388    /// Caution: if the stdout handle isn't used, this can end up blocking.
389    pub const fn keep_stdout(mut self) -> Self {
390        self.keep_stdout = true;
391        self
392    }
393
394    /// Consumes the builder and spawns `anvil`.
395    ///
396    /// # Panics
397    ///
398    /// If spawning the instance fails at any point.
399    #[track_caller]
400    pub fn spawn(self) -> AnvilInstance {
401        self.try_spawn().unwrap()
402    }
403
404    /// Consumes the builder and spawns `anvil`. If spawning fails, returns an error.
405    pub fn try_spawn(self) -> Result<AnvilInstance, NodeError> {
406        let mut cmd = self.program.as_ref().map_or_else(|| Command::new("anvil"), Command::new);
407        cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
408
409        // disable nightly warning
410        cmd.env("FOUNDRY_DISABLE_NIGHTLY_WARNING", "")
411            // disable color in logs
412            .env("NO_COLOR", "1");
413
414        // set additional environment variables
415        cmd.envs(self.envs);
416
417        let mut port = self.port.unwrap_or_default();
418        cmd.arg("-p").arg(port.to_string());
419
420        if let Some(mnemonic) = self.mnemonic {
421            cmd.arg("-m").arg(mnemonic);
422        }
423
424        if let Some(chain_id) = self.chain_id {
425            cmd.arg("--chain-id").arg(chain_id.to_string());
426        }
427
428        if let Some(block_time) = self.block_time {
429            cmd.arg("-b").arg(block_time.to_string());
430        }
431
432        if let Some(fork) = self.fork {
433            cmd.arg("-f").arg(fork);
434        }
435
436        if let Some(fork_block_number) = self.fork_block_number {
437            cmd.arg("--fork-block-number").arg(fork_block_number.to_string());
438        }
439
440        if let Some(ipc_path) = &self.ipc_path {
441            cmd.arg("--ipc").arg(ipc_path);
442        }
443
444        cmd.args(self.args);
445
446        let mut child = cmd.spawn().map_err(NodeError::SpawnError)?;
447
448        let stdout = child.stdout.take().ok_or(NodeError::NoStdout)?;
449
450        let start = Instant::now();
451        let mut reader = BufReader::new(stdout);
452
453        let mut private_keys = Vec::new();
454        let mut addresses = Vec::new();
455        let mut is_private_key = false;
456        let mut chain_id = None;
457        let mut wallet = None;
458        loop {
459            if start + Duration::from_millis(self.timeout.unwrap_or(ANVIL_STARTUP_TIMEOUT_MILLIS))
460                <= Instant::now()
461            {
462                return Err(NodeError::Timeout);
463            }
464
465            let mut line = String::new();
466            reader.read_line(&mut line).map_err(NodeError::ReadLineError)?;
467            trace!(target: "alloy::node::anvil", line);
468            if let Some(addr) = line.strip_prefix("Listening on") {
469                // <Listening on 127.0.0.1:8545>
470                // parse the actual port
471                if let Ok(addr) = SocketAddr::from_str(addr.trim()) {
472                    port = addr.port();
473                }
474                break;
475            }
476
477            if line.starts_with("Private Keys") {
478                is_private_key = true;
479            }
480
481            if is_private_key && line.starts_with('(') {
482                let key_str =
483                    line.split("0x").last().ok_or(NodeError::ParsePrivateKeyError)?.trim();
484                let key_hex = hex::decode(key_str).map_err(NodeError::FromHexError)?;
485                let key = K256SecretKey::from_bytes((&key_hex[..]).into())
486                    .map_err(|_| NodeError::DeserializePrivateKeyError)?;
487                addresses.push(Address::from_public_key(SigningKey::from(&key).verifying_key()));
488                private_keys.push(key);
489            }
490
491            if let Some(start_chain_id) = line.find("Chain ID:") {
492                let rest = &line[start_chain_id + "Chain ID:".len()..];
493                if let Ok(chain) = rest.split_whitespace().next().unwrap_or("").parse::<u64>() {
494                    chain_id = Some(chain);
495                };
496            }
497
498            if !private_keys.is_empty() {
499                let mut private_keys = private_keys.iter().map(|key| {
500                    let mut signer = LocalSigner::from(key.clone());
501                    signer.set_chain_id(chain_id);
502                    signer
503                });
504                let mut w = EthereumWallet::new(private_keys.next().unwrap());
505                for pk in private_keys {
506                    w.register_signer(pk);
507                }
508                wallet = Some(w);
509            }
510        }
511
512        if self.keep_stdout {
513            // re-attach the stdout handle if requested
514            child.stdout = Some(reader.into_inner());
515        }
516
517        Ok(AnvilInstance {
518            child,
519            private_keys,
520            addresses,
521            wallet,
522            ipc_path: self.ipc_path,
523            port,
524            chain_id: self.chain_id.or(chain_id),
525        })
526    }
527}
528
529#[cfg(test)]
530mod test {
531    use super::*;
532
533    #[test]
534    fn assert_block_time_is_natural_number() {
535        //This test is to ensure that older versions of anvil are supported
536        //even though the block time is a f64, it should be passed as a whole number
537        let anvil = Anvil::new().block_time(12);
538        assert_eq!(anvil.block_time.unwrap().to_string(), "12");
539    }
540
541    #[test]
542    fn spawn_and_drop() {
543        let _ = Anvil::new().block_time(12).try_spawn().map(drop);
544    }
545}