ethers_core/utils/
anvil.rs1use crate::{
2 types::{Address, Chain},
3 utils::{secret_key_to_address, unused_port},
4};
5use generic_array::GenericArray;
6use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
7use std::{
8 io::{BufRead, BufReader},
9 path::PathBuf,
10 process::{Child, Command},
11 time::{Duration, Instant},
12};
13
14const ANVIL_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
16
17pub struct AnvilInstance {
21 pid: Child,
22 private_keys: Vec<K256SecretKey>,
23 addresses: Vec<Address>,
24 port: u16,
25 chain_id: Option<u64>,
26}
27
28impl AnvilInstance {
29 pub fn keys(&self) -> &[K256SecretKey] {
31 &self.private_keys
32 }
33
34 pub fn addresses(&self) -> &[Address] {
36 &self.addresses
37 }
38
39 pub fn port(&self) -> u16 {
41 self.port
42 }
43
44 pub fn chain_id(&self) -> u64 {
46 self.chain_id.unwrap_or_else(|| Chain::AnvilHardhat.into())
47 }
48
49 pub fn endpoint(&self) -> String {
51 format!("http://localhost:{}", self.port)
52 }
53
54 pub fn ws_endpoint(&self) -> String {
56 format!("ws://localhost:{}", self.port)
57 }
58}
59
60impl Drop for AnvilInstance {
61 fn drop(&mut self) {
62 self.pid.kill().expect("could not kill anvil");
63 }
64}
65
66#[derive(Debug, Clone, Default)]
88#[must_use = "This Builder struct does nothing unless it is `spawn`ed"]
89pub struct Anvil {
90 program: Option<PathBuf>,
91 port: Option<u16>,
92 block_time: Option<u64>,
93 chain_id: Option<u64>,
94 mnemonic: Option<String>,
95 fork: Option<String>,
96 fork_block_number: Option<u64>,
97 args: Vec<String>,
98 timeout: Option<u64>,
99}
100
101impl Anvil {
102 pub fn new() -> Self {
116 Self::default()
117 }
118
119 pub fn at(path: impl Into<PathBuf>) -> Self {
132 Self::new().path(path)
133 }
134
135 pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
140 self.program = Some(path.into());
141 self
142 }
143
144 pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
146 self.port = Some(port.into());
147 self
148 }
149
150 pub fn chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
152 self.chain_id = Some(chain_id.into());
153 self
154 }
155
156 pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
158 self.mnemonic = Some(mnemonic.into());
159 self
160 }
161
162 pub fn block_time<T: Into<u64>>(mut self, block_time: T) -> Self {
164 self.block_time = Some(block_time.into());
165 self
166 }
167
168 pub fn fork_block_number<T: Into<u64>>(mut self, fork_block_number: T) -> Self {
172 self.fork_block_number = Some(fork_block_number.into());
173 self
174 }
175
176 pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
181 self.fork = Some(fork.into());
182 self
183 }
184
185 pub fn arg<T: Into<String>>(mut self, arg: T) -> Self {
187 self.args.push(arg.into());
188 self
189 }
190
191 pub fn args<I, S>(mut self, args: I) -> Self
193 where
194 I: IntoIterator<Item = S>,
195 S: Into<String>,
196 {
197 for arg in args {
198 self = self.arg(arg);
199 }
200 self
201 }
202
203 pub fn timeout<T: Into<u64>>(mut self, timeout: T) -> Self {
205 self.timeout = Some(timeout.into());
206 self
207 }
208
209 #[track_caller]
215 pub fn spawn(self) -> AnvilInstance {
216 let mut cmd = if let Some(ref prg) = self.program {
217 Command::new(prg)
218 } else {
219 Command::new("anvil")
220 };
221 cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
222 let port = if let Some(port) = self.port { port } else { unused_port() };
223 cmd.arg("-p").arg(port.to_string());
224
225 if let Some(mnemonic) = self.mnemonic {
226 cmd.arg("-m").arg(mnemonic);
227 }
228
229 if let Some(chain_id) = self.chain_id {
230 cmd.arg("--chain-id").arg(chain_id.to_string());
231 }
232
233 if let Some(block_time) = self.block_time {
234 cmd.arg("-b").arg(block_time.to_string());
235 }
236
237 if let Some(fork) = self.fork {
238 cmd.arg("-f").arg(fork);
239 }
240
241 if let Some(fork_block_number) = self.fork_block_number {
242 cmd.arg("--fork-block-number").arg(fork_block_number.to_string());
243 }
244
245 cmd.args(self.args);
246
247 let mut child = cmd.spawn().expect("couldnt start anvil");
248
249 let stdout = child.stdout.take().expect("Unable to get stdout for anvil child process");
250
251 let start = Instant::now();
252 let mut reader = BufReader::new(stdout);
253
254 let mut private_keys = Vec::new();
255 let mut addresses = Vec::new();
256 let mut is_private_key = false;
257 let mut chain_id = None;
258 loop {
259 if start + Duration::from_millis(self.timeout.unwrap_or(ANVIL_STARTUP_TIMEOUT_MILLIS)) <=
260 Instant::now()
261 {
262 panic!("Timed out waiting for anvil to start. Is anvil installed?")
263 }
264
265 let mut line = String::new();
266 reader.read_line(&mut line).expect("Failed to read line from anvil process");
267 if line.contains("Listening on") {
268 break
269 }
270
271 if line.starts_with("Private Keys") {
272 is_private_key = true;
273 }
274
275 if is_private_key && line.starts_with('(') {
276 let key_str = line
277 .split("0x")
278 .last()
279 .unwrap_or_else(|| panic!("could not parse private key: {}", line))
280 .trim();
281 let key_hex = hex::decode(key_str).expect("could not parse as hex");
282 let key = K256SecretKey::from_bytes(&GenericArray::clone_from_slice(&key_hex))
283 .expect("did not get private key");
284 addresses.push(secret_key_to_address(&SigningKey::from(&key)));
285 private_keys.push(key);
286 }
287
288 if let Some(start_chain_id) = line.find("Chain ID:") {
289 let rest = &line[start_chain_id + "Chain ID:".len()..];
290 if let Ok(chain) = rest.split_whitespace().next().unwrap_or("").parse::<u64>() {
291 chain_id = Some(chain);
292 };
293 }
294 }
295
296 AnvilInstance {
297 pid: child,
298 private_keys,
299 addresses,
300 port,
301 chain_id: self.chain_id.or(chain_id),
302 }
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn can_launch_anvil() {
312 let _ = Anvil::new().spawn();
313 }
314
315 #[test]
316 fn can_launch_anvil_with_more_accounts() {
317 let _ = Anvil::new().arg("--accounts").arg("20").spawn();
318 }
319
320 #[test]
321 fn assert_chain_id() {
322 let anvil = Anvil::new().fork("https://rpc.ankr.com/eth").spawn();
323 assert_eq!(anvil.chain_id(), 1);
324 }
325
326 #[test]
327 fn assert_chain_id_without_rpc() {
328 let anvil = Anvil::new().spawn();
329 assert_eq!(anvil.chain_id(), 31337);
330 }
331}