diff --git a/crates/ethers-contract/src/event.rs b/crates/ethers-contract/src/event.rs index 303935cd..684a1b53 100644 --- a/crates/ethers-contract/src/event.rs +++ b/crates/ethers-contract/src/event.rs @@ -31,7 +31,7 @@ impl<'a, 'b, P, N, D: Detokenize> Event<'a, 'b, P, N, D> { } pub fn topic0>>(mut self, topic: T) -> Self { - self.filter.topics[0] = topic.into(); + self.filter.topics[0] = Some(topic.into()); self } } diff --git a/crates/ethers-utils/src/ganache.rs b/crates/ethers-utils/src/ganache.rs new file mode 100644 index 00000000..aa3813c9 --- /dev/null +++ b/crates/ethers-utils/src/ganache.rs @@ -0,0 +1,79 @@ +use std::process::{Child, Command}; + +/// A ganache CLI instance. Will close the instance when dropped. +pub struct Ganache(Child); + +impl Drop for Ganache { + fn drop(&mut self) { + self.0.kill().expect("could not kill ganache"); + } +} + +/// Builder for launching `ganache-cli`. +/// +/// # Panics +/// +/// If `spawn` is called without `ganache-cli` being available in the user's $PATH +/// +/// # Example +/// +/// ```rust,ignore +/// use ethers_utils::ganache::GanacheBuilder; +/// let port = 8545u64; +/// let url = format!("http://localhost:{}", port).to_string(); +/// +/// let ganache = GanacheBuilder::new() +/// .port(port) +/// .mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle") +/// .spawn(); +/// +/// drop(ganache); // this will kill the instance +/// ``` +#[derive(Clone, Default)] +pub struct GanacheBuilder { + port: Option, + mnemonic: Option, +} + +impl GanacheBuilder { + /// Creates an empty Ganache builder. + /// The default port is 8545. The mnemonic is chosen randomly. + pub fn new() -> Self { + Self::default() + } + + /// Sets the port which will be used when the `ganache-cli` instance is launched. + pub fn port>(mut self, port: T) -> Self { + self.port = Some(port.into()); + self + } + + /// Sets the mnemonic which will be used when the `ganache-cli` instance is launched. + pub fn mnemonic>(mut self, mnemonic: T) -> Self { + self.mnemonic = Some(mnemonic.into()); + self + } + + /// Consumes the builder and spawns `ganache-cli` with stdout redirected + /// to /dev/null. This takes ~2 seconds to execute as it blocks while + /// waiting for `ganache-cli` to launch. + pub fn spawn(self) -> Ganache { + let mut cmd = Command::new("ganache-cli"); + cmd.stdout(std::process::Stdio::null()); + if let Some(port) = self.port { + cmd.arg("-p").arg(port.to_string()); + } + + if let Some(mnemonic) = self.mnemonic { + cmd.arg("-m").arg(mnemonic); + } + + let ganache_pid = cmd.spawn().expect("couldnt start ganache-cli"); + + // wait a couple of seconds for ganache to boot up + // TODO: Change this to poll for `port` + let sleep_time = std::time::Duration::from_secs(2); + std::thread::sleep(sleep_time); + Ganache(ganache_pid) + } +} diff --git a/crates/ethers-utils/src/lib.rs b/crates/ethers-utils/src/lib.rs index 6c804eb7..09aa48c7 100644 --- a/crates/ethers-utils/src/lib.rs +++ b/crates/ethers-utils/src/lib.rs @@ -2,6 +2,9 @@ use ethereum_types::H256; use tiny_keccak::{Hasher, Keccak}; +/// Utilities for launching a ganache-cli testnet instance +pub mod ganache; + const PREFIX: &str = "\x19Ethereum Signed Message:\n"; /// Hash a message according to EIP-191. diff --git a/crates/ethers/examples/get_logs.rs b/crates/ethers/examples/get_logs.rs index 8d885d53..3a6ea74c 100644 --- a/crates/ethers/examples/get_logs.rs +++ b/crates/ethers/examples/get_logs.rs @@ -13,7 +13,7 @@ async fn main() -> Result<()> { let filter = Filter::new() .address_str("f817796F60D268A36a57b8D2dF1B97B14C0D0E1d")? .event("ValueChanged(address,string,string)") // event name - .topic("9729a6fbefefc8f6005933898b13dc45c3a2c8b7".parse::
()?); // indexed param + .topic0("9729a6fbefefc8f6005933898b13dc45c3a2c8b7".parse::
()?); // indexed param let logs = provider.get_logs(&filter).await?; println!("Got logs: {}", serde_json::to_string(&logs).unwrap()); diff --git a/crates/ethers/examples/local_signer.rs b/crates/ethers/examples/local_signer.rs index 596b215e..d4b38dcd 100644 --- a/crates/ethers/examples/local_signer.rs +++ b/crates/ethers/examples/local_signer.rs @@ -1,16 +1,27 @@ use anyhow::Result; -use ethers::{providers::HttpProvider, signers::MainnetWallet, types::TransactionRequest}; +use ethers::{ + providers::HttpProvider, signers::MainnetWallet, types::TransactionRequest, + utils::ganache::GanacheBuilder, +}; use std::convert::TryFrom; #[tokio::main] async fn main() -> Result<()> { - // connect to the network - let provider = HttpProvider::try_from("http://localhost:8545")?; + let port = 8545u64; + let url = format!("http://localhost:{}", port).to_string(); + let _ganache = GanacheBuilder::new() + .port(port) + .mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle") + .spawn(); + // this private key belongs to the above mnemonic + let wallet: MainnetWallet = + "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse()?; - // create a wallet and connect it to the provider - let client = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7" - .parse::()? - .connect(&provider); + // connect to the network + let provider = HttpProvider::try_from(url.as_str())?; + + // connect the wallet to the provider + let client = wallet.connect(&provider); // craft the transaction let tx = TransactionRequest::new() @@ -25,8 +36,8 @@ async fn main() -> Result<()> { let receipt = client.get_transaction_receipt(tx.hash).await?; - println!("{}", serde_json::to_string(&tx)?); - println!("{}", serde_json::to_string(&receipt)?); + println!("Send tx: {}", serde_json::to_string(&tx)?); + println!("Tx receipt: {}", serde_json::to_string(&receipt)?); Ok(()) } diff --git a/crates/ethers/examples/transfer_eth.rs b/crates/ethers/examples/transfer_eth.rs index c55effe5..ad0afb79 100644 --- a/crates/ethers/examples/transfer_eth.rs +++ b/crates/ethers/examples/transfer_eth.rs @@ -2,13 +2,18 @@ use anyhow::Result; use ethers::{ providers::{networks::Any, HttpProvider}, types::{BlockNumber, TransactionRequest}, + utils::ganache::GanacheBuilder, }; use std::convert::TryFrom; #[tokio::main] async fn main() -> Result<()> { + let port = 8546u64; + let url = format!("http://localhost:{}", port).to_string(); + let _ganache = GanacheBuilder::new().port(port).spawn(); + // connect to the network - let provider = HttpProvider::::try_from("http://localhost:8545")?; + let provider = HttpProvider::::try_from(url.as_str())?; let accounts = provider.get_accounts().await?; let from = accounts[0];