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 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        self.child.kill().expect("could not kill anvil");
125    }
126}
127
128/// Builder for launching `anvil`.
129///
130/// # Panics
131///
132/// If `spawn` is called without `anvil` being available in the user's $PATH
133///
134/// # Example
135///
136/// ```no_run
137/// use alloy_node_bindings::Anvil;
138///
139/// let port = 8545u16;
140/// let url = format!("http://localhost:{}", port).to_string();
141///
142/// let anvil = Anvil::new()
143///     .port(port)
144///     .mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
145///     .spawn();
146///
147/// drop(anvil); // this will kill the instance
148/// ```
149#[derive(Clone, Debug, Default)]
150#[must_use = "This Builder struct does nothing unless it is `spawn`ed"]
151pub struct Anvil {
152    program: Option<PathBuf>,
153    port: Option<u16>,
154    // If the block_time is an integer, f64::to_string() will output without a decimal point
155    // which allows this to be backwards compatible.
156    block_time: Option<f64>,
157    chain_id: Option<ChainId>,
158    mnemonic: Option<String>,
159    ipc_path: Option<String>,
160    fork: Option<String>,
161    fork_block_number: Option<u64>,
162    args: Vec<OsString>,
163    timeout: Option<u64>,
164    keep_stdout: bool,
165}
166
167impl Anvil {
168    /// Creates an empty Anvil builder.
169    /// The default port and the mnemonic are chosen randomly.
170    ///
171    /// # Example
172    ///
173    /// ```
174    /// # use alloy_node_bindings::Anvil;
175    /// fn a() {
176    ///  let anvil = Anvil::default().spawn();
177    ///
178    ///  println!("Anvil running at `{}`", anvil.endpoint());
179    /// # }
180    /// ```
181    pub fn new() -> Self {
182        Self::default()
183    }
184
185    /// Creates an Anvil builder which will execute `anvil` at the given path.
186    ///
187    /// # Example
188    ///
189    /// ```
190    /// # use alloy_node_bindings::Anvil;
191    /// fn a() {
192    ///  let anvil = Anvil::at("~/.foundry/bin/anvil").spawn();
193    ///
194    ///  println!("Anvil running at `{}`", anvil.endpoint());
195    /// # }
196    /// ```
197    pub fn at(path: impl Into<PathBuf>) -> Self {
198        Self::new().path(path)
199    }
200
201    /// Sets the `path` to the `anvil` cli
202    ///
203    /// By default, it's expected that `anvil` is in `$PATH`, see also
204    /// [`std::process::Command::new()`]
205    pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
206        self.program = Some(path.into());
207        self
208    }
209
210    /// Sets the port which will be used when the `anvil` instance is launched.
211    pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
212        self.port = Some(port.into());
213        self
214    }
215
216    /// Sets the path for the ipc server
217    pub fn ipc_path(mut self, path: impl Into<String>) -> Self {
218        self.ipc_path = Some(path.into());
219        self
220    }
221
222    /// Sets the chain_id the `anvil` instance will use.
223    ///
224    /// By default [`DEFAULT_IPC_ENDPOINT`] will be used.
225    pub const fn chain_id(mut self, chain_id: u64) -> Self {
226        self.chain_id = Some(chain_id);
227        self
228    }
229
230    /// Sets the mnemonic which will be used when the `anvil` instance is launched.
231    pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
232        self.mnemonic = Some(mnemonic.into());
233        self
234    }
235
236    /// Sets the block-time in seconds which will be used when the `anvil` instance is launched.
237    pub const fn block_time(mut self, block_time: u64) -> Self {
238        self.block_time = Some(block_time as f64);
239        self
240    }
241
242    /// Sets the block-time in sub-seconds which will be used when the `anvil` instance is launched.
243    /// Older versions of `anvil` do not support sub-second block times.
244    pub const fn block_time_f64(mut self, block_time: f64) -> Self {
245        self.block_time = Some(block_time);
246        self
247    }
248
249    /// Sets the `fork-block-number` which will be used in addition to [`Self::fork`].
250    ///
251    /// **Note:** if set, then this requires `fork` to be set as well
252    pub const fn fork_block_number(mut self, fork_block_number: u64) -> Self {
253        self.fork_block_number = Some(fork_block_number);
254        self
255    }
256
257    /// Sets the `fork` argument to fork from another currently running Ethereum client
258    /// at a given block. Input should be the HTTP location and port of the other client,
259    /// e.g. `http://localhost:8545`. You can optionally specify the block to fork from
260    /// using an @ sign: `http://localhost:8545@1599200`
261    pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
262        self.fork = Some(fork.into());
263        self
264    }
265
266    /// Select the [`EthereumHardfork`] to start anvil with.
267    pub fn hardfork(mut self, hardfork: EthereumHardfork) -> Self {
268        self = self.args(["--hardfork", hardfork.to_string().as_str()]);
269        self
270    }
271
272    /// Set the [`EthereumHardfork`] to [`EthereumHardfork::Paris`].
273    pub fn paris(mut self) -> Self {
274        self = self.hardfork(EthereumHardfork::Paris);
275        self
276    }
277
278    /// Set the [`EthereumHardfork`] to [`EthereumHardfork::Cancun`].
279    pub fn cancun(mut self) -> Self {
280        self = self.hardfork(EthereumHardfork::Cancun);
281        self
282    }
283
284    /// Set the [`EthereumHardfork`] to [`EthereumHardfork::Shanghai`].
285    pub fn shanghai(mut self) -> Self {
286        self = self.hardfork(EthereumHardfork::Shanghai);
287        self
288    }
289
290    /// Set the [`EthereumHardfork`] to [`EthereumHardfork::Prague`].
291    pub fn prague(mut self) -> Self {
292        self = self.hardfork(EthereumHardfork::Prague);
293        self
294    }
295
296    /// Instantiate `anvil` with the `--odyssey` flag.
297    pub fn odyssey(mut self) -> Self {
298        self = self.arg("--odyssey");
299        self
300    }
301
302    /// Adds an argument to pass to the `anvil`.
303    pub fn push_arg<T: Into<OsString>>(&mut self, arg: T) {
304        self.args.push(arg.into());
305    }
306
307    /// Adds multiple arguments to pass to the `anvil`.
308    pub fn extend_args<I, S>(&mut self, args: I)
309    where
310        I: IntoIterator<Item = S>,
311        S: Into<OsString>,
312    {
313        for arg in args {
314            self.push_arg(arg);
315        }
316    }
317
318    /// Adds an argument to pass to the `anvil`.
319    pub fn arg<T: Into<OsString>>(mut self, arg: T) -> Self {
320        self.args.push(arg.into());
321        self
322    }
323
324    /// Adds multiple arguments to pass to the `anvil`.
325    pub fn args<I, S>(mut self, args: I) -> Self
326    where
327        I: IntoIterator<Item = S>,
328        S: Into<OsString>,
329    {
330        for arg in args {
331            self = self.arg(arg);
332        }
333        self
334    }
335
336    /// Sets the timeout which will be used when the `anvil` instance is launched.
337    pub const fn timeout(mut self, timeout: u64) -> Self {
338        self.timeout = Some(timeout);
339        self
340    }
341
342    /// Keep the handle to anvil's stdout in order to read from it.
343    ///
344    /// Caution: if the stdout handle isn't used, this can end up blocking.
345    pub const fn keep_stdout(mut self) -> Self {
346        self.keep_stdout = true;
347        self
348    }
349
350    /// Consumes the builder and spawns `anvil`.
351    ///
352    /// # Panics
353    ///
354    /// If spawning the instance fails at any point.
355    #[track_caller]
356    pub fn spawn(self) -> AnvilInstance {
357        self.try_spawn().unwrap()
358    }
359
360    /// Consumes the builder and spawns `anvil`. If spawning fails, returns an error.
361    pub fn try_spawn(self) -> Result<AnvilInstance, NodeError> {
362        let mut cmd = self.program.as_ref().map_or_else(|| Command::new("anvil"), Command::new);
363        cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
364
365        // disable nightly warning
366        cmd.env("FOUNDRY_DISABLE_NIGHTLY_WARNING", "");
367
368        let mut port = self.port.unwrap_or_default();
369        cmd.arg("-p").arg(port.to_string());
370
371        if let Some(mnemonic) = self.mnemonic {
372            cmd.arg("-m").arg(mnemonic);
373        }
374
375        if let Some(chain_id) = self.chain_id {
376            cmd.arg("--chain-id").arg(chain_id.to_string());
377        }
378
379        if let Some(block_time) = self.block_time {
380            cmd.arg("-b").arg(block_time.to_string());
381        }
382
383        if let Some(fork) = self.fork {
384            cmd.arg("-f").arg(fork);
385        }
386
387        if let Some(fork_block_number) = self.fork_block_number {
388            cmd.arg("--fork-block-number").arg(fork_block_number.to_string());
389        }
390
391        if let Some(ipc_path) = &self.ipc_path {
392            cmd.arg("--ipc").arg(ipc_path);
393        }
394
395        cmd.args(self.args);
396
397        let mut child = cmd.spawn().map_err(NodeError::SpawnError)?;
398
399        let stdout = child.stdout.take().ok_or(NodeError::NoStdout)?;
400
401        let start = Instant::now();
402        let mut reader = BufReader::new(stdout);
403
404        let mut private_keys = Vec::new();
405        let mut addresses = Vec::new();
406        let mut is_private_key = false;
407        let mut chain_id = None;
408        let mut wallet = None;
409        loop {
410            if start + Duration::from_millis(self.timeout.unwrap_or(ANVIL_STARTUP_TIMEOUT_MILLIS))
411                <= Instant::now()
412            {
413                return Err(NodeError::Timeout);
414            }
415
416            let mut line = String::new();
417            reader.read_line(&mut line).map_err(NodeError::ReadLineError)?;
418            trace!(target: "anvil", line);
419            if let Some(addr) = line.strip_prefix("Listening on") {
420                // <Listening on 127.0.0.1:8545>
421                // parse the actual port
422                if let Ok(addr) = SocketAddr::from_str(addr.trim()) {
423                    port = addr.port();
424                }
425                break;
426            }
427
428            if line.starts_with("Private Keys") {
429                is_private_key = true;
430            }
431
432            if is_private_key && line.starts_with('(') {
433                let key_str =
434                    line.split("0x").last().ok_or(NodeError::ParsePrivateKeyError)?.trim();
435                let key_hex = hex::decode(key_str).map_err(NodeError::FromHexError)?;
436                let key = K256SecretKey::from_bytes((&key_hex[..]).into())
437                    .map_err(|_| NodeError::DeserializePrivateKeyError)?;
438                addresses.push(Address::from_public_key(SigningKey::from(&key).verifying_key()));
439                private_keys.push(key);
440            }
441
442            if let Some(start_chain_id) = line.find("Chain ID:") {
443                let rest = &line[start_chain_id + "Chain ID:".len()..];
444                if let Ok(chain) = rest.split_whitespace().next().unwrap_or("").parse::<u64>() {
445                    chain_id = Some(chain);
446                };
447            }
448
449            if !private_keys.is_empty() {
450                let mut private_keys = private_keys.iter().map(|key| {
451                    let mut signer = LocalSigner::from(key.clone());
452                    signer.set_chain_id(chain_id);
453                    signer
454                });
455                let mut w = EthereumWallet::new(private_keys.next().unwrap());
456                for pk in private_keys {
457                    w.register_signer(pk);
458                }
459                wallet = Some(w);
460            }
461        }
462
463        if self.keep_stdout {
464            // re-attach the stdout handle if requested
465            child.stdout = Some(reader.into_inner());
466        }
467
468        Ok(AnvilInstance {
469            child,
470            private_keys,
471            addresses,
472            wallet,
473            ipc_path: self.ipc_path,
474            port,
475            chain_id: self.chain_id.or(chain_id),
476        })
477    }
478}
479
480#[cfg(test)]
481mod test {
482    use super::*;
483
484    #[test]
485    fn assert_block_time_is_natural_number() {
486        //This test is to ensure that older versions of anvil are supported
487        //even though the block time is a f64, it should be passed as a whole number
488        let anvil = Anvil::new().block_time(12);
489        assert_eq!(anvil.block_time.unwrap().to_string(), "12");
490    }
491}