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 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 self.child.kill().expect("could not kill anvil");
125 }
126}
127
128#[derive(Clone, Debug, Default)]
150#[must_use = "This Builder struct does nothing unless it is `spawn`ed"]
151pub struct Anvil {
152 program: Option<PathBuf>,
153 port: Option<u16>,
154 block_time: Option<f64>,
157 chain_id: Option<ChainId>,
158 mnemonic: Option<String>,
159 ipc_path: Option<String>,
160 fork: Option<String>,
161 fork_block_number: Option<u64>,
162 args: Vec<OsString>,
163 timeout: Option<u64>,
164 keep_stdout: bool,
165}
166
167impl Anvil {
168 pub fn new() -> Self {
182 Self::default()
183 }
184
185 pub fn at(path: impl Into<PathBuf>) -> Self {
198 Self::new().path(path)
199 }
200
201 pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
206 self.program = Some(path.into());
207 self
208 }
209
210 pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
212 self.port = Some(port.into());
213 self
214 }
215
216 pub fn ipc_path(mut self, path: impl Into<String>) -> Self {
218 self.ipc_path = Some(path.into());
219 self
220 }
221
222 pub const fn chain_id(mut self, chain_id: u64) -> Self {
226 self.chain_id = Some(chain_id);
227 self
228 }
229
230 pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
232 self.mnemonic = Some(mnemonic.into());
233 self
234 }
235
236 pub const fn block_time(mut self, block_time: u64) -> Self {
238 self.block_time = Some(block_time as f64);
239 self
240 }
241
242 pub const fn block_time_f64(mut self, block_time: f64) -> Self {
245 self.block_time = Some(block_time);
246 self
247 }
248
249 pub const fn fork_block_number(mut self, fork_block_number: u64) -> Self {
253 self.fork_block_number = Some(fork_block_number);
254 self
255 }
256
257 pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
262 self.fork = Some(fork.into());
263 self
264 }
265
266 pub fn hardfork(mut self, hardfork: EthereumHardfork) -> Self {
268 self = self.args(["--hardfork", hardfork.to_string().as_str()]);
269 self
270 }
271
272 pub fn paris(mut self) -> Self {
274 self = self.hardfork(EthereumHardfork::Paris);
275 self
276 }
277
278 pub fn cancun(mut self) -> Self {
280 self = self.hardfork(EthereumHardfork::Cancun);
281 self
282 }
283
284 pub fn shanghai(mut self) -> Self {
286 self = self.hardfork(EthereumHardfork::Shanghai);
287 self
288 }
289
290 pub fn prague(mut self) -> Self {
292 self = self.hardfork(EthereumHardfork::Prague);
293 self
294 }
295
296 pub fn odyssey(mut self) -> Self {
298 self = self.arg("--odyssey");
299 self
300 }
301
302 pub fn push_arg<T: Into<OsString>>(&mut self, arg: T) {
304 self.args.push(arg.into());
305 }
306
307 pub fn extend_args<I, S>(&mut self, args: I)
309 where
310 I: IntoIterator<Item = S>,
311 S: Into<OsString>,
312 {
313 for arg in args {
314 self.push_arg(arg);
315 }
316 }
317
318 pub fn arg<T: Into<OsString>>(mut self, arg: T) -> Self {
320 self.args.push(arg.into());
321 self
322 }
323
324 pub fn args<I, S>(mut self, args: I) -> Self
326 where
327 I: IntoIterator<Item = S>,
328 S: Into<OsString>,
329 {
330 for arg in args {
331 self = self.arg(arg);
332 }
333 self
334 }
335
336 pub const fn timeout(mut self, timeout: u64) -> Self {
338 self.timeout = Some(timeout);
339 self
340 }
341
342 pub const fn keep_stdout(mut self) -> Self {
346 self.keep_stdout = true;
347 self
348 }
349
350 #[track_caller]
356 pub fn spawn(self) -> AnvilInstance {
357 self.try_spawn().unwrap()
358 }
359
360 pub fn try_spawn(self) -> Result<AnvilInstance, NodeError> {
362 let mut cmd = self.program.as_ref().map_or_else(|| Command::new("anvil"), Command::new);
363 cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
364
365 cmd.env("FOUNDRY_DISABLE_NIGHTLY_WARNING", "");
367
368 let mut port = self.port.unwrap_or_default();
369 cmd.arg("-p").arg(port.to_string());
370
371 if let Some(mnemonic) = self.mnemonic {
372 cmd.arg("-m").arg(mnemonic);
373 }
374
375 if let Some(chain_id) = self.chain_id {
376 cmd.arg("--chain-id").arg(chain_id.to_string());
377 }
378
379 if let Some(block_time) = self.block_time {
380 cmd.arg("-b").arg(block_time.to_string());
381 }
382
383 if let Some(fork) = self.fork {
384 cmd.arg("-f").arg(fork);
385 }
386
387 if let Some(fork_block_number) = self.fork_block_number {
388 cmd.arg("--fork-block-number").arg(fork_block_number.to_string());
389 }
390
391 if let Some(ipc_path) = &self.ipc_path {
392 cmd.arg("--ipc").arg(ipc_path);
393 }
394
395 cmd.args(self.args);
396
397 let mut child = cmd.spawn().map_err(NodeError::SpawnError)?;
398
399 let stdout = child.stdout.take().ok_or(NodeError::NoStdout)?;
400
401 let start = Instant::now();
402 let mut reader = BufReader::new(stdout);
403
404 let mut private_keys = Vec::new();
405 let mut addresses = Vec::new();
406 let mut is_private_key = false;
407 let mut chain_id = None;
408 let mut wallet = None;
409 loop {
410 if start + Duration::from_millis(self.timeout.unwrap_or(ANVIL_STARTUP_TIMEOUT_MILLIS))
411 <= Instant::now()
412 {
413 return Err(NodeError::Timeout);
414 }
415
416 let mut line = String::new();
417 reader.read_line(&mut line).map_err(NodeError::ReadLineError)?;
418 trace!(target: "anvil", line);
419 if let Some(addr) = line.strip_prefix("Listening on") {
420 if let Ok(addr) = SocketAddr::from_str(addr.trim()) {
423 port = addr.port();
424 }
425 break;
426 }
427
428 if line.starts_with("Private Keys") {
429 is_private_key = true;
430 }
431
432 if is_private_key && line.starts_with('(') {
433 let key_str =
434 line.split("0x").last().ok_or(NodeError::ParsePrivateKeyError)?.trim();
435 let key_hex = hex::decode(key_str).map_err(NodeError::FromHexError)?;
436 let key = K256SecretKey::from_bytes((&key_hex[..]).into())
437 .map_err(|_| NodeError::DeserializePrivateKeyError)?;
438 addresses.push(Address::from_public_key(SigningKey::from(&key).verifying_key()));
439 private_keys.push(key);
440 }
441
442 if let Some(start_chain_id) = line.find("Chain ID:") {
443 let rest = &line[start_chain_id + "Chain ID:".len()..];
444 if let Ok(chain) = rest.split_whitespace().next().unwrap_or("").parse::<u64>() {
445 chain_id = Some(chain);
446 };
447 }
448
449 if !private_keys.is_empty() {
450 let mut private_keys = private_keys.iter().map(|key| {
451 let mut signer = LocalSigner::from(key.clone());
452 signer.set_chain_id(chain_id);
453 signer
454 });
455 let mut w = EthereumWallet::new(private_keys.next().unwrap());
456 for pk in private_keys {
457 w.register_signer(pk);
458 }
459 wallet = Some(w);
460 }
461 }
462
463 if self.keep_stdout {
464 child.stdout = Some(reader.into_inner());
466 }
467
468 Ok(AnvilInstance {
469 child,
470 private_keys,
471 addresses,
472 wallet,
473 ipc_path: self.ipc_path,
474 port,
475 chain_id: self.chain_id.or(chain_id),
476 })
477 }
478}
479
480#[cfg(test)]
481mod test {
482 use super::*;
483
484 #[test]
485 fn assert_block_time_is_natural_number() {
486 let anvil = Anvil::new().block_time(12);
489 assert_eq!(anvil.block_time.unwrap().to_string(), "12");
490 }
491}