ethers_hardhat_rs/
utils.rs1use std::env;
2use std::fs::canonicalize;
3use std::io::ErrorKind;
4use std::marker::PhantomData;
5use std::path::PathBuf;
6
7use async_process::Child;
8use async_process::Command;
9use async_process::ExitStatus;
10
11use ethers_providers_rs::providers::http;
12use ethers_providers_rs::Provider;
13use ethers_signer_rs::signer::Signer;
14use ethers_signer_rs::wallet::WalletSigner;
15use ethers_wallet_rs::{hd_wallet::bip32::DriveKey, wallet::Wallet};
16use futures::executor::block_on;
17use futures::executor::ThreadPool;
18use once_cell::sync::OnceCell;
19
20use crate::error::HardhatError;
21
22#[cfg(target_family = "unix")]
24pub async fn kill_process_recursive(process_id: u32) -> anyhow::Result<ExitStatus> {
25 let mut child = Command::new("kill")
26 .arg(format!("{}", process_id))
27 .spawn()?;
28
29 Ok(child.status().await?)
30}
31
32pub fn hardhat_command<P>(hardhat_root: P) -> anyhow::Result<Command>
34where
35 P: Into<PathBuf>,
36{
37 let mut command = Command::new("npx");
38
39 command.arg("hardhat");
40
41 command.current_dir(hardhat_root.into());
42
43 Ok(command)
44}
45
46pub fn thread_pool() -> &'static ThreadPool {
48 static POOLS: OnceCell<ThreadPool> = OnceCell::new();
49
50 POOLS.get_or_init(|| ThreadPool::new().unwrap())
51}
52pub fn find_manifest_dir() -> anyhow::Result<PathBuf> {
54 let start_dir = env::current_dir()?;
55
56 fn search(start_dir: PathBuf) -> anyhow::Result<PathBuf> {
57 log::trace!(target:"HARDHAT","Search manifest file in {}",start_dir.to_string_lossy());
58
59 for item in start_dir.read_dir()? {
60 if let Ok(item) = item {
61 if item.path().is_dir() {
62 continue;
63 }
64
65 if item.file_name() == "Cargo.toml" {
66 let path = canonicalize(start_dir)?;
67
68 log::trace!(target:"HARDHAT","found cargo manifest dir, {}",path.to_string_lossy());
69
70 return Ok(path);
71 }
72 }
73 }
74
75 if let Some(parent) = start_dir.parent() {
76 return search(parent.to_path_buf());
77 }
78
79 Err(HardhatError::CargoManifestDirNotFound.into())
80 }
81
82 search(start_dir)
83}
84
85pub fn hardhat_default_path() -> anyhow::Result<PathBuf> {
87 find_manifest_dir().map(|p| p.join("sol"))
88}
89
90#[async_trait::async_trait]
91pub trait HardhatCommandContext {
92 #[allow(unused)]
95 fn init_command(hardhat_root: PathBuf, c: &mut Command) -> anyhow::Result<()> {
96 Ok(())
97 }
98
99 #[allow(unused)]
100 async fn start_command(c: &mut Child) -> anyhow::Result<()> {
101 Ok(())
102 }
103
104 #[allow(unused)]
105 async fn drop_command(hardhat_root: PathBuf) -> anyhow::Result<()> {
106 Ok(())
107 }
108}
109
110#[derive(Debug)]
111pub struct HardhatCommand<C: HardhatCommandContext> {
112 hardhat_root: PathBuf,
113 command: Command,
115 child_process: Option<Child>,
117
118 _marked: PhantomData<C>,
119}
120
121impl<C> HardhatCommand<C>
122where
123 C: HardhatCommandContext,
124{
125 pub fn new() -> anyhow::Result<Self> {
126 Self::new_with(hardhat_default_path()?)
127 }
128 pub fn new_with<P>(hardhat_root: P) -> anyhow::Result<Self>
130 where
131 P: Into<PathBuf>,
132 {
133 let hardhat_root: PathBuf = hardhat_root.into();
134
135 let mut command = hardhat_command(hardhat_root.clone())?;
136
137 C::init_command(hardhat_root.clone(), &mut command)?;
138
139 Ok(Self {
140 hardhat_root,
141 child_process: None,
142 command,
143 _marked: Default::default(),
144 })
145 }
146
147 pub fn is_started(&self) -> bool {
149 self.child_process.is_some()
150 }
151
152 pub async fn start(&mut self) -> anyhow::Result<bool> {
156 if self.is_started() {
157 return Ok(false);
158 }
159
160 let mut child = match self.command.spawn() {
161 Ok(child) => child,
162 Err(err) => {
163 if err.kind() == ErrorKind::NotFound {
164 return Err(HardhatError::NodejsRequired.into());
165 } else {
166 return Err(err.into());
167 }
168 }
169 };
170
171 C::start_command(&mut child).await?;
172
173 self.child_process = Some(child);
174
175 return Ok(true);
176 }
177
178 pub async fn stop(&mut self) -> anyhow::Result<()> {
182 if let Some(child_process) = self.child_process.take() {
183 kill_process_recursive(child_process.id()).await?;
184 Ok(())
185 } else {
186 Err(HardhatError::HardhatNetworkStopped.into())
187 }
188 }
189
190 pub async fn status(&mut self) -> anyhow::Result<ExitStatus> {
192 if let Some(mut child_process) = self.child_process.take() {
193 Ok(child_process.status().await?)
194 } else {
195 Err(HardhatError::HardhatNetworkStopped.into())
196 }
197 }
198}
199
200impl<C> Drop for HardhatCommand<C>
201where
202 C: HardhatCommandContext,
203{
204 fn drop(&mut self) {
205 if self.is_started() {
206 let child_process = self.child_process.take().unwrap();
207 let hardhat_root = self.hardhat_root.clone();
208
209 block_on(async move {
210 let drop_result = C::drop_command(hardhat_root).await;
211
212 log::debug!("drop command result, {:?}", drop_result);
213
214 _ = kill_process_recursive(child_process.id()).await;
215 });
216 }
217 }
218}
219
220pub fn get_hardhat_network_account(i: usize) -> Signer {
222 let drive_key = DriveKey::new(
223 "test test test test test test test test test test test junk",
224 "",
225 );
226 let key = drive_key
227 .drive(format!("m/44'/60'/0'/0/{}", i))
228 .expect("Bip32 drive key");
229
230 Wallet::new(key.private_key)
231 .expect("Create wallet")
232 .try_into_signer()
233 .expect("Create signer error")
234}
235
236pub fn get_hardhat_network_provider() -> Provider {
238 http::connect_to("http://localhost:8545")
239}
240
241#[cfg(test)]
242mod tests {
243
244 use super::find_manifest_dir;
245
246 #[test]
247 fn test_manifest_dir() {
248 _ = pretty_env_logger::try_init();
249
250 log::debug!("{:?}", find_manifest_dir().expect("find manifest dir"));
251 }
252}