Skip to main content

alloy_node_bindings/nodes/
anvil.rs

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