alloy_node_bindings/nodes/
anvil.rs1use 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
21pub const DEFAULT_IPC_ENDPOINT: &str =
23 if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
24
25const ANVIL_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
27
28#[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 pub const fn child(&self) -> &Child {
45 &self.child
46 }
47
48 pub const fn child_mut(&mut self) -> &mut Child {
50 &mut self.child
51 }
52
53 pub fn keys(&self) -> &[K256SecretKey] {
55 &self.private_keys
56 }
57
58 #[track_caller]
64 pub fn first_key(&self) -> &K256SecretKey {
65 self.private_keys.first().unwrap()
66 }
67
68 pub fn nth_key(&self, idx: usize) -> Option<&K256SecretKey> {
70 self.private_keys.get(idx)
71 }
72
73 pub fn addresses(&self) -> &[Address] {
75 &self.addresses
76 }
77
78 pub const fn port(&self) -> u16 {
80 self.port
81 }
82
83 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 #[doc(alias = "http_endpoint")]
91 pub fn endpoint(&self) -> String {
92 format!("http://localhost:{}", self.port)
93 }
94
95 pub fn ws_endpoint(&self) -> String {
97 format!("ws://localhost:{}", self.port)
98 }
99
100 pub fn ipc_path(&self) -> &str {
102 self.ipc_path.as_deref().unwrap_or(DEFAULT_IPC_ENDPOINT)
103 }
104
105 #[doc(alias = "http_endpoint_url")]
107 pub fn endpoint_url(&self) -> Url {
108 Url::parse(&self.endpoint()).unwrap()
109 }
110
111 pub fn ws_endpoint_url(&self) -> Url {
113 Url::parse(&self.ws_endpoint()).unwrap()
114 }
115
116 pub fn wallet(&self) -> Option<EthereumWallet> {
118 self.wallet.clone()
119 }
120}
121
122impl Drop for AnvilInstance {
123 fn drop(&mut self) {
124 #[cfg(unix)]
125 {
126 if let Ok(out) =
129 Command::new("kill").arg("-SIGTERM").arg(self.child.id().to_string()).output()
130 {
131 if out.status.success() {
132 return;
133 }
134 }
135 }
136 self.child.kill().expect("could not kill anvil");
137 }
138}
139
140#[derive(Clone, Debug, Default)]
162#[must_use = "This Builder struct does nothing unless it is `spawn`ed"]
163pub struct Anvil {
164 program: Option<PathBuf>,
165 port: Option<u16>,
166 block_time: Option<f64>,
169 chain_id: Option<ChainId>,
170 mnemonic: Option<String>,
171 ipc_path: Option<String>,
172 fork: Option<String>,
173 fork_block_number: Option<u64>,
174 args: Vec<OsString>,
175 envs: Vec<(OsString, OsString)>,
176 timeout: Option<u64>,
177 keep_stdout: bool,
178}
179
180impl Anvil {
181 pub fn new() -> Self {
195 Self::default()
196 }
197
198 pub fn at(path: impl Into<PathBuf>) -> Self {
211 Self::new().path(path)
212 }
213
214 pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
219 self.program = Some(path.into());
220 self
221 }
222
223 pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
225 self.port = Some(port.into());
226 self
227 }
228
229 pub fn ipc_path(mut self, path: impl Into<String>) -> Self {
231 self.ipc_path = Some(path.into());
232 self
233 }
234
235 pub const fn chain_id(mut self, chain_id: u64) -> Self {
239 self.chain_id = Some(chain_id);
240 self
241 }
242
243 pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
245 self.mnemonic = Some(mnemonic.into());
246 self
247 }
248
249 pub const fn block_time(mut self, block_time: u64) -> Self {
251 self.block_time = Some(block_time as f64);
252 self
253 }
254
255 pub const fn block_time_f64(mut self, block_time: f64) -> Self {
258 self.block_time = Some(block_time);
259 self
260 }
261
262 pub const fn fork_block_number(mut self, fork_block_number: u64) -> Self {
266 self.fork_block_number = Some(fork_block_number);
267 self
268 }
269
270 pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
275 self.fork = Some(fork.into());
276 self
277 }
278
279 pub fn hardfork(mut self, hardfork: EthereumHardfork) -> Self {
281 self = self.args(["--hardfork", hardfork.to_string().as_str()]);
282 self
283 }
284
285 pub fn paris(mut self) -> Self {
287 self = self.hardfork(EthereumHardfork::Paris);
288 self
289 }
290
291 pub fn cancun(mut self) -> Self {
293 self = self.hardfork(EthereumHardfork::Cancun);
294 self
295 }
296
297 pub fn shanghai(mut self) -> Self {
299 self = self.hardfork(EthereumHardfork::Shanghai);
300 self
301 }
302
303 pub fn prague(mut self) -> Self {
305 self = self.hardfork(EthereumHardfork::Prague);
306 self
307 }
308
309 pub fn odyssey(mut self) -> Self {
311 self = self.arg("--odyssey");
312 self
313 }
314
315 pub fn push_arg<T: Into<OsString>>(&mut self, arg: T) {
317 self.args.push(arg.into());
318 }
319
320 pub fn extend_args<I, S>(&mut self, args: I)
322 where
323 I: IntoIterator<Item = S>,
324 S: Into<OsString>,
325 {
326 for arg in args {
327 self.push_arg(arg);
328 }
329 }
330
331 pub fn arg<T: Into<OsString>>(mut self, arg: T) -> Self {
333 self.args.push(arg.into());
334 self
335 }
336
337 pub fn args<I, S>(mut self, args: I) -> Self
339 where
340 I: IntoIterator<Item = S>,
341 S: Into<OsString>,
342 {
343 for arg in args {
344 self = self.arg(arg);
345 }
346 self
347 }
348
349 pub fn env<K, V>(mut self, key: K, value: V) -> Self
351 where
352 K: Into<OsString>,
353 V: Into<OsString>,
354 {
355 self.envs.push((key.into(), value.into()));
356 self
357 }
358
359 pub fn envs<I, K, V>(mut self, envs: I) -> Self
361 where
362 I: IntoIterator<Item = (K, V)>,
363 K: Into<OsString>,
364 V: Into<OsString>,
365 {
366 for (key, value) in envs {
367 self = self.env(key, value);
368 }
369 self
370 }
371
372 pub const fn timeout(mut self, timeout: u64) -> Self {
374 self.timeout = Some(timeout);
375 self
376 }
377
378 pub const fn keep_stdout(mut self) -> Self {
382 self.keep_stdout = true;
383 self
384 }
385
386 #[track_caller]
392 pub fn spawn(self) -> AnvilInstance {
393 self.try_spawn().unwrap()
394 }
395
396 pub fn try_spawn(self) -> Result<AnvilInstance, NodeError> {
398 let mut cmd = self.program.as_ref().map_or_else(|| Command::new("anvil"), Command::new);
399 cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
400
401 cmd.env("FOUNDRY_DISABLE_NIGHTLY_WARNING", "")
403 .env("NO_COLOR", "1");
405
406 cmd.envs(self.envs);
408
409 let mut port = self.port.unwrap_or_default();
410 cmd.arg("-p").arg(port.to_string());
411
412 if let Some(mnemonic) = self.mnemonic {
413 cmd.arg("-m").arg(mnemonic);
414 }
415
416 if let Some(chain_id) = self.chain_id {
417 cmd.arg("--chain-id").arg(chain_id.to_string());
418 }
419
420 if let Some(block_time) = self.block_time {
421 cmd.arg("-b").arg(block_time.to_string());
422 }
423
424 if let Some(fork) = self.fork {
425 cmd.arg("-f").arg(fork);
426 }
427
428 if let Some(fork_block_number) = self.fork_block_number {
429 cmd.arg("--fork-block-number").arg(fork_block_number.to_string());
430 }
431
432 if let Some(ipc_path) = &self.ipc_path {
433 cmd.arg("--ipc").arg(ipc_path);
434 }
435
436 cmd.args(self.args);
437
438 let mut child = cmd.spawn().map_err(NodeError::SpawnError)?;
439
440 let stdout = child.stdout.take().ok_or(NodeError::NoStdout)?;
441
442 let start = Instant::now();
443 let mut reader = BufReader::new(stdout);
444
445 let mut private_keys = Vec::new();
446 let mut addresses = Vec::new();
447 let mut is_private_key = false;
448 let mut chain_id = None;
449 let mut wallet = None;
450 loop {
451 if start + Duration::from_millis(self.timeout.unwrap_or(ANVIL_STARTUP_TIMEOUT_MILLIS))
452 <= Instant::now()
453 {
454 return Err(NodeError::Timeout);
455 }
456
457 let mut line = String::new();
458 reader.read_line(&mut line).map_err(NodeError::ReadLineError)?;
459 trace!(target: "anvil", line);
460 if let Some(addr) = line.strip_prefix("Listening on") {
461 if let Ok(addr) = SocketAddr::from_str(addr.trim()) {
464 port = addr.port();
465 }
466 break;
467 }
468
469 if line.starts_with("Private Keys") {
470 is_private_key = true;
471 }
472
473 if is_private_key && line.starts_with('(') {
474 let key_str =
475 line.split("0x").last().ok_or(NodeError::ParsePrivateKeyError)?.trim();
476 let key_hex = hex::decode(key_str).map_err(NodeError::FromHexError)?;
477 let key = K256SecretKey::from_bytes((&key_hex[..]).into())
478 .map_err(|_| NodeError::DeserializePrivateKeyError)?;
479 addresses.push(Address::from_public_key(SigningKey::from(&key).verifying_key()));
480 private_keys.push(key);
481 }
482
483 if let Some(start_chain_id) = line.find("Chain ID:") {
484 let rest = &line[start_chain_id + "Chain ID:".len()..];
485 if let Ok(chain) = rest.split_whitespace().next().unwrap_or("").parse::<u64>() {
486 chain_id = Some(chain);
487 };
488 }
489
490 if !private_keys.is_empty() {
491 let mut private_keys = private_keys.iter().map(|key| {
492 let mut signer = LocalSigner::from(key.clone());
493 signer.set_chain_id(chain_id);
494 signer
495 });
496 let mut w = EthereumWallet::new(private_keys.next().unwrap());
497 for pk in private_keys {
498 w.register_signer(pk);
499 }
500 wallet = Some(w);
501 }
502 }
503
504 if self.keep_stdout {
505 child.stdout = Some(reader.into_inner());
507 }
508
509 Ok(AnvilInstance {
510 child,
511 private_keys,
512 addresses,
513 wallet,
514 ipc_path: self.ipc_path,
515 port,
516 chain_id: self.chain_id.or(chain_id),
517 })
518 }
519}
520
521#[cfg(test)]
522mod test {
523 use super::*;
524
525 #[test]
526 fn assert_block_time_is_natural_number() {
527 let anvil = Anvil::new().block_time(12);
530 assert_eq!(anvil.block_time.unwrap().to_string(), "12");
531 }
532
533 #[test]
534 fn spawn_and_drop() {
535 let _ = Anvil::new().block_time(12).try_spawn().map(drop);
536 }
537}