alloy_node_bindings/nodes/
anvil.rs1use 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
21pub const DEFAULT_IPC_ENDPOINT: &str =
23 if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
24
25#[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 pub const fn child(&self) -> &Child {
43 &self.child
44 }
45
46 pub const fn child_mut(&mut self) -> &mut Child {
48 &mut self.child
49 }
50
51 pub fn keys(&self) -> &[K256SecretKey] {
53 &self.private_keys
54 }
55
56 #[track_caller]
62 pub fn first_key(&self) -> &K256SecretKey {
63 self.private_keys.first().unwrap()
64 }
65
66 pub fn nth_key(&self, idx: usize) -> Option<&K256SecretKey> {
68 self.private_keys.get(idx)
69 }
70
71 pub fn addresses(&self) -> &[Address] {
73 &self.addresses
74 }
75
76 pub fn host(&self) -> &str {
78 &self.host
79 }
80
81 pub const fn port(&self) -> u16 {
83 self.port
84 }
85
86 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 #[doc(alias = "http_endpoint")]
94 pub fn endpoint(&self) -> String {
95 format!("http://{}:{}", self.host, self.port)
96 }
97
98 pub fn ws_endpoint(&self) -> String {
100 format!("ws://{}:{}", self.host, self.port)
101 }
102
103 pub fn ipc_path(&self) -> &str {
105 self.ipc_path.as_deref().unwrap_or(DEFAULT_IPC_ENDPOINT)
106 }
107
108 #[doc(alias = "http_endpoint_url")]
110 pub fn endpoint_url(&self) -> Url {
111 Url::parse(&self.endpoint()).unwrap()
112 }
113
114 pub fn ws_endpoint_url(&self) -> Url {
116 Url::parse(&self.ws_endpoint()).unwrap()
117 }
118
119 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#[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 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 pub fn new() -> Self {
187 Self::default()
188 }
189
190 pub fn at(path: impl Into<PathBuf>) -> Self {
206 Self::new().path(path)
207 }
208
209 pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
214 self.program = Some(path.into());
215 self
216 }
217
218 pub fn host<T: Into<String>>(mut self, host: T) -> Self {
220 self.host = Some(host.into());
221 self
222 }
223
224 pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
229 self.port = Some(port.into());
230 self
231 }
232
233 pub fn ipc_path(mut self, path: impl Into<String>) -> Self {
235 self.ipc_path = Some(path.into());
236 self
237 }
238
239 pub const fn chain_id(mut self, chain_id: u64) -> Self {
243 self.chain_id = Some(chain_id);
244 self
245 }
246
247 pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
249 self.mnemonic = Some(mnemonic.into());
250 self
251 }
252
253 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 pub const fn block_time_f64(mut self, block_time: f64) -> Self {
262 self.block_time = Some(block_time);
263 self
264 }
265
266 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 pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
279 self.fork = Some(fork.into());
280 self
281 }
282
283 pub fn hardfork(mut self, hardfork: EthereumHardfork) -> Self {
285 self = self.args(["--hardfork", hardfork.to_string().as_str()]);
286 self
287 }
288
289 pub fn paris(mut self) -> Self {
291 self = self.hardfork(EthereumHardfork::Paris);
292 self
293 }
294
295 pub fn cancun(mut self) -> Self {
297 self = self.hardfork(EthereumHardfork::Cancun);
298 self
299 }
300
301 pub fn shanghai(mut self) -> Self {
303 self = self.hardfork(EthereumHardfork::Shanghai);
304 self
305 }
306
307 pub fn prague(mut self) -> Self {
309 self = self.hardfork(EthereumHardfork::Prague);
310 self
311 }
312
313 pub fn odyssey(mut self) -> Self {
315 self = self.arg("--odyssey");
316 self
317 }
318
319 pub fn auto_impersonate(mut self) -> Self {
321 self = self.arg("--auto-impersonate");
322 self
323 }
324
325 pub fn push_arg<T: Into<OsString>>(&mut self, arg: T) {
327 self.args.push(arg.into());
328 }
329
330 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 pub fn arg<T: Into<OsString>>(mut self, arg: T) -> Self {
343 self.args.push(arg.into());
344 self
345 }
346
347 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 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 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 pub const fn timeout(mut self, timeout: u64) -> Self {
385 self.timeout = Some(timeout);
386 self
387 }
388
389 pub const fn keep_stdout(mut self) -> Self {
393 self.keep_stdout = true;
394 self
395 }
396
397 #[track_caller]
403 pub fn spawn(self) -> AnvilInstance {
404 self.try_spawn().unwrap()
405 }
406
407 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 cmd.env("FOUNDRY_DISABLE_NIGHTLY_WARNING", "")
418 .env("NO_COLOR", "1");
420
421 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 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 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 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}