Skip to main content

cbrzn_ethers_core/utils/
anvil.rs

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