use evmlib::testnet::Testnet;
use evmlib::wallet::Wallet;
use evmlib::Network as EvmNetwork;
use tracing::{debug, info};
#[derive(Debug, thiserror::Error)]
pub enum AnvilError {
#[error("Failed to start Anvil: {0}")]
Startup(String),
#[error("Anvil health check failed: {0}")]
HealthCheck(String),
#[error("Contract deployment failed: {0}")]
ContractDeployment(String),
}
pub type Result<T> = std::result::Result<T, AnvilError>;
pub struct TestAnvil {
testnet: Testnet,
}
impl TestAnvil {
pub async fn new() -> Result<Self> {
info!("Starting Anvil EVM testnet");
let testnet = Testnet::new()
.await
.map_err(|e| AnvilError::Startup(format!("Failed to start Anvil testnet: {e}")))?;
info!("Anvil testnet started");
Ok(Self { testnet })
}
#[must_use]
pub fn to_network(&self) -> EvmNetwork {
self.testnet.to_network()
}
#[must_use]
pub fn testnet(&self) -> &Testnet {
&self.testnet
}
pub fn default_wallet_key(&self) -> Result<String> {
self.testnet
.default_wallet_private_key()
.map_err(|e| AnvilError::Startup(format!("Failed to get wallet key: {e}")))
}
pub fn create_funded_wallet(&self) -> Result<Wallet> {
let network = self.testnet.to_network();
let private_key = self
.testnet
.default_wallet_private_key()
.map_err(|e| AnvilError::Startup(format!("Failed to get wallet key: {e}")))?;
let wallet = Wallet::new_from_private_key(network, &private_key)
.map_err(|e| AnvilError::Startup(format!("Failed to create funded wallet: {e}")))?;
debug!("Created funded wallet with address: {}", wallet.address());
Ok(wallet)
}
pub fn create_empty_wallet(&self) -> Result<Wallet> {
let network = self.testnet.to_network();
let random_key = format!("0x{}", hex::encode(rand::random::<[u8; 32]>()));
let wallet = Wallet::new_from_private_key(network, &random_key)
.map_err(|e| AnvilError::Startup(format!("Failed to create empty wallet: {e}")))?;
debug!(
"Created empty wallet (no funds) with address: {}",
wallet.address()
);
Ok(wallet)
}
#[must_use]
pub fn into_testnet(self) -> Testnet {
self.testnet
}
pub async fn shutdown(&mut self) {
info!("Shutting down Anvil testnet");
}
}
#[allow(dead_code)]
pub fn create_funded_wallet_for_network(network: &EvmNetwork, private_key: &str) -> Result<Wallet> {
let wallet = Wallet::new_from_private_key(network.clone(), private_key)
.map_err(|e| AnvilError::Startup(format!("Failed to create funded wallet: {e}")))?;
debug!(
"Created funded wallet for explicit network: {}",
wallet.address()
);
Ok(wallet)
}
pub mod test_accounts {
pub const ACCOUNT_0: &str = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
pub const ACCOUNT_0_KEY: &str =
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
#[allow(dead_code)]
pub const ACCOUNT_1: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
#[allow(dead_code)]
pub const ACCOUNT_1_KEY: &str =
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
#[allow(dead_code)]
pub const ACCOUNT_2: &str = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC";
#[allow(dead_code)]
pub const ACCOUNT_2_KEY: &str =
"0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a";
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_anvil_creation() {
let anvil = TestAnvil::new().await.unwrap();
let _network = anvil.to_network();
assert!(!anvil.default_wallet_key().unwrap().is_empty());
}
#[test]
fn test_account_constants() {
assert!(test_accounts::ACCOUNT_0.starts_with("0x"));
assert!(test_accounts::ACCOUNT_0_KEY.starts_with("0x"));
}
}