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)]
153#[must_use = "This Builder struct does nothing unless it is `spawn`ed"]
154pub struct Anvil {
155 program: Option<PathBuf>,
156 host: Option<String>,
157 port: Option<u16>,
158 block_time: Option<f64>,
161 chain_id: Option<ChainId>,
162 mnemonic: Option<String>,
163 ipc_path: Option<String>,
164 fork: Option<String>,
165 fork_block_number: Option<u64>,
166 args: Vec<OsString>,
167 envs: Vec<(OsString, OsString)>,
168 timeout: Option<u64>,
169 keep_stdout: bool,
170}
171
172impl Anvil {
173 pub fn new() -> Self {
187 Self::default()
188 }
189
190 pub fn at(path: impl Into<PathBuf>) -> Self {
203 Self::new().path(path)
204 }
205
206 pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
211 self.program = Some(path.into());
212 self
213 }
214
215 pub fn host<T: Into<String>>(mut self, host: T) -> Self {
217 self.host = Some(host.into());
218 self
219 }
220
221 pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
223 self.port = Some(port.into());
224 self
225 }
226
227 pub fn ipc_path(mut self, path: impl Into<String>) -> Self {
229 self.ipc_path = Some(path.into());
230 self
231 }
232
233 pub const fn chain_id(mut self, chain_id: u64) -> Self {
237 self.chain_id = Some(chain_id);
238 self
239 }
240
241 pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
243 self.mnemonic = Some(mnemonic.into());
244 self
245 }
246
247 pub const fn block_time(mut self, block_time: u64) -> Self {
249 self.block_time = Some(block_time as f64);
250 self
251 }
252
253 pub const fn block_time_f64(mut self, block_time: f64) -> Self {
256 self.block_time = Some(block_time);
257 self
258 }
259
260 pub const fn fork_block_number(mut self, fork_block_number: u64) -> Self {
264 self.fork_block_number = Some(fork_block_number);
265 self
266 }
267
268 pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
273 self.fork = Some(fork.into());
274 self
275 }
276
277 pub fn hardfork(mut self, hardfork: EthereumHardfork) -> Self {
279 self = self.args(["--hardfork", hardfork.to_string().as_str()]);
280 self
281 }
282
283 pub fn paris(mut self) -> Self {
285 self = self.hardfork(EthereumHardfork::Paris);
286 self
287 }
288
289 pub fn cancun(mut self) -> Self {
291 self = self.hardfork(EthereumHardfork::Cancun);
292 self
293 }
294
295 pub fn shanghai(mut self) -> Self {
297 self = self.hardfork(EthereumHardfork::Shanghai);
298 self
299 }
300
301 pub fn prague(mut self) -> Self {
303 self = self.hardfork(EthereumHardfork::Prague);
304 self
305 }
306
307 pub fn odyssey(mut self) -> Self {
309 self = self.arg("--odyssey");
310 self
311 }
312
313 pub fn auto_impersonate(mut self) -> Self {
315 self = self.arg("--auto-impersonate");
316 self
317 }
318
319 pub fn push_arg<T: Into<OsString>>(&mut self, arg: T) {
321 self.args.push(arg.into());
322 }
323
324 pub fn extend_args<I, S>(&mut self, args: I)
326 where
327 I: IntoIterator<Item = S>,
328 S: Into<OsString>,
329 {
330 for arg in args {
331 self.push_arg(arg);
332 }
333 }
334
335 pub fn arg<T: Into<OsString>>(mut self, arg: T) -> Self {
337 self.args.push(arg.into());
338 self
339 }
340
341 pub fn args<I, S>(mut self, args: I) -> Self
343 where
344 I: IntoIterator<Item = S>,
345 S: Into<OsString>,
346 {
347 for arg in args {
348 self = self.arg(arg);
349 }
350 self
351 }
352
353 pub fn env<K, V>(mut self, key: K, value: V) -> Self
355 where
356 K: Into<OsString>,
357 V: Into<OsString>,
358 {
359 self.envs.push((key.into(), value.into()));
360 self
361 }
362
363 pub fn envs<I, K, V>(mut self, envs: I) -> Self
365 where
366 I: IntoIterator<Item = (K, V)>,
367 K: Into<OsString>,
368 V: Into<OsString>,
369 {
370 for (key, value) in envs {
371 self = self.env(key, value);
372 }
373 self
374 }
375
376 pub const fn timeout(mut self, timeout: u64) -> Self {
379 self.timeout = Some(timeout);
380 self
381 }
382
383 pub const fn keep_stdout(mut self) -> Self {
387 self.keep_stdout = true;
388 self
389 }
390
391 #[track_caller]
397 pub fn spawn(self) -> AnvilInstance {
398 self.try_spawn().unwrap()
399 }
400
401 pub fn try_spawn(self) -> Result<AnvilInstance, NodeError> {
403 let mut cmd = self.program.as_ref().map_or_else(|| Command::new("anvil"), Command::new);
404 cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
405
406 cmd.env("FOUNDRY_DISABLE_NIGHTLY_WARNING", "")
408 .env("NO_COLOR", "1");
410
411 cmd.envs(self.envs);
413
414 if let Some(ref host) = self.host {
415 cmd.arg("--host").arg(host);
416 }
417
418 let mut port = self.port.unwrap_or_default();
419 cmd.arg("-p").arg(port.to_string());
420
421 if let Some(mnemonic) = self.mnemonic {
422 cmd.arg("-m").arg(mnemonic);
423 }
424
425 if let Some(chain_id) = self.chain_id {
426 cmd.arg("--chain-id").arg(chain_id.to_string());
427 }
428
429 if let Some(block_time) = self.block_time {
430 cmd.arg("-b").arg(block_time.to_string());
431 }
432
433 if let Some(fork) = self.fork {
434 cmd.arg("-f").arg(fork);
435 }
436
437 if let Some(fork_block_number) = self.fork_block_number {
438 cmd.arg("--fork-block-number").arg(fork_block_number.to_string());
439 }
440
441 if let Some(ipc_path) = &self.ipc_path {
442 cmd.arg("--ipc").arg(ipc_path);
443 }
444
445 cmd.args(self.args);
446
447 let mut child = cmd.spawn().map_err(NodeError::SpawnError)?;
448
449 let stdout = child.stdout.take().ok_or(NodeError::NoStdout)?;
450
451 let start = Instant::now();
452 let mut reader = BufReader::new(stdout);
453 let timeout = self.timeout.map(Duration::from_millis).unwrap_or(NODE_STARTUP_TIMEOUT);
454
455 let mut private_keys = Vec::new();
456 let mut addresses = Vec::new();
457 let mut is_private_key = false;
458 let mut chain_id = None;
459 let mut wallet = None;
460 loop {
461 if start + timeout <= Instant::now() {
462 let _ = child.kill();
463 return Err(NodeError::Timeout);
464 }
465
466 let mut line = String::new();
467 reader.read_line(&mut line).map_err(NodeError::ReadLineError)?;
468 trace!(target: "alloy::node::anvil", line);
469 if let Some(addr) = line.strip_prefix("Listening on") {
470 if let Ok(addr) = SocketAddr::from_str(addr.trim()) {
473 port = addr.port();
474 }
475 break;
476 }
477
478 if line.starts_with("Private Keys") {
479 is_private_key = true;
480 }
481
482 if is_private_key && line.starts_with('(') {
483 let key_str =
484 line.split("0x").last().ok_or(NodeError::ParsePrivateKeyError)?.trim();
485 let key_hex = hex::decode(key_str).map_err(NodeError::FromHexError)?;
486 let key = K256SecretKey::from_bytes((&key_hex[..]).into())
487 .map_err(|_| NodeError::DeserializePrivateKeyError)?;
488 addresses.push(Address::from_public_key(SigningKey::from(&key).verifying_key()));
489 private_keys.push(key);
490 }
491
492 if let Some(start_chain_id) = line.find("Chain ID:") {
493 let rest = &line[start_chain_id + "Chain ID:".len()..];
494 if let Ok(chain) = rest.split_whitespace().next().unwrap_or("").parse::<u64>() {
495 chain_id = Some(chain);
496 };
497 }
498
499 if !private_keys.is_empty() {
500 let mut private_keys = private_keys.iter().map(|key| {
501 let mut signer = LocalSigner::from(key.clone());
502 signer.set_chain_id(chain_id);
503 signer
504 });
505 let mut w = EthereumWallet::new(private_keys.next().unwrap());
506 for pk in private_keys {
507 w.register_signer(pk);
508 }
509 wallet = Some(w);
510 }
511 }
512
513 if self.keep_stdout {
514 child.stdout = Some(reader.into_inner());
516 }
517
518 Ok(AnvilInstance {
519 child,
520 private_keys,
521 addresses,
522 wallet,
523 ipc_path: self.ipc_path,
524 host: self.host.unwrap_or_else(|| "localhost".to_string()),
525 port,
526 chain_id: self.chain_id.or(chain_id),
527 })
528 }
529}
530
531#[cfg(test)]
532mod test {
533 use super::*;
534
535 #[test]
536 fn assert_block_time_is_natural_number() {
537 let anvil = Anvil::new().block_time(12);
540 assert_eq!(anvil.block_time.unwrap().to_string(), "12");
541 }
542
543 #[test]
544 fn spawn_and_drop() {
545 let _ = Anvil::new().block_time(12).try_spawn().map(drop);
546 }
547
548 #[test]
549 fn can_set_host() {
550 let anvil = Anvil::new().host("0.0.0.0").block_time(12).try_spawn();
551 if let Ok(anvil) = anvil {
552 assert_eq!(anvil.host(), "0.0.0.0");
553 assert!(anvil.endpoint().starts_with("http://0.0.0.0:"));
554 assert!(anvil.ws_endpoint().starts_with("ws://0.0.0.0:"));
555 }
556 }
557
558 #[test]
559 fn default_host_is_localhost() {
560 let anvil = Anvil::new().block_time(12).try_spawn();
561 if let Ok(anvil) = anvil {
562 assert_eq!(anvil.host(), "localhost");
563 assert!(anvil.endpoint().starts_with("http://localhost:"));
564 assert!(anvil.ws_endpoint().starts_with("ws://localhost:"));
565 }
566 }
567}