alloy_node_bindings/nodes/
anvil.rs

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