feat: Adds support for launching ganache-cli

This commit is contained in:
Georgios Konstantopoulos 2020-05-30 17:11:51 +03:00
parent 2bba40a788
commit 7a17b2dec2
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
6 changed files with 110 additions and 12 deletions

View File

@ -31,7 +31,7 @@ impl<'a, 'b, P, N, D: Detokenize> Event<'a, 'b, P, N, D> {
} }
pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self { pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
self.filter.topics[0] = topic.into(); self.filter.topics[0] = Some(topic.into());
self self
} }
} }

View File

@ -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<u64>,
mnemonic: Option<String>,
}
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<T: Into<u64>>(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<T: Into<String>>(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)
}
}

View File

@ -2,6 +2,9 @@
use ethereum_types::H256; use ethereum_types::H256;
use tiny_keccak::{Hasher, Keccak}; use tiny_keccak::{Hasher, Keccak};
/// Utilities for launching a ganache-cli testnet instance
pub mod ganache;
const PREFIX: &str = "\x19Ethereum Signed Message:\n"; const PREFIX: &str = "\x19Ethereum Signed Message:\n";
/// Hash a message according to EIP-191. /// Hash a message according to EIP-191.

View File

@ -13,7 +13,7 @@ async fn main() -> Result<()> {
let filter = Filter::new() let filter = Filter::new()
.address_str("f817796F60D268A36a57b8D2dF1B97B14C0D0E1d")? .address_str("f817796F60D268A36a57b8D2dF1B97B14C0D0E1d")?
.event("ValueChanged(address,string,string)") // event name .event("ValueChanged(address,string,string)") // event name
.topic("9729a6fbefefc8f6005933898b13dc45c3a2c8b7".parse::<Address>()?); // indexed param .topic0("9729a6fbefefc8f6005933898b13dc45c3a2c8b7".parse::<Address>()?); // indexed param
let logs = provider.get_logs(&filter).await?; let logs = provider.get_logs(&filter).await?;
println!("Got logs: {}", serde_json::to_string(&logs).unwrap()); println!("Got logs: {}", serde_json::to_string(&logs).unwrap());

View File

@ -1,16 +1,27 @@
use anyhow::Result; 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; use std::convert::TryFrom;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
// connect to the network let port = 8545u64;
let provider = HttpProvider::try_from("http://localhost:8545")?; 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 // connect to the network
let client = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7" let provider = HttpProvider::try_from(url.as_str())?;
.parse::<MainnetWallet>()?
.connect(&provider); // connect the wallet to the provider
let client = wallet.connect(&provider);
// craft the transaction // craft the transaction
let tx = TransactionRequest::new() let tx = TransactionRequest::new()
@ -25,8 +36,8 @@ async fn main() -> Result<()> {
let receipt = client.get_transaction_receipt(tx.hash).await?; let receipt = client.get_transaction_receipt(tx.hash).await?;
println!("{}", serde_json::to_string(&tx)?); println!("Send tx: {}", serde_json::to_string(&tx)?);
println!("{}", serde_json::to_string(&receipt)?); println!("Tx receipt: {}", serde_json::to_string(&receipt)?);
Ok(()) Ok(())
} }

View File

@ -2,13 +2,18 @@ use anyhow::Result;
use ethers::{ use ethers::{
providers::{networks::Any, HttpProvider}, providers::{networks::Any, HttpProvider},
types::{BlockNumber, TransactionRequest}, types::{BlockNumber, TransactionRequest},
utils::ganache::GanacheBuilder,
}; };
use std::convert::TryFrom; use std::convert::TryFrom;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { 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 // connect to the network
let provider = HttpProvider::<Any>::try_from("http://localhost:8545")?; let provider = HttpProvider::<Any>::try_from(url.as_str())?;
let accounts = provider.get_accounts().await?; let accounts = provider.get_accounts().await?;
let from = accounts[0]; let from = accounts[0];