use std::env;
use std::fs::canonicalize;
use std::io::ErrorKind;
use std::marker::PhantomData;
use std::path::PathBuf;
use async_process::Child;
use async_process::Command;
use async_process::ExitStatus;
use ethers_provider::providers::http;
use ethers_provider::Provider;
use ethers_signer::signer::Signer;
use ethers_signer::wallet::WalletSigner;
use ethers_wallet::{hd_wallet::bip32::DriveKey, wallet::Wallet};
use futures::executor::block_on;
use futures::executor::ThreadPool;
use once_cell::sync::OnceCell;
use crate::error::HardhatError;
#[cfg(target_family = "unix")]
pub async fn kill_process_recursive(process_id: u32) -> anyhow::Result<ExitStatus> {
let mut child = Command::new("kill")
.arg(format!("{}", process_id))
.spawn()?;
Ok(child.status().await?)
}
pub fn hardhat_command<P>(hardhat_root: P) -> anyhow::Result<Command>
where
P: Into<PathBuf>,
{
let mut command = Command::new("npx");
command.arg("hardhat");
command.current_dir(hardhat_root.into());
Ok(command)
}
pub fn thread_pool() -> &'static ThreadPool {
static POOLS: OnceCell<ThreadPool> = OnceCell::new();
POOLS.get_or_init(|| ThreadPool::new().unwrap())
}
pub fn find_manifest_dir() -> anyhow::Result<PathBuf> {
let start_dir = env::current_dir()?;
fn search(start_dir: PathBuf) -> anyhow::Result<PathBuf> {
log::trace!(target:"HARDHAT","Search manifest file in {}",start_dir.to_string_lossy());
for item in start_dir.read_dir()? {
if let Ok(item) = item {
if item.path().is_dir() {
continue;
}
if item.file_name() == "Cargo.toml" {
let path = canonicalize(start_dir)?;
log::trace!(target:"HARDHAT","found cargo manifest dir, {}",path.to_string_lossy());
return Ok(path);
}
}
}
if let Some(parent) = start_dir.parent() {
return search(parent.to_path_buf());
}
Err(HardhatError::CargoManifestDirNotFound.into())
}
search(start_dir)
}
pub fn hardhat_default_path() -> anyhow::Result<PathBuf> {
find_manifest_dir().map(|p| p.join("sol"))
}
#[async_trait::async_trait]
pub trait HardhatCommandContext {
#[allow(unused)]
fn init_command(hardhat_root: PathBuf, c: &mut Command) -> anyhow::Result<()> {
Ok(())
}
#[allow(unused)]
async fn start_command(c: &mut Child) -> anyhow::Result<()> {
Ok(())
}
#[allow(unused)]
async fn drop_command(hardhat_root: PathBuf) -> anyhow::Result<()> {
Ok(())
}
}
#[derive(Debug)]
pub struct HardhatCommand<C: HardhatCommandContext> {
hardhat_root: PathBuf,
command: Command,
child_process: Option<Child>,
_marked: PhantomData<C>,
}
impl<C> HardhatCommand<C>
where
C: HardhatCommandContext,
{
pub fn new() -> anyhow::Result<Self> {
Self::new_with(hardhat_default_path()?)
}
pub fn new_with<P>(hardhat_root: P) -> anyhow::Result<Self>
where
P: Into<PathBuf>,
{
let hardhat_root: PathBuf = hardhat_root.into();
log::debug!("hardhat start in directory: {:?}", hardhat_root);
let mut command = hardhat_command(hardhat_root.clone())?;
C::init_command(hardhat_root.clone(), &mut command)?;
Ok(Self {
hardhat_root,
child_process: None,
command,
_marked: Default::default(),
})
}
pub fn is_started(&self) -> bool {
self.child_process.is_some()
}
pub async fn start(&mut self) -> anyhow::Result<bool> {
if self.is_started() {
return Ok(false);
}
let mut child = match self.command.spawn() {
Ok(child) => child,
Err(err) => {
if err.kind() == ErrorKind::NotFound {
return Err(HardhatError::NodejsRequired.into());
} else {
return Err(err.into());
}
}
};
C::start_command(&mut child).await?;
self.child_process = Some(child);
return Ok(true);
}
pub async fn stop(&mut self) -> anyhow::Result<()> {
if let Some(child_process) = self.child_process.take() {
kill_process_recursive(child_process.id()).await?;
Ok(())
} else {
Err(HardhatError::HardhatNetworkStopped.into())
}
}
pub async fn status(&mut self) -> anyhow::Result<ExitStatus> {
if let Some(mut child_process) = self.child_process.take() {
Ok(child_process.status().await?)
} else {
Err(HardhatError::HardhatNetworkStopped.into())
}
}
}
impl<C> Drop for HardhatCommand<C>
where
C: HardhatCommandContext,
{
fn drop(&mut self) {
if self.is_started() {
let child_process = self.child_process.take().unwrap();
let hardhat_root = self.hardhat_root.clone();
block_on(async move {
let drop_result = C::drop_command(hardhat_root).await;
log::debug!("drop command result, {:?}", drop_result);
_ = kill_process_recursive(child_process.id()).await;
});
}
}
}
pub fn get_hardhat_network_account(i: usize) -> Signer {
let drive_key = DriveKey::new(
"test test test test test test test test test test test junk",
"",
);
let key = drive_key
.drive(format!("m/44'/60'/0'/0/{}", i))
.expect("Bip32 drive key");
Wallet::new(key)
.expect("Create wallet")
.try_into_signer()
.expect("Create signer error")
}
pub fn get_hardhat_network_provider() -> Provider {
http::connect_to("http://localhost:8545")
}
#[cfg(test)]
mod tests {
use super::find_manifest_dir;
#[test]
fn test_manifest_dir() {
_ = pretty_env_logger::try_init();
log::debug!("{:?}", find_manifest_dir().expect("find manifest dir"));
}
}