diff --git a/Cargo.lock b/Cargo.lock index a0c4aa48..fefd0e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -693,6 +693,7 @@ dependencies = [ "ethereum-types", "ethers", "funty", + "futures-util", "generic-array", "glob", "hex", @@ -704,6 +705,7 @@ dependencies = [ "serde_json", "thiserror", "tiny-keccak", + "tokio", ] [[package]] diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index 34c9aff0..578efb0a 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -36,6 +36,9 @@ hex = { version = "0.4.3", default-features = false, features = ["std"] } # https://github.com/bitvecto-rs/bitvec/issues/105#issuecomment-778570981 funty = "=1.1.0" +# async +tokio = { version = "1.2", default-features = false, optional = true} +futures-util = { version = "0.3.13", default-features = false, optional = true} [dev-dependencies] ethers = { version = "0.2", path = "../ethers" } @@ -47,6 +50,7 @@ once_cell = { version = "1.7.2" } [features] celo = [] # celo support extends the transaction format with extra fields +setup = ["tokio", "futures-util"] # async support for concurrent setup [package.metadata.docs.rs] all-features = true diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 5c784526..f095c92c 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -17,6 +17,13 @@ mod solc; #[cfg(not(target_arch = "wasm32"))] pub use solc::{CompiledContract, Solc}; +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "setup")] +mod setup; +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "setup")] +pub use setup::*; + mod hash; pub use hash::{hash_message, id, keccak256, serialize}; diff --git a/ethers-core/src/utils/setup.rs b/ethers-core/src/utils/setup.rs new file mode 100644 index 00000000..952fcaed --- /dev/null +++ b/ethers-core/src/utils/setup.rs @@ -0,0 +1,54 @@ +//! Setup utilities to start necessary infrastructure + +use crate::utils::solc::{CompiledContract, SolcError}; +use crate::utils::{Ganache, GanacheInstance, Geth, GethInstance, Solc}; +use std::collections::HashMap; + +/// Builds the contracts and returns a hashmap for each named contract +/// +/// Same as [crate::utils::Solc::build] but async +pub async fn compile(solc: Solc) -> Result, SolcError> { + tokio::task::spawn_blocking(|| solc.build()).await.unwrap() +} + +/// Launches a [crate::utils::GanacheInstance] +/// +/// Same as [crate::utils::Ganache::spawn] but async +pub async fn launch_ganache(ganache: Ganache) -> GanacheInstance { + tokio::task::spawn_blocking(|| ganache.spawn()) + .await + .unwrap() +} + +/// Compiles the contracts and launches a [crate::utils::GanacheInstance] +/// +/// Same as [crate::utils::setup::compile] and [crate::utils::setup::launch_ganache] +pub async fn compile_and_launch_ganache( + solc: Solc, + ganache: Ganache, +) -> Result<(HashMap, GanacheInstance), SolcError> { + let solc_fut = compile(solc); + let ganache_fut = launch_ganache(ganache); + let (solc, ganache) = futures_util::join!(solc_fut, ganache_fut); + solc.map(|solc| (solc, ganache)) +} + +/// Launches a [crate::utils::GethInstance] +/// +/// Same as [crate::utils::Geth::spawn] but async +pub async fn launch_geth(geth: Geth) -> GethInstance { + tokio::task::spawn_blocking(|| geth.spawn()).await.unwrap() +} + +/// Compiles the contracts and launches a [crate::utils::GethInstance] +/// +/// Same as [crate::utils::setup::compile] and [crate::utils::setup::launch_geth] +pub async fn compile_and_launch_geth( + solc: Solc, + geth: Geth, +) -> Result<(HashMap, GethInstance), SolcError> { + let solc_fut = compile(solc); + let geth_fut = launch_geth(geth); + let (solc, geth) = futures_util::join!(solc_fut, geth_fut); + solc.map(|solc| (solc, geth)) +} diff --git a/ethers/Cargo.toml b/ethers/Cargo.toml index 25b8a72f..ed664243 100644 --- a/ethers/Cargo.toml +++ b/ethers/Cargo.toml @@ -35,7 +35,7 @@ abigen = ["ethers-contract/abigen"] [dependencies] ethers-contract = { version = "0.2.2", path = "../ethers-contract" } -ethers-core = { version = "0.2.2", path = "../ethers-core" } +ethers-core = { version = "0.2.2", path = "../ethers-core", features = ["setup"] } ethers-providers = { version = "0.2.2", path = "../ethers-providers" } ethers-signers = { version = "0.2.2", path = "../ethers-signers" } ethers-middleware = { version = "0.2.2", path = "../ethers-middleware" } diff --git a/ethers/examples/contract_human_readable.rs b/ethers/examples/contract_human_readable.rs index ef3c0b52..7c6f43cc 100644 --- a/ethers/examples/contract_human_readable.rs +++ b/ethers/examples/contract_human_readable.rs @@ -1,7 +1,7 @@ use anyhow::Result; use ethers::{ prelude::*, - utils::{Ganache, Solc}, + utils::{compile_and_launch_ganache, Ganache, Solc}, }; use std::{convert::TryFrom, sync::Arc, time::Duration}; @@ -19,54 +19,52 @@ abigen!( #[tokio::main] async fn main() -> Result<()> { - // 1. compile the contract (note this requires that you are inside the `ethers/examples` directory) - let compiled = Solc::new("**/contract.sol").build()?; + // 1. compile the contract (note this requires that you are inside the `ethers/examples` directory) and launch ganache + let (compiled, ganache) = + compile_and_launch_ganache(Solc::new("**/contract.sol"), Ganache::new()).await?; let contract = compiled .get("SimpleStorage") .expect("could not find contract"); - // 2. launch ganache - let ganache = Ganache::new().spawn(); - - // 3. instantiate our wallet + // 2. instantiate our wallet let wallet: LocalWallet = ganache.keys()[0].clone().into(); - // 4. connect to the network + // 3. connect to the network let provider = Provider::::try_from(ganache.endpoint())?.interval(Duration::from_millis(10u64)); - // 5. instantiate the client with the wallet + // 4. instantiate the client with the wallet let client = SignerMiddleware::new(provider, wallet); let client = Arc::new(client); - // 6. create a factory which will be used to deploy instances of the contract + // 5. create a factory which will be used to deploy instances of the contract let factory = ContractFactory::new( contract.abi.clone(), contract.bytecode.clone(), client.clone(), ); - // 7. deploy it with the constructor arguments + // 6. deploy it with the constructor arguments let contract = factory.deploy("initial value".to_string())?.send().await?; - // 8. get the contract's address + // 7. get the contract's address let addr = contract.address(); - // 9. instantiate the contract + // 8. instantiate the contract let contract = SimpleContract::new(addr, client.clone()); - // 10. call the `setValue` method + // 9. call the `setValue` method // (first `await` returns a PendingTransaction, second one waits for it to be mined) let _receipt = contract.set_value("hi".to_owned()).send().await?.await?; - // 11. get all events + // 10. get all events let logs = contract .value_changed_filter() .from_block(0u64) .query() .await?; - // 12. get the new value + // 11. get the new value let value = contract.get_value().call().await?; println!("Value: {}. Logs: {}", value, serde_json::to_string(&logs)?); diff --git a/ethers/examples/contract_with_abi.rs b/ethers/examples/contract_with_abi.rs index fa07aaa8..d02e219c 100644 --- a/ethers/examples/contract_with_abi.rs +++ b/ethers/examples/contract_with_abi.rs @@ -1,7 +1,7 @@ use anyhow::Result; use ethers::{ prelude::*, - utils::{Ganache, Solc}, + utils::{compile_and_launch_ganache, Ganache, Solc}, }; use std::{convert::TryFrom, sync::Arc, time::Duration}; @@ -15,55 +15,54 @@ abigen!( #[tokio::main] async fn main() -> Result<()> { - // 1. compile the contract (note this requires that you are inside the `ethers/examples` directory) - let compiled = Solc::new("**/contract.sol").build()?; + // 1. compile the contract (note this requires that you are inside the `ethers/examples` directory) and launch ganache + let (compiled, ganache) = + compile_and_launch_ganache(Solc::new("**/contract.sol"), Ganache::new()).await?; + let contract = compiled .get("SimpleStorage") .expect("could not find contract"); dbg!("OK"); - // 2. launch ganache - let ganache = Ganache::new().spawn(); - - // 3. instantiate our wallet + // 2. instantiate our wallet let wallet: LocalWallet = ganache.keys()[0].clone().into(); - // 4. connect to the network + // 3. connect to the network let provider = Provider::::try_from(ganache.endpoint())?.interval(Duration::from_millis(10u64)); - // 5. instantiate the client with the wallet + // 4. instantiate the client with the wallet let client = SignerMiddleware::new(provider, wallet); let client = Arc::new(client); - // 6. create a factory which will be used to deploy instances of the contract + // 5. create a factory which will be used to deploy instances of the contract let factory = ContractFactory::new( contract.abi.clone(), contract.bytecode.clone(), client.clone(), ); - // 7. deploy it with the constructor arguments + // 6. deploy it with the constructor arguments let contract = factory.deploy("initial value".to_string())?.send().await?; - // 8. get the contract's address + // 7. get the contract's address let addr = contract.address(); - // 9. instantiate the contract + // 8. instantiate the contract let contract = SimpleContract::new(addr, client.clone()); - // 10. call the `setValue` method + // 9. call the `setValue` method // (first `await` returns a PendingTransaction, second one waits for it to be mined) let _receipt = contract.set_value("hi".to_owned()).send().await?.await?; - // 11. get all events + // 10. get all events let logs = contract .value_changed_filter() .from_block(0u64) .query() .await?; - // 12. get the new value + // 11. get the new value let value = contract.get_value().call().await?; println!("Value: {}. Logs: {}", value, serde_json::to_string(&logs)?);