cbrzn_ethers_core/utils/
anvil.rs1use crate::{
2 types::{Address, Chain},
3 utils::{secret_key_to_address, unused_port},
4};
5use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
6use std::{
7 io::{BufRead, BufReader},
8 path::PathBuf,
9 process::{Child, Command},
10 time::{Duration, Instant},
11};
12
13const ANVIL_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
15
16pub struct AnvilInstance {
20 pid: Child,
21 private_keys: Vec<K256SecretKey>,
22 addresses: Vec<Address>,
23 port: u16,
24 chain_id: Option<u64>,
25}
26
27impl AnvilInstance {
28 pub fn keys(&self) -> &[K256SecretKey] {
30 &self.private_keys
31 }
32
33 pub fn addresses(&self) -> &[Address] {
35 &self.addresses
36 }
37
38 pub fn port(&self) -> u16 {
40 self.port
41 }
42
43 pub fn chain_id(&self) -> u64 {
45 self.chain_id.unwrap_or_else(|| Chain::AnvilHardhat.into())
46 }
47
48 pub fn endpoint(&self) -> String {
50 format!("http://localhost:{}", self.port)
51 }
52
53 pub fn ws_endpoint(&self) -> String {
55 format!("ws://localhost:{}", self.port)
56 }
57}
58
59impl Drop for AnvilInstance {
60 fn drop(&mut self) {
61 self.pid.kill().expect("could not kill anvil");
62 }
63}
64
65#[derive(Debug, Clone, Default)]
87pub struct Anvil {
88 program: Option<PathBuf>,
89 port: Option<u16>,
90 block_time: Option<u64>,
91 chain_id: Option<u64>,
92 mnemonic: Option<String>,
93 fork: Option<String>,
94 fork_block_number: Option<u64>,
95 args: Vec<String>,
96 timeout: Option<u64>,
97}
98
99impl Anvil {
100 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn at(path: impl Into<PathBuf>) -> Self {
130 Self::new().path(path)
131 }
132
133 #[must_use]
138 pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
139 self.program = Some(path.into());
140 self
141 }
142
143 #[must_use]
145 pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
146 self.port = Some(port.into());
147 self
148 }
149
150 #[must_use]
152 pub fn chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
153 self.chain_id = Some(chain_id.into());
154 self
155 }
156
157 #[must_use]
159 pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
160 self.mnemonic = Some(mnemonic.into());
161 self
162 }
163
164 #[must_use]
166 pub fn block_time<T: Into<u64>>(mut self, block_time: T) -> Self {
167 self.block_time = Some(block_time.into());
168 self
169 }
170
171 #[must_use]
175 pub fn fork_block_number<T: Into<u64>>(mut self, fork_block_number: T) -> Self {
176 self.fork_block_number = Some(fork_block_number.into());
177 self
178 }
179
180 #[must_use]
185 pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
186 self.fork = Some(fork.into());
187 self
188 }
189
190 #[must_use]
192 pub fn arg<T: Into<String>>(mut self, arg: T) -> Self {
193 self.args.push(arg.into());
194 self
195 }
196
197 #[must_use]
199 pub fn args<I, S>(mut self, args: I) -> Self
200 where
201 I: IntoIterator<Item = S>,
202 S: Into<String>,
203 {
204 for arg in args {
205 self = self.arg(arg);
206 }
207 self
208 }
209
210 #[must_use]
212 pub fn timeout<T: Into<u64>>(mut self, timeout: T) -> Self {
213 self.timeout = Some(timeout.into());
214 self
215 }
216
217 pub fn spawn(self) -> AnvilInstance {
220 let mut cmd = if let Some(ref prg) = self.program {
221 Command::new(prg)
222 } else {
223 Command::new("anvil")
224 };
225 cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
226 let port = if let Some(port) = self.port { port } else { unused_port() };
227 cmd.arg("-p").arg(port.to_string());
228
229 if let Some(mnemonic) = self.mnemonic {
230 cmd.arg("-m").arg(mnemonic);
231 }
232
233 if let Some(chain_id) = self.chain_id {
234 cmd.arg("--chain-id").arg(chain_id.to_string());
235 }
236
237 if let Some(block_time) = self.block_time {
238 cmd.arg("-b").arg(block_time.to_string());
239 }
240
241 if let Some(fork) = self.fork {
242 cmd.arg("-f").arg(fork);
243 }
244
245 if let Some(fork_block_number) = self.fork_block_number {
246 cmd.arg("--fork-block-number").arg(fork_block_number.to_string());
247 }
248
249 cmd.args(self.args);
250
251 let mut child = cmd.spawn().expect("couldnt start anvil");
252
253 let stdout = child.stdout.take().expect("Unable to get stdout for anvil child process");
254
255 let start = Instant::now();
256 let mut reader = BufReader::new(stdout);
257
258 let mut private_keys = Vec::new();
259 let mut addresses = Vec::new();
260 let mut is_private_key = false;
261 loop {
262 if start + Duration::from_millis(self.timeout.unwrap_or(ANVIL_STARTUP_TIMEOUT_MILLIS)) <=
263 Instant::now()
264 {
265 panic!("Timed out waiting for anvil to start. Is anvil installed?")
266 }
267
268 let mut line = String::new();
269 reader.read_line(&mut line).expect("Failed to read line from anvil process");
270 if line.contains("Listening on") {
271 break
272 }
273
274 if line.starts_with("Private Keys") {
275 is_private_key = true;
276 }
277
278 if is_private_key && line.starts_with('(') {
279 let key_str = &line[6..line.len() - 1];
280 let key_hex = hex::decode(key_str).expect("could not parse as hex");
281 let key = K256SecretKey::from_be_bytes(&key_hex).expect("did not get private key");
282 addresses.push(secret_key_to_address(&SigningKey::from(&key)));
283 private_keys.push(key);
284 }
285 }
286
287 AnvilInstance { pid: child, private_keys, addresses, port, chain_id: self.chain_id }
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn can_launch_anvil() {
297 let _ = Anvil::new().spawn();
298 }
299}