diff --git a/Cargo.lock b/Cargo.lock index adb9af0a..567472bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -857,6 +857,7 @@ dependencies = [ "ethers-middleware", "ethers-providers", "ethers-signers", + "ethers-solc", "hex", "rand 0.8.4", "serde", @@ -875,6 +876,7 @@ dependencies = [ "ethers-middleware", "ethers-providers", "ethers-signers", + "ethers-solc", "futures-util", "hex", "once_cell", @@ -932,7 +934,6 @@ dependencies = [ "ethabi", "futures-util", "generic-array 0.14.4", - "glob", "hex", "hex-literal", "k256", @@ -942,7 +943,6 @@ dependencies = [ "rand 0.8.4", "rlp", "rlp-derive", - "semver 1.0.4", "serde", "serde_json", "syn", @@ -988,6 +988,7 @@ dependencies = [ "ethers-core", "ethers-providers", "ethers-signers", + "ethers-solc", "futures-util", "hex", "instant", @@ -1074,6 +1075,7 @@ name = "ethers-solc" version = "0.1.0" dependencies = [ "colored", + "ethers-core", "futures-util", "hex", "md-5", @@ -1308,12 +1310,6 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - [[package]] name = "group" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index dab4edba..e2ac4802 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,6 @@ legacy = [ # individual features per sub-crate ## core -setup = ["ethers-core/setup"] eip712 = ["ethers-contract/eip712", "ethers-core/eip712"] ## providers ws = ["ethers-providers/ws"] @@ -77,10 +76,11 @@ abigen = ["ethers-contract/abigen"] [dependencies] ethers-contract = { version = "^0.5.0", default-features = false, path = "./ethers-contract" } -ethers-core = { version = "^0.5.0", default-features = false, path = "./ethers-core", features = ["setup"] } +ethers-core = { version = "^0.5.0", default-features = false, path = "./ethers-core" } ethers-providers = { version = "^0.5.0", default-features = false, path = "./ethers-providers" } ethers-signers = { version = "^0.5.0", default-features = false, path = "./ethers-signers" } ethers-middleware = { version = "^0.5.0", default-features = false, path = "./ethers-middleware" } +ethers-solc = { version = "^0.1.0", default-features = false, path = "./ethers-solc" } [dev-dependencies] ethers-contract = { version = "^0.5.0", default-features = false, path = "./ethers-contract", features = ["abigen", "eip712"] } diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml index 304b55bc..7c68a899 100644 --- a/ethers-contract/Cargo.toml +++ b/ethers-contract/Cargo.toml @@ -32,6 +32,7 @@ ethers-contract-abigen = { version = "^0.5.0", path = "ethers-contract-abigen" } ethers-contract-derive = { version = "^0.5.0", path = "ethers-contract-derive" } ethers-core = { version = "^0.5.0", path = "../ethers-core", default-features = false, features = ["eip712"]} ethers-derive-eip712 = { version = "0.1.0", path = "../ethers-core/ethers-derive-eip712"} +ethers-solc = { version = "^0.1.0", path = "../ethers-solc", default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] tokio = { version = "1.5", default-features = false, features = ["macros"] } diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs index f348483c..43f60c5f 100644 --- a/ethers-contract/src/contract.rs +++ b/ethers-contract/src/contract.rs @@ -67,7 +67,6 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc}; /// ```no_run /// use ethers_core::{ /// abi::Abi, -/// utils::Solc, /// types::{Address, H256}, /// }; /// use ethers_contract::Contract; diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs index a2cfb070..6be23ba8 100644 --- a/ethers-contract/src/factory.rs +++ b/ethers-contract/src/factory.rs @@ -90,7 +90,7 @@ impl Deployer { /// # Example /// /// ```no_run -/// use ethers_core::utils::Solc; +/// use ethers_solc::Solc; /// use ethers_contract::ContractFactory; /// use ethers_providers::{Provider, Http}; /// use ethers_signers::Wallet; @@ -99,9 +99,9 @@ impl Deployer { /// # async fn foo() -> Result<(), Box> { /// // first we'll compile the contract (you can alternatively compile it yourself /// // and pass the ABI/Bytecode -/// let compiled = Solc::new("./tests/contract.sol").build().unwrap(); +/// let compiled = Solc::default().compile_source("./tests/contract.sol").unwrap(); /// let contract = compiled -/// .get("SimpleStorage") +/// .get("./tests/contract.sol", "SimpleStorage") /// .expect("could not find contract"); /// /// // connect to the network @@ -109,7 +109,7 @@ impl Deployer { /// let client = std::sync::Arc::new(client); /// /// // create a factory which will be used to deploy instances of the contract -/// let factory = ContractFactory::new(contract.abi.clone(), contract.bytecode.clone(), client); +/// let factory = ContractFactory::new(contract.abi.unwrap().clone(), contract.bin.unwrap().clone(), client); /// /// // The deployer created by the `deploy` call exposes a builder which gets consumed /// // by the async `send` call diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs index e8ab939e..388188ab 100644 --- a/ethers-contract/tests/abigen.rs +++ b/ethers-contract/tests/abigen.rs @@ -4,9 +4,9 @@ use ethers_contract::{abigen, EthEvent}; use ethers_core::{ abi::{AbiDecode, AbiEncode, Address, Tokenizable}, types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256}, - utils::Solc, }; use ethers_providers::Provider; +use ethers_solc::Solc; use std::{convert::TryFrom, sync::Arc}; #[test] @@ -287,11 +287,13 @@ async fn can_handle_underscore_functions() { .interval(std::time::Duration::from_millis(10)); let client = Arc::new(provider); - let compiled = Solc::new("./tests/solidity-contracts/SimpleStorage.sol").build().unwrap(); - let compiled = compiled.get("SimpleStorage").unwrap(); + let contract = "SimpleStorage"; + let path = "./tests/solidity-contracts/SimpleStorage.sol"; + let compiled = Solc::default().compile_source(path).unwrap(); + let compiled = compiled.get(path, contract).unwrap(); let factory = ethers_contract::ContractFactory::new( - compiled.abi.clone(), - compiled.bytecode.clone(), + compiled.abi.unwrap().clone(), + compiled.bin.unwrap().clone(), client.clone(), ); let addr = factory.deploy("hi".to_string()).unwrap().legacy().send().await.unwrap().address(); diff --git a/ethers-contract/tests/common/mod.rs b/ethers-contract/tests/common/mod.rs index fcb94ebf..1ebf5002 100644 --- a/ethers-contract/tests/common/mod.rs +++ b/ethers-contract/tests/common/mod.rs @@ -10,12 +10,9 @@ use ethers_contract::EthEvent; mod derive; use ethers_contract::{Contract, ContractFactory}; -use ethers_core::{ - abi::Abi, - types::Bytes, - utils::{GanacheInstance, Solc}, -}; +use ethers_core::{abi::Abi, types::Bytes, utils::GanacheInstance}; use ethers_providers::{Http, Middleware, Provider}; +use ethers_solc::Solc; use std::{convert::TryFrom, sync::Arc, time::Duration}; // Note: The `EthEvent` derive macro implements the necessary conversion between `Tokens` and @@ -33,9 +30,10 @@ pub struct ValueChanged { /// compiles the given contract and returns the ABI and Bytecode pub fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) { - let compiled = Solc::new(&format!("./tests/solidity-contracts/{}", filename)).build().unwrap(); - let contract = compiled.get(name).expect("could not find contract"); - (contract.abi.clone(), contract.bytecode.clone()) + let path = format!("./tests/solidity-contracts/{}", filename); + let compiled = Solc::default().compile_source(&path).unwrap(); + let contract = compiled.get(&path, name).expect("could not find contract"); + (contract.abi.unwrap().clone(), contract.bin.unwrap().clone()) } /// connects the private key to http://localhost:8545 diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index 762190a9..5a094d0c 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -27,10 +27,8 @@ tiny-keccak = { version = "2.0.2", default-features = false } serde = { version = "1.0.124", default-features = false, features = ["derive"] } serde_json = { version = "1.0.64", default-features = false } thiserror = { version = "1.0.30", default-features = false } -glob = { version = "0.3.0", default-features = false } bytes = { version = "1.1.0", features = ["serde"] } hex = { version = "0.4.3", default-features = false, features = ["std"] } -semver = "1.0.4" once_cell = "1.8.0" # eip712 feature enabled dependencies diff --git a/ethers-core/src/lib.rs b/ethers-core/src/lib.rs index a105e58b..71dd77cf 100644 --- a/ethers-core/src/lib.rs +++ b/ethers-core/src/lib.rs @@ -33,8 +33,7 @@ //! ## Utilities //! //! The crate provides utilities for launching local Ethereum testnets by using `ganache-cli` -//! via the `GanacheBuilder` struct. In addition, you're able to compile contracts on the -//! filesystem by providing a glob to their path, using the `Solc` struct. +//! via the `GanacheBuilder` struct. //! //! # Features //! diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 18e35adc..0099e2e4 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -10,20 +10,6 @@ mod geth; #[cfg(not(target_arch = "wasm32"))] pub use geth::{Geth, GethInstance}; -/// Solidity compiler bindings -#[cfg(not(target_arch = "wasm32"))] -pub 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 deleted file mode 100644 index 6c7384c6..00000000 --- a/ethers-core/src/utils/setup.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Setup utilities to start necessary infrastructure - -use crate::utils::{ - solc::{CompiledContract, SolcError}, - 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-core/src/utils/solc.rs b/ethers-core/src/utils/solc.rs deleted file mode 100644 index d89e6cd5..00000000 --- a/ethers-core/src/utils/solc.rs +++ /dev/null @@ -1,584 +0,0 @@ -use std::{collections::HashMap, fmt, io::BufRead, path::PathBuf, process::Command, str::FromStr}; - -use glob::glob; -use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; -use thiserror::Error; - -use crate::{abi::Abi, types::Bytes}; -use once_cell::sync::Lazy; -use semver::Version; - -/// The name of the `solc` binary on the system -const SOLC: &str = "solc"; - -/// Support for configuring the EVM version -/// https://blog.soliditylang.org/2018/03/08/solidity-0.4.21-release-announcement/ -static CONSTANTINOPLE_SOLC: Lazy = Lazy::new(|| Version::from_str("0.4.21").unwrap()); - -/// Petersburg support -/// https://blog.soliditylang.org/2019/03/05/solidity-0.5.5-release-announcement/ -static PETERSBURG_SOLC: Lazy = Lazy::new(|| Version::from_str("0.5.5").unwrap()); - -/// Istanbul support -/// https://blog.soliditylang.org/2019/12/09/solidity-0.5.14-release-announcement/ -static ISTANBUL_SOLC: Lazy = Lazy::new(|| Version::from_str("0.5.14").unwrap()); - -/// Berlin support -/// https://blog.soliditylang.org/2021/06/10/solidity-0.8.5-release-announcement/ -static BERLIN_SOLC: Lazy = Lazy::new(|| Version::from_str("0.8.5").unwrap()); - -/// London support -/// https://blog.soliditylang.org/2021/08/11/solidity-0.8.7-release-announcement/ -static LONDON_SOLC: Lazy = Lazy::new(|| Version::from_str("0.8.7").unwrap()); - -type Result = std::result::Result; - -#[derive(Debug, Error)] -pub enum SolcError { - /// Internal solc error - #[error("Solc Error: {0}")] - SolcError(String), - #[error(transparent)] - SemverError(#[from] semver::Error), - /// Deserialization error - #[error(transparent)] - SerdeJson(#[from] serde_json::Error), -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -/// The result of a solc compilation -pub struct CompiledContract { - /// The contract's ABI - pub abi: Abi, - /// The contract's bytecode - pub bytecode: Bytes, - /// The contract's runtime bytecode - pub runtime_bytecode: Bytes, -} - -/// Solidity Compiler Bindings -/// -/// Assumes that `solc` is installed and available in the caller's $PATH. Any calls -/// will **panic** otherwise. -/// -/// By default, it uses 200 optimizer runs and Istanbul as the EVM version -/// -/// # Examples -/// -/// ```no_run -/// use ethers_core::utils::Solc; -/// -/// # fn main() -> Result<(), Box> { -/// // Give it a glob -/// let contracts = Solc::new("./contracts/*") -/// .optimizer(Some(200)) -/// .build()?; -/// -/// // this will return None if the specified contract did not exist in the compiled -/// // files -/// let contract = contracts.get("SimpleStorage").expect("contract not found"); -/// # Ok(()) -/// # } -/// ``` -pub struct Solc { - /// The path to the Solc binary - pub solc_path: Option, - - /// The path where contracts will be read from - pub paths: Vec, - - /// Number of optimizer runs. None for no optimization - pub optimizer: Option, - - /// Evm Version - pub evm_version: EvmVersion, - - /// Paths for importing other libraries - pub allowed_paths: Vec, - - /// Output a single json document containing the specified information. - /// Default is `abi,bin,bin-runtime` - pub combined_json: Option, - - /// Additional arguments to pass to solc - pub args: Vec, -} - -impl Solc { - /// Instantiates The Solc builder with the provided glob of Solidity files - pub fn new(path: &str) -> Self { - // Convert the glob to a vector of string paths - // TODO: This might not be the most robust way to do this - let paths = glob(path) - .expect("could not get glob") - .map(|path| path.expect("path not found").to_string_lossy().to_string()) - .collect::>(); - Self::new_with_paths(paths) - } - - /// Instantiates the Solc builder for the provided paths - pub fn new_with_paths(paths: Vec) -> Self { - Self { - paths, - solc_path: None, - optimizer: Some(200), // default optimizer runs = 200 - evm_version: EvmVersion::Istanbul, - allowed_paths: Vec::new(), - combined_json: Some("abi,bin,bin-runtime".to_string()), - args: Vec::new(), - } - } - - /// Gets the complete solc output as json object - pub fn exec(self) -> Result { - let path = self.solc_path.unwrap_or_else(|| PathBuf::from(SOLC)); - - let mut command = Command::new(&path); - let version = Solc::version(Some(path)); - - if let Some(combined_json) = self.combined_json { - command.arg("--combined-json").arg(combined_json); - } - - if let Some(evm_version) = normalize_evm_version(&version, self.evm_version) { - command.arg("--evm-version").arg(evm_version.to_string()); - } - - if let Some(runs) = self.optimizer { - command.arg("--optimize").arg("--optimize-runs").arg(runs.to_string()); - } - - command.args(self.args); - - for path in self.paths { - command.arg(path); - } - - let command = command.output().expect("could not run `solc`"); - - if !command.status.success() { - return Err(SolcError::SolcError(String::from_utf8_lossy(&command.stderr).to_string())) - } - - // Deserialize the output - Ok(serde_json::from_slice(&command.stdout)?) - } - - /// Gets the ABI for the contracts - pub fn build_raw(self) -> Result> { - let mut output = self.exec()?; - let contract_values = output["contracts"].as_object_mut().ok_or_else(|| { - SolcError::SolcError("no contracts found in `solc` output".to_string()) - })?; - - let mut contracts = HashMap::with_capacity(contract_values.len()); - - for (name, contract) in contract_values { - if let serde_json::Value::String(bin) = contract["bin"].take() { - let name = name.rsplit(':').next().expect("could not strip fname").to_owned(); - - // abi could be an escaped string (solc<=0.7) or an array (solc>=0.8) - let abi = match contract["abi"].take() { - serde_json::Value::String(abi) => abi, - val @ serde_json::Value::Array(_) => val.to_string(), - val => { - return Err(SolcError::SolcError(format!( - "Expected abi in solc output, found {:?}", - val - ))) - } - }; - - let runtime_bin = - if let serde_json::Value::String(bin) = contract["bin-runtime"].take() { - bin - } else { - panic!("no runtime bytecode found") - }; - contracts.insert(name, CompiledContractStr { abi, bin, runtime_bin }); - } else { - return Err(SolcError::SolcError("could not find `bin` in solc output".to_string())) - } - } - - Ok(contracts) - } - - /// Builds the contracts and returns a hashmap for each named contract - pub fn build(self) -> Result> { - // Build, and then get the data in the correct format - let contracts = self - .build_raw()? - .into_iter() - .map(|(name, contract)| { - // parse the ABI - let abi = serde_json::from_str(&contract.abi) - .expect("could not parse `solc` abi, this should never happen"); - - // parse the bytecode - let bytecode = - hex::decode(contract.bin).expect("solc did not produce valid bytecode").into(); - - // parse the runtime bytecode - let runtime_bytecode = hex::decode(contract.runtime_bin) - .expect("solc did not produce valid runtime-bytecode") - .into(); - (name, CompiledContract { abi, bytecode, runtime_bytecode }) - }) - .collect::>(); - - Ok(contracts) - } - - /// Returns the output of `solc --version` - /// - /// # Panics - /// - /// If `solc` is not found - pub fn version(solc_path: Option) -> Version { - let solc_path = solc_path.unwrap_or_else(|| PathBuf::from(SOLC)); - let command_output = Command::new(&solc_path) - .arg("--version") - .output() - .unwrap_or_else(|_| panic!("`{:?}` not found", solc_path)); - - let version = command_output - .stdout - .lines() - .last() - .expect("expected version in solc output") - .expect("could not get solc version"); - - // Return the version trimmed - let version = version.replace("Version: ", ""); - Version::from_str(&version[0..5]).expect("not a version") - } - - /// Sets the EVM version for compilation - pub fn evm_version(mut self, version: EvmVersion) -> Self { - self.evm_version = version; - self - } - - /// Sets the path to the solc binary - pub fn solc_path(mut self, path: PathBuf) -> Self { - self.solc_path = Some(std::fs::canonicalize(path).unwrap()); - self - } - - /// Sets the `combined-json` option, by default this is set to `abi,bin,bin-runtime` - /// NOTE: In order to get the `CompiledContract` from `Self::build`, this _must_ contain - /// `abi,bin`. - pub fn combined_json(mut self, combined_json: impl Into) -> Self { - self.combined_json = Some(combined_json.into()); - self - } - - /// Sets the optimizer runs (default = 200). None indicates no optimization - /// - /// ```rust,no_run - /// use ethers_core::utils::Solc; - /// - /// // No optimization - /// let contracts = Solc::new("./contracts/*") - /// .optimizer(None) - /// .build().unwrap(); - /// - /// // Some(200) is default, optimizer on with 200 runs - /// // .arg() allows passing arbitrary args to solc command - /// let optimized_contracts = Solc::new("./contracts/*") - /// .optimizer(Some(200)) - /// .arg("--metadata-hash=none") - /// .build().unwrap(); - /// ``` - pub fn optimizer(mut self, runs: Option) -> Self { - self.optimizer = runs; - self - } - - /// Sets the allowed paths for using files from outside the same directory - // TODO: Test this - pub fn allowed_paths(mut self, paths: Vec) -> Self { - self.allowed_paths = paths; - self - } - - /// Adds an argument to pass to solc - pub fn arg>(mut self, arg: T) -> Self { - self.args.push(arg.into()); - self - } - - /// Adds multiple arguments to pass to solc - pub fn args(mut self, args: I) -> Self - where - I: IntoIterator, - S: Into, - { - for arg in args { - self = self.arg(arg); - } - self - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum EvmVersion { - Homestead, - TangerineWhistle, - SpuriusDragon, - Constantinople, - Petersburg, - Istanbul, - Berlin, - London, -} - -impl fmt::Display for EvmVersion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let string = match self { - EvmVersion::Homestead => "homestead", - EvmVersion::TangerineWhistle => "tangerineWhistle", - EvmVersion::SpuriusDragon => "spuriusDragon", - EvmVersion::Constantinople => "constantinople", - EvmVersion::Petersburg => "petersburg", - EvmVersion::Istanbul => "istanbul", - EvmVersion::Berlin => "berlin", - EvmVersion::London => "london", - }; - write!(f, "{}", string) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -// Helper struct for deserializing the solc string outputs -struct SolcOutput { - contracts: HashMap, - version: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -/// Helper struct for deserializing the solc string outputs -pub struct CompiledContractStr { - /// The contract's raw ABI - pub abi: String, - /// The contract's bytecode in hex - pub bin: String, - /// The contract's runtime bytecode in hex - pub runtime_bin: String, -} - -fn normalize_evm_version(version: &Version, evm_version: EvmVersion) -> Option { - // the EVM version flag was only added at 0.4.21 - // we work our way backwards - if version >= &CONSTANTINOPLE_SOLC { - // If the Solc is at least at london, it supports all EVM versions - Some(if version >= &LONDON_SOLC { - evm_version - // For all other cases, cap at the at-the-time highest possible fork - } else if version >= &BERLIN_SOLC && evm_version >= EvmVersion::Berlin { - EvmVersion::Berlin - } else if version >= &ISTANBUL_SOLC && evm_version >= EvmVersion::Istanbul { - EvmVersion::Istanbul - } else if version >= &PETERSBURG_SOLC && evm_version >= EvmVersion::Petersburg { - EvmVersion::Petersburg - } else if evm_version >= EvmVersion::Constantinople { - EvmVersion::Constantinople - } else { - evm_version - }) - } else { - None - } -} - -/// General `solc` contract output -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Contract { - pub abi: Abi, - pub evm: Evm, - #[serde( - deserialize_with = "de_from_json_opt", - serialize_with = "ser_to_inner_json", - skip_serializing_if = "Option::is_none" - )] - pub metadata: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Evm { - pub bytecode: Bytecode, - pub deployed_bytecode: Bytecode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Bytecode { - #[serde(deserialize_with = "deserialize_bytes")] - pub object: Bytes, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Metadata { - pub compiler: Compiler, - pub language: String, - pub output: Output, - pub settings: Settings, - pub sources: Sources, - pub version: i64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Compiler { - pub version: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Output { - pub abi: Vec, - pub devdoc: Option, - pub userdoc: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SolcAbi { - pub inputs: Vec, - #[serde(rename = "stateMutability")] - pub state_mutability: Option, - #[serde(rename = "type")] - pub abi_type: String, - pub name: Option, - pub outputs: Option>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Item { - #[serde(rename = "internalType")] - pub internal_type: String, - pub name: String, - #[serde(rename = "type")] - pub put_type: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Doc { - pub kind: String, - pub methods: Libraries, - pub version: u32, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Libraries { - #[serde(flatten)] - pub libs: HashMap, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Settings { - #[serde(rename = "compilationTarget")] - pub compilation_target: CompilationTarget, - #[serde(rename = "evmVersion")] - pub evm_version: String, - pub libraries: Libraries, - pub metadata: MetadataClass, - pub optimizer: Optimizer, - pub remappings: Vec>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CompilationTarget { - #[serde(flatten)] - pub inner: HashMap, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MetadataClass { - #[serde(rename = "bytecodeHash")] - pub bytecode_hash: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Optimizer { - pub enabled: bool, - pub runs: u32, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Sources { - #[serde(flatten)] - pub inner: HashMap, -} - -pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result -where - D: Deserializer<'de>, -{ - let value = String::deserialize(d)?; - - Ok(hex::decode(&value).map_err(|e| serde::de::Error::custom(e.to_string()))?.into()) -} - -fn de_from_json_opt<'de, D, T>(deserializer: D) -> std::result::Result, D::Error> -where - D: Deserializer<'de>, - T: DeserializeOwned, -{ - if let Some(val) = >::deserialize(deserializer)? { - serde_json::from_str(&val).map_err(serde::de::Error::custom) - } else { - Ok(None) - } -} - -fn ser_to_inner_json(val: &T, s: S) -> std::result::Result -where - S: Serializer, - T: Serialize, -{ - let val = serde_json::to_string(val).map_err(serde::ser::Error::custom)?; - s.serialize_str(&val) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_solc_version() { - Solc::version(None); - } - - #[test] - fn test_evm_version_normalization() { - for (solc_version, evm_version, expected) in &[ - // Ensure 0.4.21 it always returns None - ("0.4.20", EvmVersion::Homestead, None), - // Constantinople clipping - ("0.4.21", EvmVersion::Homestead, Some(EvmVersion::Homestead)), - ("0.4.21", EvmVersion::Constantinople, Some(EvmVersion::Constantinople)), - ("0.4.21", EvmVersion::London, Some(EvmVersion::Constantinople)), - // Petersburg - ("0.5.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)), - ("0.5.5", EvmVersion::Petersburg, Some(EvmVersion::Petersburg)), - ("0.5.5", EvmVersion::London, Some(EvmVersion::Petersburg)), - // Istanbul - ("0.5.14", EvmVersion::Homestead, Some(EvmVersion::Homestead)), - ("0.5.14", EvmVersion::Istanbul, Some(EvmVersion::Istanbul)), - ("0.5.14", EvmVersion::London, Some(EvmVersion::Istanbul)), - // Berlin - ("0.8.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)), - ("0.8.5", EvmVersion::Berlin, Some(EvmVersion::Berlin)), - ("0.8.5", EvmVersion::London, Some(EvmVersion::Berlin)), - // London - ("0.8.7", EvmVersion::Homestead, Some(EvmVersion::Homestead)), - ("0.8.7", EvmVersion::London, Some(EvmVersion::London)), - ("0.8.7", EvmVersion::London, Some(EvmVersion::London)), - ] { - assert_eq!( - &normalize_evm_version(&Version::from_str(solc_version).unwrap(), *evm_version), - expected - ) - } - } -} diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml index 97408aba..669b5903 100644 --- a/ethers-middleware/Cargo.toml +++ b/ethers-middleware/Cargo.toml @@ -42,6 +42,7 @@ hex = { version = "0.4.3", default-features = false, features = ["std"] } rand = { version = "0.8.4", default-features = false } ethers-providers = { version = "^0.5.0", path = "../ethers-providers", default-features = false, features = ["ws", "rustls"] } once_cell = "1.8.0" +ethers-solc = { version = "^0.1.0", path = "../ethers-solc", default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] tokio = { version = "1.5", default-features = false, features = ["rt", "macros", "time"] } diff --git a/ethers-middleware/tests/signer.rs b/ethers-middleware/tests/signer.rs index 6ddad69e..de5d6bdf 100644 --- a/ethers-middleware/tests/signer.rs +++ b/ethers-middleware/tests/signer.rs @@ -214,18 +214,19 @@ async fn deploy_and_call_contract() { use ethers_core::{ abi::Abi, types::{BlockNumber, Bytes, H256, U256}, - utils::Solc, }; + use ethers_solc::Solc; use std::sync::Arc; - fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) { - let compiled = - Solc::new(&format!("./tests/solidity-contracts/{}", filename)).build().unwrap(); - let contract = compiled.get(name).expect("could not find contract"); - (contract.abi.clone(), contract.bytecode.clone()) + // compiles the given contract and returns the ABI and Bytecode + fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) { + let path = format!("./tests/solidity-contracts/{}", path); + let compiled = Solc::default().compile_source(&path).unwrap(); + let contract = compiled.get(&path, name).expect("could not find contract"); + (contract.abi.unwrap().clone(), contract.bin.unwrap().clone()) } - let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol"); + let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage"); // Celo testnet let provider = Provider::::try_from("https://alfajores-forno.celo-testnet.org") diff --git a/ethers-middleware/tests/transformer.rs b/ethers-middleware/tests/transformer.rs index 92767b04..377de503 100644 --- a/ethers-middleware/tests/transformer.rs +++ b/ethers-middleware/tests/transformer.rs @@ -1,21 +1,27 @@ #![cfg(not(target_arch = "wasm32"))] #![allow(unused)] use ethers_contract::{BaseContract, ContractFactory}; -use ethers_core::{ - types::*, - utils::{Ganache, Solc}, -}; +use ethers_core::{abi::Abi, types::*, utils::Ganache}; use ethers_middleware::{ transformer::{DsProxy, TransformerMiddleware}, SignerMiddleware, }; use ethers_providers::{Http, Middleware, Provider}; use ethers_signers::{LocalWallet, Signer}; +use ethers_solc::Solc; use rand::Rng; use std::{convert::TryFrom, sync::Arc, time::Duration}; type HttpWallet = SignerMiddleware, LocalWallet>; +// compiles the given contract and returns the ABI and Bytecode +fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) { + let path = format!("./tests/solidity-contracts/{}", path); + let compiled = Solc::default().compile_source(&path).unwrap(); + let contract = compiled.get(&path, name).expect("could not find contract"); + (contract.abi.unwrap().clone(), contract.bin.unwrap().clone()) +} + #[tokio::test] #[cfg(not(feature = "celo"))] async fn ds_proxy_transformer() { @@ -35,15 +41,8 @@ async fn ds_proxy_transformer() { let provider = Arc::new(signer_middleware.clone()); // deploy DsProxyFactory which we'll use to deploy a new DsProxy contract. - let compiled = Solc::new("./tests/solidity-contracts/DSProxy.sol") - .build() - .expect("could not compile DSProxyFactory"); - let contract = compiled.get("DSProxyFactory").expect("could not find DSProxyFactory"); - let factory = ContractFactory::new( - contract.abi.clone(), - contract.bytecode.clone(), - Arc::clone(&provider), - ); + let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory"); + let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider)); let ds_proxy_factory = factory.deploy(()).unwrap().legacy(); let ds_proxy_factory = ds_proxy_factory.send().await.unwrap(); @@ -58,15 +57,8 @@ async fn ds_proxy_transformer() { let ds_proxy_addr = ds_proxy.address(); // deploy SimpleStorage and try to update its value via transformer middleware. - let compiled = Solc::new("./tests/solidity-contracts/SimpleStorage.sol") - .build() - .expect("could not compile SimpleStorage"); - let contract = compiled.get("SimpleStorage").expect("could not find SimpleStorage"); - let factory = ContractFactory::new( - contract.abi.clone(), - contract.bytecode.clone(), - Arc::clone(&provider), - ); + let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage"); + let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider)); let deployer = factory.deploy(()).unwrap().legacy(); let simple_storage = deployer.send().await.unwrap(); @@ -108,15 +100,8 @@ async fn ds_proxy_code() { let provider = Arc::new(signer_middleware.clone()); // deploy DsProxyFactory which we'll use to deploy a new DsProxy contract. - let compiled = Solc::new("./tests/solidity-contracts/DSProxy.sol") - .build() - .expect("could not compile DSProxyFactory"); - let contract = compiled.get("DSProxyFactory").expect("could not find DSProxyFactory"); - let factory = ContractFactory::new( - contract.abi.clone(), - contract.bytecode.clone(), - Arc::clone(&provider), - ); + let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory"); + let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider)); let ds_proxy_factory = factory.deploy(()).unwrap().legacy(); let ds_proxy_factory = ds_proxy_factory.send().await.unwrap(); @@ -131,11 +116,8 @@ async fn ds_proxy_code() { let ds_proxy_addr = ds_proxy.address(); // compile the SimpleStorage contract which we will use to interact via DsProxy. - let compiled = Solc::new("./tests/solidity-contracts/SimpleStorage.sol") - .build() - .expect("could not compile SimpleStorage"); - let ss = compiled.get("SimpleStorage").expect("could not find SimpleStorage"); - let ss_base_contract: BaseContract = ss.abi.clone().into(); + let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage"); + let ss_base_contract: BaseContract = abi.into(); let expected_value: u64 = rng.gen(); let calldata = ss_base_contract .encode("setValue", U256::from(expected_value)) @@ -145,7 +127,7 @@ async fn ds_proxy_code() { ds_proxy .execute::, Bytes>( Arc::clone(&provider), - ss.bytecode.clone(), + bytecode.clone(), calldata, ) .expect("could not construct DSProxy contract call") diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml index 655a4cf5..57cca929 100644 --- a/ethers-solc/Cargo.toml +++ b/ethers-solc/Cargo.toml @@ -11,9 +11,10 @@ homepage = "https://docs.rs/ethers" description = """ Utilites for working with solc """ -keywords = ["ethereum", "web3", "etherscan", "ethers"] +keywords = ["ethereum", "web3", "solc", "solidity", "ethers"] [dependencies] +ethers-core = { version = "^0.5.0", path = "../ethers-core", default-features = false } serde_json = "1.0.68" serde = { version = "1.0.130", features = ["derive"] } semver = "1.0.4" diff --git a/ethers-solc/src/artifacts.rs b/ethers-solc/src/artifacts.rs index 461220c3..44bf8f95 100644 --- a/ethers-solc/src/artifacts.rs +++ b/ethers-solc/src/artifacts.rs @@ -1,4 +1,5 @@ //! Solc artifact types +use ethers_core::{abi::Abi, types::Bytes}; use colored::Colorize; use md5::Digest; @@ -11,10 +12,7 @@ use std::{ }; use crate::{compile::*, utils}; -use serde::{ - de::{self, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; /// An ordered list of files and their source pub type Sources = BTreeMap; @@ -366,7 +364,7 @@ impl AsRef for Source { } /// Output type `solc` produces -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] pub struct CompilerOutput { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub errors: Vec, @@ -385,6 +383,15 @@ impl CompilerOutput { pub fn diagnostics<'a>(&'a self, ignored_error_codes: &'a [u64]) -> OutputDiagnostics { OutputDiagnostics { errors: &self.errors, ignored_error_codes } } + + /// Given the contract file's path and the contract's name, tries to return the contract's + /// bytecode, runtime bytecode, and abi + pub fn get(&self, path: &str, contract: &str) -> Option { + self.contracts + .get(path) + .and_then(|contracts| contracts.get(contract)) + .map(CompactContractRef::from) + } } /// Helper type to implement display for solc errors @@ -419,11 +426,11 @@ impl<'a> fmt::Display for OutputDiagnostics<'a> { } } -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Contract { - /// The Ethereum Contract ABI. If empty, it is represented as an empty - /// array. See https://docs.soliditylang.org/en/develop/abi-spec.html - pub abi: Vec, + /// The Ethereum Contract ABI. + /// See https://docs.soliditylang.org/en/develop/abi-spec.html + pub abi: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub metadata: Option, #[serde(default)] @@ -443,15 +450,19 @@ pub struct Contract { } /// Minimal representation of a contract's abi with bytecode -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct CompactContract { /// The Ethereum Contract ABI. If empty, it is represented as an empty /// array. See https://docs.soliditylang.org/en/develop/abi-spec.html - pub abi: Vec, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub bin: Option, + pub abi: Option, + #[serde( + default, + deserialize_with = "deserialize_opt_bytes", + skip_serializing_if = "Option::is_none" + )] + pub bin: Option, #[serde(default, rename = "bin-runtime", skip_serializing_if = "Option::is_none")] - pub bin_runtime: Option, + pub bin_runtime: Option, } impl From for CompactContract { @@ -469,25 +480,25 @@ impl From for CompactContract { /// Helper type to serialize while borrowing from `Contract` #[derive(Clone, Debug, Serialize)] pub struct CompactContractRef<'a> { - pub abi: &'a [serde_json::Value], + pub abi: Option<&'a Abi>, #[serde(default, skip_serializing_if = "Option::is_none")] - pub bin: Option<&'a str>, + pub bin: Option<&'a Bytes>, #[serde(default, rename = "bin-runtime", skip_serializing_if = "Option::is_none")] - pub bin_runtime: Option<&'a str>, + pub bin_runtime: Option<&'a Bytes>, } impl<'a> From<&'a Contract> for CompactContractRef<'a> { fn from(c: &'a Contract) -> Self { let (bin, bin_runtime) = if let Some(ref evm) = c.evm { ( - Some(evm.bytecode.object.as_str()), - evm.deployed_bytecode.bytecode.as_ref().map(|evm| evm.object.as_str()), + Some(&evm.bytecode.object), + evm.deployed_bytecode.bytecode.as_ref().map(|evm| &evm.object), ) } else { (None, None) }; - Self { abi: &c.abi, bin, bin_runtime } + Self { abi: c.abi.as_ref(), bin, bin_runtime } } } @@ -545,7 +556,8 @@ pub struct Bytecode { #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")] pub function_debug_data: BTreeMap, /// The bytecode as a hex string. - pub object: String, + #[serde(deserialize_with = "deserialize_bytes")] + pub object: Bytes, /// Opcodes list (string) pub opcodes: String, /// The source mapping as a string. See the source mapping definition. @@ -660,26 +672,12 @@ pub struct Error { pub r#type: String, pub component: String, pub severity: Severity, - #[serde(default, deserialize_with = "from_optional_str")] + #[serde(default, with = "display_from_str_opt")] pub error_code: Option, pub message: String, pub formatted_message: Option, } -fn from_optional_str<'de, T, D>(deserializer: D) -> Result, D::Error> -where - T: FromStr, - T::Err: fmt::Display, - D: Deserializer<'de>, -{ - let s = Option::::deserialize(deserializer)?; - if let Some(s) = s { - T::from_str(&s).map_err(de::Error::custom).map(Some) - } else { - Ok(None) - } -} - impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(msg) = &self.formatted_message { @@ -821,6 +819,26 @@ mod display_from_str_opt { } } +pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result +where + D: Deserializer<'de>, +{ + let value = String::deserialize(d)?; + Ok(hex::decode(&value).map_err(|e| serde::de::Error::custom(e.to_string()))?.into()) +} + +pub fn deserialize_opt_bytes<'de, D>(d: D) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + let value = Option::::deserialize(d)?; + if let Some(value) = value { + Ok(Some(hex::decode(&value).map_err(|e| serde::de::Error::custom(e.to_string()))?.into())) + } else { + Ok(None) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/ethers-solc/src/compile.rs b/ethers-solc/src/compile.rs index 21b48ea0..3e007274 100644 --- a/ethers-solc/src/compile.rs +++ b/ethers-solc/src/compile.rs @@ -53,7 +53,7 @@ impl Solc { } /// Convenience function for compiling all sources under the given path - pub fn compile_source(&self, path: impl AsRef) -> Result { + pub fn compile_source(&self, path: impl AsRef) -> Result { self.compile(&CompilerInput::new(path)?) } diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index d476ba34..744c9678 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -206,6 +206,8 @@ impl SolcConfigBuilder { /// Determines how to handle compiler output pub enum ArtifactOutput { + /// No-op, does not write the artifacts to disk. + Nothing, /// Creates a single json artifact with /// ```json /// { @@ -225,6 +227,7 @@ impl ArtifactOutput { /// Is expected to handle the output and where to store it pub fn on_output(&self, output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> { match self { + ArtifactOutput::Nothing => Ok(()), ArtifactOutput::MinimalCombined => { fs::create_dir_all(&layout.artifacts)?; @@ -254,6 +257,9 @@ impl Default for ArtifactOutput { impl fmt::Debug for ArtifactOutput { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + ArtifactOutput::Nothing => { + write!(f, "Nothing") + } ArtifactOutput::MinimalCombined => { write!(f, "MinimalCombined") } diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 5b873449..04ef525b 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -231,7 +231,7 @@ impl Default for ProjectBuilder { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub enum ProjectCompileOutput<'a> { /// Nothing to compile because unchanged sources Unchanged, diff --git a/examples/abigen.rs b/examples/abigen.rs index 3d29e0e5..e3c8bc1a 100644 --- a/examples/abigen.rs +++ b/examples/abigen.rs @@ -1,4 +1,4 @@ -use ethers::{contract::Abigen, utils::Solc}; +use ethers::{contract::Abigen, solc::Solc}; fn main() -> anyhow::Result<()> { let mut args = std::env::args(); @@ -9,10 +9,11 @@ fn main() -> anyhow::Result<()> { println!("Generating bindings for {}\n", contract); - // compile it if needed + // compile it let abi = if contract.ends_with(".sol") { - let contracts = Solc::new(&contract).build_raw()?; - contracts.get(&contract_name).unwrap().abi.clone() + let contracts = Solc::default().compile_source(&contract)?; + let abi = contracts.get(&contract, &contract_name).unwrap().abi.unwrap(); + serde_json::to_string(abi).unwrap() } else { contract }; diff --git a/examples/contract_human_readable.rs b/examples/contract_human_readable.rs index 5e1bae22..9829b065 100644 --- a/examples/contract_human_readable.rs +++ b/examples/contract_human_readable.rs @@ -1,9 +1,7 @@ use anyhow::Result; -use ethers::{ - prelude::*, - utils::{compile_and_launch_ganache, Ganache, Solc}, -}; -use std::{convert::TryFrom, sync::Arc, time::Duration}; +use ethers::{prelude::*, utils::Ganache}; +use ethers_solc::{ArtifactOutput, Project, ProjectCompileOutput, ProjectPathsConfig}; +use std::{convert::TryFrom, path::PathBuf, sync::Arc, time::Duration}; // Generate the type-safe contract bindings by providing the ABI // definition in human readable format @@ -19,13 +17,30 @@ abigen!( #[tokio::main] async fn main() -> Result<()> { - // 1. compile the contract (note this requires that you are inside the `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"); + // the directory we use is root-dir/examples + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"); + // we use `root` for both the project root and for where to search for contracts since + // everything is in the same directory + let paths = ProjectPathsConfig::builder().root(&root).sources(&root).build().unwrap(); + // get the solc project instance using the paths above + let solc = Project::builder() + .paths(paths) + .ephemeral() + .artifacts(ArtifactOutput::Nothing) + .build() + .unwrap(); + // compile the project and get the artifacts + let compiled = solc.compile().unwrap(); + let compiled = match compiled { + ProjectCompileOutput::Compiled((output, _)) => output, + _ => panic!("expected compilation artifacts"), + }; + let path = root.join("contract.sol"); + let path = path.to_str(); + let contract = compiled.get(path.unwrap(), "SimpleStorage").expect("could not find contract"); - // 2. instantiate our wallet + // 2. instantiate our wallet & ganache + let ganache = Ganache::new().spawn(); let wallet: LocalWallet = ganache.keys()[0].clone().into(); // 3. connect to the network @@ -37,8 +52,11 @@ async fn main() -> Result<()> { let client = Arc::new(client); // 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()); + let factory = ContractFactory::new( + contract.abi.unwrap().clone(), + contract.bin.unwrap().clone(), + client.clone(), + ); // 6. deploy it with the constructor arguments let contract = factory.deploy("initial value".to_string())?.legacy().send().await?; diff --git a/src/lib.rs b/src/lib.rs index 707b55e6..ab77ce62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,6 +88,7 @@ pub use ethers_core as core; pub use ethers_middleware as middleware; pub use ethers_providers as providers; pub use ethers_signers as signers; +pub use ethers_solc as solc; // Re-export ethers_core::utils/types/abi // We hide these docs so that the rustdoc links send the visitor @@ -107,4 +108,5 @@ pub mod prelude { pub use ethers_middleware::*; pub use ethers_providers::*; pub use ethers_signers::*; + pub use ethers_solc::*; }