Improve Ganache Flexibility (#37)
* feat(core): add more features to ganache * test(provider): choose endpoint dynamically * test(signer): choose endpoint and accounts dynamically * test(contract): choose endpoint and accounts dynamically * fix: dynamic port / accounts in examples * core(chore): fix doctest
This commit is contained in:
parent
1cfbc7b3c3
commit
bb1ac9c666
|
@ -244,15 +244,6 @@ version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cloudabi"
|
|
||||||
version = "0.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
|
@ -436,7 +427,6 @@ dependencies = [
|
||||||
"rustc-hex",
|
"rustc-hex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serial_test",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
@ -508,7 +498,6 @@ dependencies = [
|
||||||
"rustc-hex",
|
"rustc-hex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serial_test",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
|
@ -972,15 +961,6 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
|
|
||||||
dependencies = [
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
|
@ -1151,30 +1131,6 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a7fad362df89617628a7508b3e9d588ade1b0ac31aa25de168193ad999c2dd4"
|
checksum = "9a7fad362df89617628a7508b3e9d588ade1b0ac31aa25de168193ad999c2dd4"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.7.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cloudabi",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"smallvec",
|
|
||||||
"winapi 0.3.8",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
@ -1461,12 +1417,6 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scopeguard"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sct"
|
name = "sct"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -1543,28 +1493,6 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serial_test"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fef5f7c7434b2f2c598adc6f9494648a1e41274a75c0ba4056f680ae0c117fd6"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
"parking_lot",
|
|
||||||
"serial_test_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serial_test_derive"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d08338d8024b227c62bd68a12c7c9883f5c66780abaef15c550dc56f46ee6515"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|
|
@ -27,7 +27,6 @@ futures = "0.3.5"
|
||||||
ethers = { version = "0.1.3", path = "../ethers" }
|
ethers = { version = "0.1.3", path = "../ethers" }
|
||||||
tokio = { version = "0.2.21", default-features = false, features = ["macros"] }
|
tokio = { version = "0.2.21", default-features = false, features = ["macros"] }
|
||||||
serde_json = "1.0.55"
|
serde_json = "1.0.55"
|
||||||
serial_test = "0.4.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
abigen = ["ethers-contract-abigen", "ethers-contract-derive"]
|
abigen = ["ethers-contract-abigen", "ethers-contract-derive"]
|
||||||
|
|
|
@ -4,7 +4,7 @@ use ethers_core::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use ethers_contract::{Contract, ContractFactory};
|
use ethers_contract::{Contract, ContractFactory};
|
||||||
use ethers_core::utils::{Ganache, GanacheInstance, Solc};
|
use ethers_core::utils::{GanacheInstance, Solc};
|
||||||
use ethers_providers::{Http, Provider};
|
use ethers_providers::{Http, Provider};
|
||||||
use ethers_signers::{Client, Wallet};
|
use ethers_signers::{Client, Wallet};
|
||||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||||
|
@ -44,11 +44,14 @@ pub fn compile() -> (Abi, Bytes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// connects the private key to http://localhost:8545
|
/// connects the private key to http://localhost:8545
|
||||||
pub fn connect(private_key: &str) -> Arc<Client<Http, Wallet>> {
|
pub fn connect(ganache: &GanacheInstance, idx: usize) -> Arc<Client<Http, Wallet>> {
|
||||||
let provider = Provider::<Http>::try_from("http://localhost:8545")
|
let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();
|
||||||
.unwrap()
|
let wallet: Wallet = ganache.keys()[idx].clone().into();
|
||||||
.interval(Duration::from_millis(10u64));
|
Arc::new(
|
||||||
Arc::new(private_key.parse::<Wallet>().unwrap().connect(provider))
|
wallet
|
||||||
|
.connect(provider)
|
||||||
|
.interval(Duration::from_millis(10u64)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Launches a ganache instance and deploys the SimpleStorage contract
|
/// Launches a ganache instance and deploys the SimpleStorage contract
|
||||||
|
@ -56,18 +59,12 @@ pub async fn deploy(
|
||||||
client: Arc<Client<Http, Wallet>>,
|
client: Arc<Client<Http, Wallet>>,
|
||||||
abi: Abi,
|
abi: Abi,
|
||||||
bytecode: Bytes,
|
bytecode: Bytes,
|
||||||
) -> (GanacheInstance, Contract<Http, Wallet>) {
|
) -> Contract<Http, Wallet> {
|
||||||
let ganache = Ganache::new()
|
|
||||||
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
|
||||||
.spawn();
|
|
||||||
|
|
||||||
let factory = ContractFactory::new(abi, bytecode, client);
|
let factory = ContractFactory::new(abi, bytecode, client);
|
||||||
let contract = factory
|
factory
|
||||||
.deploy("initial value".to_string())
|
.deploy("initial value".to_string())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
|
||||||
(ganache, contract)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,23 +12,19 @@ mod eth_tests {
|
||||||
types::Address,
|
types::Address,
|
||||||
utils::Ganache,
|
utils::Ganache,
|
||||||
};
|
};
|
||||||
use serial_test::serial;
|
use std::{convert::TryFrom, sync::Arc};
|
||||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
|
||||||
async fn deploy_and_call_contract() {
|
async fn deploy_and_call_contract() {
|
||||||
let (abi, bytecode) = compile();
|
let (abi, bytecode) = compile();
|
||||||
|
|
||||||
// launch ganache
|
// launch ganache
|
||||||
let _ganache = Ganache::new()
|
let ganache = Ganache::new().spawn();
|
||||||
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
|
||||||
.spawn();
|
|
||||||
|
|
||||||
// Instantiate the clients. We assume that clients consume the provider and the wallet
|
// Instantiate the clients. We assume that clients consume the provider and the wallet
|
||||||
// (which makes sense), so for multi-client tests, you must clone the provider.
|
// (which makes sense), so for multi-client tests, you must clone the provider.
|
||||||
let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc");
|
let client = connect(&ganache, 0);
|
||||||
let client2 = connect("cc96601bc52293b53c4736a12af9130abf347669b3813f9ec4cafdf6991b087e");
|
let client2 = connect(&ganache, 1);
|
||||||
|
|
||||||
// create a factory which will be used to deploy instances of the contract
|
// create a factory which will be used to deploy instances of the contract
|
||||||
let factory = ContractFactory::new(abi, bytecode, client.clone());
|
let factory = ContractFactory::new(abi, bytecode, client.clone());
|
||||||
|
@ -80,11 +76,11 @@ mod eth_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
|
||||||
async fn get_past_events() {
|
async fn get_past_events() {
|
||||||
let (abi, bytecode) = compile();
|
let (abi, bytecode) = compile();
|
||||||
let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc");
|
let ganache = Ganache::new().spawn();
|
||||||
let (_ganache, contract) = deploy(client.clone(), abi, bytecode).await;
|
let client = connect(&ganache, 0);
|
||||||
|
let contract = deploy(client.clone(), abi, bytecode).await;
|
||||||
|
|
||||||
// make a call with `client2`
|
// make a call with `client2`
|
||||||
let _tx_hash = contract
|
let _tx_hash = contract
|
||||||
|
@ -109,11 +105,11 @@ mod eth_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
|
||||||
async fn watch_events() {
|
async fn watch_events() {
|
||||||
let (abi, bytecode) = compile();
|
let (abi, bytecode) = compile();
|
||||||
let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc");
|
let ganache = Ganache::new().spawn();
|
||||||
let (_ganache, contract) = deploy(client, abi, bytecode).await;
|
let client = connect(&ganache, 0);
|
||||||
|
let contract = deploy(client, abi, bytecode).await;
|
||||||
|
|
||||||
// We spawn the event listener:
|
// We spawn the event listener:
|
||||||
let mut stream = contract
|
let mut stream = contract
|
||||||
|
@ -144,17 +140,21 @@ mod eth_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
|
||||||
async fn signer_on_node() {
|
async fn signer_on_node() {
|
||||||
let (abi, bytecode) = compile();
|
let (abi, bytecode) = compile();
|
||||||
let provider = Provider::<Http>::try_from("http://localhost:8545")
|
// spawn ganache
|
||||||
|
let ganache = Ganache::new().spawn();
|
||||||
|
|
||||||
|
// connect
|
||||||
|
let provider = Provider::<Http>::try_from(ganache.endpoint())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.interval(Duration::from_millis(10u64));
|
.interval(std::time::Duration::from_millis(50u64));
|
||||||
let deployer = "3cDB3d9e1B74692Bb1E3bb5fc81938151cA64b02"
|
|
||||||
.parse::<Address>()
|
// get the first account
|
||||||
.unwrap();
|
let deployer = provider.get_accounts().await.unwrap()[0];
|
||||||
let client = Arc::new(Client::from(provider).with_sender(deployer));
|
let client = Arc::new(Client::from(provider).with_sender(deployer));
|
||||||
let (_ganache, contract) = deploy(client, abi, bytecode).await;
|
|
||||||
|
let contract = deploy(client, abi, bytecode).await;
|
||||||
|
|
||||||
// make a call without the signer
|
// make a call without the signer
|
||||||
let tx_hash = contract
|
let tx_hash = contract
|
||||||
|
|
|
@ -1,18 +1,48 @@
|
||||||
|
use crate::types::PrivateKey;
|
||||||
use std::{
|
use std::{
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
net::TcpListener,
|
||||||
process::{Child, Command},
|
process::{Child, Command},
|
||||||
time::Duration,
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
const SLEEP_TIME: Duration = Duration::from_secs(3);
|
/// How long we will wait for ganache to indicate that it is ready.
|
||||||
|
const GANACHE_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
|
||||||
|
|
||||||
/// A ganache CLI instance. Will close the instance when dropped.
|
/// A ganache CLI instance. Will close the instance when dropped.
|
||||||
///
|
///
|
||||||
/// Construct this using [`Ganache`](crate::utils::Ganache)
|
/// Construct this using [`Ganache`](crate::utils::Ganache)
|
||||||
pub struct GanacheInstance(Child);
|
pub struct GanacheInstance {
|
||||||
|
pid: Child,
|
||||||
|
private_keys: Vec<PrivateKey>,
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GanacheInstance {
|
||||||
|
/// Returns the private keys used to instantiate this instance
|
||||||
|
pub fn keys(&self) -> &[PrivateKey] {
|
||||||
|
&self.private_keys
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the port of this instance
|
||||||
|
pub fn port(&self) -> u16 {
|
||||||
|
self.port
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the HTTP endpoint of this instance
|
||||||
|
pub fn endpoint(&self) -> String {
|
||||||
|
format!("http://localhost:{}", self.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the Websocket endpoint of this instance
|
||||||
|
pub fn ws_endpoint(&self) -> String {
|
||||||
|
format!("ws://localhost:{}", self.port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for GanacheInstance {
|
impl Drop for GanacheInstance {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.0.kill().expect("could not kill ganache");
|
let _ = self.pid.kill().expect("could not kill ganache");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +57,7 @@ impl Drop for GanacheInstance {
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use ethers::utils::Ganache;
|
/// use ethers::utils::Ganache;
|
||||||
///
|
///
|
||||||
/// let port = 8545u64;
|
/// let port = 8545u16;
|
||||||
/// let url = format!("http://localhost:{}", port).to_string();
|
/// let url = format!("http://localhost:{}", port).to_string();
|
||||||
///
|
///
|
||||||
/// let ganache = Ganache::new()
|
/// let ganache = Ganache::new()
|
||||||
|
@ -39,7 +69,7 @@ impl Drop for GanacheInstance {
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Ganache {
|
pub struct Ganache {
|
||||||
port: Option<u64>,
|
port: Option<u16>,
|
||||||
block_time: Option<u64>,
|
block_time: Option<u64>,
|
||||||
mnemonic: Option<String>,
|
mnemonic: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -52,7 +82,7 @@ impl Ganache {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the port which will be used when the `ganache-cli` instance is launched.
|
/// 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 {
|
pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
|
||||||
self.port = Some(port.into());
|
self.port = Some(port.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -74,10 +104,13 @@ impl Ganache {
|
||||||
/// waiting for `ganache-cli` to launch.
|
/// waiting for `ganache-cli` to launch.
|
||||||
pub fn spawn(self) -> GanacheInstance {
|
pub fn spawn(self) -> GanacheInstance {
|
||||||
let mut cmd = Command::new("ganache-cli");
|
let mut cmd = Command::new("ganache-cli");
|
||||||
cmd.stdout(std::process::Stdio::null());
|
cmd.stdout(std::process::Stdio::piped());
|
||||||
if let Some(port) = self.port {
|
let port = if let Some(port) = self.port {
|
||||||
|
port
|
||||||
|
} else {
|
||||||
|
unused_port()
|
||||||
|
};
|
||||||
cmd.arg("-p").arg(port.to_string());
|
cmd.arg("-p").arg(port.to_string());
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mnemonic) = self.mnemonic {
|
if let Some(mnemonic) = self.mnemonic {
|
||||||
cmd.arg("-m").arg(mnemonic);
|
cmd.arg("-m").arg(mnemonic);
|
||||||
|
@ -87,10 +120,61 @@ impl Ganache {
|
||||||
cmd.arg("-b").arg(block_time.to_string());
|
cmd.arg("-b").arg(block_time.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let ganache_pid = cmd.spawn().expect("couldnt start ganache-cli");
|
let mut child = cmd.spawn().expect("couldnt start ganache-cli");
|
||||||
|
|
||||||
// wait a couple of seconds for ganache to boot up
|
let stdout = child
|
||||||
std::thread::sleep(SLEEP_TIME);
|
.stdout
|
||||||
GanacheInstance(ganache_pid)
|
.expect("Unable to get stdout for ganache child process");
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut reader = BufReader::new(stdout);
|
||||||
|
|
||||||
|
let mut private_keys = Vec::new();
|
||||||
|
let mut is_private_key = false;
|
||||||
|
loop {
|
||||||
|
if start + Duration::from_millis(GANACHE_STARTUP_TIMEOUT_MILLIS) <= Instant::now() {
|
||||||
|
panic!("Timed out waiting for ganache to start. Is ganache-cli installed?")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut line = String::new();
|
||||||
|
reader
|
||||||
|
.read_line(&mut line)
|
||||||
|
.expect("Failed to read line from ganache process");
|
||||||
|
if line.starts_with("Listening on") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.starts_with("Private Keys") {
|
||||||
|
is_private_key = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_private_key && line.starts_with('(') {
|
||||||
|
let key_str = &line[6..line.len() - 1];
|
||||||
|
let key: PrivateKey = key_str.parse().expect("did not get private key");
|
||||||
|
private_keys.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
child.stdout = Some(reader.into_inner());
|
||||||
|
|
||||||
|
GanacheInstance {
|
||||||
|
pid: child,
|
||||||
|
private_keys,
|
||||||
|
port,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A bit of hack to find an unused TCP port.
|
||||||
|
///
|
||||||
|
/// Does not guarantee that the given port is unused after the function exists, just that it was
|
||||||
|
/// unused before the function started (i.e., it does not reserve a port).
|
||||||
|
pub fn unused_port() -> u16 {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0")
|
||||||
|
.expect("Failed to create TCP listener to find unused port");
|
||||||
|
|
||||||
|
let local_addr = listener
|
||||||
|
.local_addr()
|
||||||
|
.expect("Failed to read TCP listener local_addr to find unused port");
|
||||||
|
local_addr.port()
|
||||||
|
}
|
||||||
|
|
|
@ -45,9 +45,11 @@ rustc-hex = "2.1.0"
|
||||||
tokio = { version = "0.2.21", default-features = false, features = ["rt-core", "macros"] }
|
tokio = { version = "0.2.21", default-features = false, features = ["rt-core", "macros"] }
|
||||||
async-std = { version = "1.6.2", default-features = false, features = ["attributes"] }
|
async-std = { version = "1.6.2", default-features = false, features = ["attributes"] }
|
||||||
async-tungstenite = { version = "0.6.0", features = ["tokio-runtime"] }
|
async-tungstenite = { version = "0.6.0", features = ["tokio-runtime"] }
|
||||||
serial_test = "0.4.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
# slightly opinionated, but for convenience we default to tokio-tls
|
||||||
|
# to allow websockets w/ TLS support
|
||||||
|
default = ["tokio-tls"]
|
||||||
celo = ["ethers-core/celo"]
|
celo = ["ethers-core/celo"]
|
||||||
|
|
||||||
tokio-runtime = [
|
tokio-runtime = [
|
||||||
|
|
|
@ -534,6 +534,14 @@ impl TryFrom<&str> for Provider<HttpProvider> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Provider<HttpProvider> {
|
||||||
|
type Error = ParseError;
|
||||||
|
|
||||||
|
fn try_from(src: String) -> Result<Self, Self::Error> {
|
||||||
|
Provider::try_from(src.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod ens_tests {
|
mod ens_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -10,7 +10,6 @@ mod eth_tests {
|
||||||
types::TransactionRequest,
|
types::TransactionRequest,
|
||||||
utils::{parse_ether, Ganache},
|
utils::{parse_ether, Ganache},
|
||||||
};
|
};
|
||||||
use serial_test::serial;
|
|
||||||
|
|
||||||
// Without TLS this would error with "TLS Support not compiled in"
|
// Without TLS this would error with "TLS Support not compiled in"
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -36,7 +35,6 @@ mod eth_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
|
||||||
#[cfg(feature = "tokio-runtime")]
|
#[cfg(feature = "tokio-runtime")]
|
||||||
async fn watch_blocks_websocket() {
|
async fn watch_blocks_websocket() {
|
||||||
use ethers::{
|
use ethers::{
|
||||||
|
@ -44,8 +42,8 @@ mod eth_tests {
|
||||||
types::H256,
|
types::H256,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ganache = Ganache::new().block_time(2u64).spawn();
|
let ganache = Ganache::new().block_time(2u64).spawn();
|
||||||
let (ws, _) = async_tungstenite::tokio::connect_async("ws://localhost:8545")
|
let (ws, _) = async_tungstenite::tokio::connect_async(ganache.ws_endpoint())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let provider = Provider::new(Ws::new(ws)).interval(Duration::from_millis(500u64));
|
let provider = Provider::new(Ws::new(ws)).interval(Duration::from_millis(500u64));
|
||||||
|
@ -57,22 +55,20 @@ mod eth_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
|
||||||
async fn pending_txs_with_confirmations_ganache() {
|
async fn pending_txs_with_confirmations_ganache() {
|
||||||
let _ganache = Ganache::new().block_time(2u64).spawn();
|
let ganache = Ganache::new().block_time(2u64).spawn();
|
||||||
let provider = Provider::<Http>::try_from("http://localhost:8545")
|
let provider = Provider::<Http>::try_from(ganache.endpoint())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.interval(Duration::from_millis(500u64));
|
.interval(Duration::from_millis(500u64));
|
||||||
generic_pending_txs_test(provider).await;
|
generic_pending_txs_test(provider).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
|
||||||
#[cfg(any(feature = "tokio-runtime", feature = "tokio-tls"))]
|
#[cfg(any(feature = "tokio-runtime", feature = "tokio-tls"))]
|
||||||
async fn websocket_pending_txs_with_confirmations_ganache() {
|
async fn websocket_pending_txs_with_confirmations_ganache() {
|
||||||
use ethers::providers::Ws;
|
use ethers::providers::Ws;
|
||||||
let _ganache = Ganache::new().block_time(2u64).port(8546u64).spawn();
|
let ganache = Ganache::new().block_time(2u64).spawn();
|
||||||
let ws = Ws::connect("ws://localhost:8546").await.unwrap();
|
let ws = Ws::connect(ganache.ws_endpoint()).await.unwrap();
|
||||||
let provider = Provider::new(ws);
|
let provider = Provider::new(ws);
|
||||||
generic_pending_txs_test(provider).await;
|
generic_pending_txs_test(provider).await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,12 @@ impl Wallet {
|
||||||
pub fn chain_id(&self) -> Option<u64> {
|
pub fn chain_id(&self) -> Option<u64> {
|
||||||
self.chain_id
|
self.chain_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the wallet's address
|
||||||
|
// (we duplicate this method
|
||||||
|
pub fn address(&self) -> Address {
|
||||||
|
self.address
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PrivateKey> for Wallet {
|
impl From<PrivateKey> for Wallet {
|
||||||
|
|
|
@ -45,20 +45,14 @@ mod eth_tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn send_eth() {
|
async fn send_eth() {
|
||||||
let port = 8545u64;
|
let ganache = Ganache::new().spawn();
|
||||||
let url = format!("http://localhost:{}", port).to_string();
|
|
||||||
let _ganache = Ganache::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
|
// this private key belongs to the above mnemonic
|
||||||
let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
|
let wallet: Wallet = ganache.keys()[0].clone().into();
|
||||||
.parse()
|
let wallet2: Wallet = ganache.keys()[1].clone().into();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// connect to the network
|
// connect to the network
|
||||||
let provider = Provider::<Http>::try_from(url.as_str())
|
let provider = Provider::<Http>::try_from(ganache.endpoint())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.interval(Duration::from_millis(10u64));
|
.interval(Duration::from_millis(10u64));
|
||||||
|
|
||||||
|
@ -66,10 +60,7 @@ mod eth_tests {
|
||||||
let client = wallet.connect(provider);
|
let client = wallet.connect(provider);
|
||||||
|
|
||||||
// craft the transaction
|
// craft the transaction
|
||||||
let tx = TransactionRequest::new()
|
let tx = TransactionRequest::new().to(wallet2.address()).value(10000);
|
||||||
.send_to_str("986eE0C8B91A58e490Ee59718Cca41056Cf55f24")
|
|
||||||
.unwrap()
|
|
||||||
.value(10000);
|
|
||||||
|
|
||||||
let balance_before = client.get_balance(client.address(), None).await.unwrap();
|
let balance_before = client.get_balance(client.address(), None).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -21,18 +21,14 @@ async fn main() -> Result<()> {
|
||||||
.expect("could not find contract");
|
.expect("could not find contract");
|
||||||
|
|
||||||
// 2. launch ganache
|
// 2. launch ganache
|
||||||
let port = 8546u64;
|
let ganache = Ganache::new().spawn();
|
||||||
let url = format!("http://localhost:{}", port).to_string();
|
|
||||||
let _ganache = Ganache::new().port(port)
|
|
||||||
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
|
||||||
.spawn();
|
|
||||||
|
|
||||||
// 3. instantiate our wallet
|
// 3. instantiate our wallet
|
||||||
let wallet =
|
let wallet: Wallet = ganache.keys()[0].clone().into();
|
||||||
"380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::<Wallet>()?;
|
|
||||||
|
|
||||||
// 4. connect to the network
|
// 4. connect to the network
|
||||||
let provider = Provider::<Http>::try_from(url.as_str())?.interval(Duration::from_millis(10u64));
|
let provider =
|
||||||
|
Provider::<Http>::try_from(ganache.endpoint())?.interval(Duration::from_millis(10u64));
|
||||||
|
|
||||||
// 5. instantiate the client with the wallet
|
// 5. instantiate the client with the wallet
|
||||||
let client = wallet.connect(provider);
|
let client = wallet.connect(provider);
|
||||||
|
|
|
@ -4,27 +4,19 @@ use std::convert::TryFrom;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let port = 8545u64;
|
let ganache = Ganache::new().spawn();
|
||||||
let url = format!("http://localhost:{}", port).to_string();
|
|
||||||
let _ganache = Ganache::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: Wallet = ganache.keys()[0].clone().into();
|
||||||
let wallet: Wallet =
|
let wallet2: Wallet = ganache.keys()[1].clone().into();
|
||||||
"380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse()?;
|
|
||||||
|
|
||||||
// connect to the network
|
// connect to the network
|
||||||
let provider = Provider::<Http>::try_from(url.as_str())?;
|
let provider = Provider::<Http>::try_from(ganache.endpoint())?;
|
||||||
|
|
||||||
// connect the wallet to the provider
|
// connect the wallet to the provider
|
||||||
let client = wallet.connect(provider);
|
let client = wallet.connect(provider);
|
||||||
|
|
||||||
// craft the transaction
|
// craft the transaction
|
||||||
let tx = TransactionRequest::new()
|
let tx = TransactionRequest::new().to(wallet2.address()).value(10000);
|
||||||
.send_to_str("986eE0C8B91A58e490Ee59718Cca41056Cf55f24")?
|
|
||||||
.value(10000);
|
|
||||||
|
|
||||||
// send it!
|
// send it!
|
||||||
let tx_hash = client.send_transaction(tx, None).await?;
|
let tx_hash = client.send_transaction(tx, None).await?;
|
||||||
|
|
|
@ -4,20 +4,16 @@ use std::convert::TryFrom;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let port = 8546u64;
|
let ganache = Ganache::new().spawn();
|
||||||
let url = format!("http://localhost:{}", port).to_string();
|
|
||||||
let _ganache = Ganache::new().port(port).spawn();
|
|
||||||
|
|
||||||
// connect to the network
|
// connect to the network
|
||||||
let provider = Provider::<Http>::try_from(url.as_str())?;
|
let provider = Provider::<Http>::try_from(ganache.endpoint())?;
|
||||||
let accounts = provider.get_accounts().await?;
|
let accounts = provider.get_accounts().await?;
|
||||||
let from = accounts[0];
|
let from = accounts[0];
|
||||||
|
let to = accounts[1];
|
||||||
|
|
||||||
// craft the tx
|
// craft the tx
|
||||||
let tx = TransactionRequest::new()
|
let tx = TransactionRequest::new().to(to).value(1000).from(from); // specify the `from` field so that the client knows which account to use
|
||||||
.send_to_str("9A7e5d4bcA656182e66e33340d776D1542143006")?
|
|
||||||
.value(1000)
|
|
||||||
.from(from); // specify the `from` field so that the client knows which account to use
|
|
||||||
|
|
||||||
let balance_before = provider.get_balance(from, None).await?;
|
let balance_before = provider.get_balance(from, None).await?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue