feat: use ethers_solc::Solc instead of ethers_core::utils::Solc (#546)
* feat(ethers-solc): deserialize bytecode as bytes * feat(ethers-solc): add method to fetch compact contract * feat(ethers-solc): use Abi type instead of Vec<serde_json::Value> * test(contract): use new Solc bindings * test(middleware): use new Solc bindings * chore: remove solc from ethers-core * chore: remove concurrent setup code from ethers-core * feat(ethers-solc): add ArtifactOutput::Nothing as a no-op artifact logger * feat: add ethers-solc to top level crate * examples: use new solc building pattern * chore(solc): re-use opt str impl for error code decoding * fix abigen example * chore: fix doctests * chore: remove setup feature * fix: decode string to bytes correctly * chore: clippy lints
This commit is contained in:
parent
4123823383
commit
f0dea75219
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -90,7 +90,7 @@ impl<M: Middleware> Deployer<M> {
|
|||
/// # 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<M: Middleware> Deployer<M> {
|
|||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// // 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<M: Middleware> Deployer<M> {
|
|||
/// 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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
//!
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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<HashMap<String, CompiledContract>, 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<String, CompiledContract>, 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<String, CompiledContract>, 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))
|
||||
}
|
|
@ -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<Version> = 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<Version> = 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<Version> = 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<Version> = 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<Version> = Lazy::new(|| Version::from_str("0.8.7").unwrap());
|
||||
|
||||
type Result<T> = std::result::Result<T, SolcError>;
|
||||
|
||||
#[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<dyn std::error::Error>> {
|
||||
/// // 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<PathBuf>,
|
||||
|
||||
/// The path where contracts will be read from
|
||||
pub paths: Vec<String>,
|
||||
|
||||
/// Number of optimizer runs. None for no optimization
|
||||
pub optimizer: Option<usize>,
|
||||
|
||||
/// Evm Version
|
||||
pub evm_version: EvmVersion,
|
||||
|
||||
/// Paths for importing other libraries
|
||||
pub allowed_paths: Vec<PathBuf>,
|
||||
|
||||
/// Output a single json document containing the specified information.
|
||||
/// Default is `abi,bin,bin-runtime`
|
||||
pub combined_json: Option<String>,
|
||||
|
||||
/// Additional arguments to pass to solc
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
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::<Vec<String>>();
|
||||
Self::new_with_paths(paths)
|
||||
}
|
||||
|
||||
/// Instantiates the Solc builder for the provided paths
|
||||
pub fn new_with_paths(paths: Vec<String>) -> 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<serde_json::Value> {
|
||||
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<HashMap<String, CompiledContractStr>> {
|
||||
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<HashMap<String, CompiledContract>> {
|
||||
// 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::<HashMap<String, CompiledContract>>();
|
||||
|
||||
Ok(contracts)
|
||||
}
|
||||
|
||||
/// Returns the output of `solc --version`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `solc` is not found
|
||||
pub fn version(solc_path: Option<PathBuf>) -> 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<String>) -> 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<usize>) -> 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<PathBuf>) -> Self {
|
||||
self.allowed_paths = paths;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an argument to pass to solc
|
||||
pub fn arg<T: Into<String>>(mut self, arg: T) -> Self {
|
||||
self.args.push(arg.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds multiple arguments to pass to solc
|
||||
pub fn args<I, S>(mut self, args: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: Into<String>,
|
||||
{
|
||||
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<String, CompiledContractStr>,
|
||||
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<EvmVersion> {
|
||||
// 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<Metadata>,
|
||||
}
|
||||
|
||||
#[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<SolcAbi>,
|
||||
pub devdoc: Option<Doc>,
|
||||
pub userdoc: Option<Doc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SolcAbi {
|
||||
pub inputs: Vec<Item>,
|
||||
#[serde(rename = "stateMutability")]
|
||||
pub state_mutability: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub abi_type: String,
|
||||
pub name: Option<String>,
|
||||
pub outputs: Option<Vec<Item>>,
|
||||
}
|
||||
|
||||
#[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<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[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<Option<serde_json::Value>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CompilationTarget {
|
||||
#[serde(flatten)]
|
||||
pub inner: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[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<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result<Bytes, D::Error>
|
||||
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<Option<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
if let Some(val) = <Option<String>>::deserialize(deserializer)? {
|
||||
serde_json::from_str(&val).map_err(serde::de::Error::custom)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn ser_to_inner_json<S, T>(val: &T, s: S) -> std::result::Result<S::Ok, S::Error>
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"] }
|
||||
|
|
|
@ -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::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
||||
|
|
|
@ -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<Provider<Http>, 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::<HttpWallet, Arc<HttpWallet>, Bytes>(
|
||||
Arc::clone(&provider),
|
||||
ss.bytecode.clone(),
|
||||
bytecode.clone(),
|
||||
calldata,
|
||||
)
|
||||
.expect("could not construct DSProxy contract call")
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<PathBuf, Source>;
|
||||
|
@ -366,7 +364,7 @@ impl AsRef<str> 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<Error>,
|
||||
|
@ -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<CompactContractRef> {
|
||||
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<serde_json::Value>,
|
||||
/// The Ethereum Contract ABI.
|
||||
/// See https://docs.soliditylang.org/en/develop/abi-spec.html
|
||||
pub abi: Option<Abi>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<String>,
|
||||
#[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_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub bin: Option<String>,
|
||||
pub abi: Option<Abi>,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_opt_bytes",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub bin: Option<Bytes>,
|
||||
#[serde(default, rename = "bin-runtime", skip_serializing_if = "Option::is_none")]
|
||||
pub bin_runtime: Option<String>,
|
||||
pub bin_runtime: Option<Bytes>,
|
||||
}
|
||||
|
||||
impl From<Contract> for CompactContract {
|
||||
|
@ -469,25 +480,25 @@ impl From<Contract> 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<String, FunctionDebugData>,
|
||||
/// 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<u64>,
|
||||
pub message: String,
|
||||
pub formatted_message: Option<String>,
|
||||
}
|
||||
|
||||
fn from_optional_str<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: fmt::Display,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = Option::<String>::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<Bytes, D::Error>
|
||||
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<Option<Bytes>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = Option::<String>::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::*;
|
||||
|
|
|
@ -53,7 +53,7 @@ impl Solc {
|
|||
}
|
||||
|
||||
/// Convenience function for compiling all sources under the given path
|
||||
pub fn compile_source<T: Serialize>(&self, path: impl AsRef<Path>) -> Result<CompilerOutput> {
|
||||
pub fn compile_source(&self, path: impl AsRef<Path>) -> Result<CompilerOutput> {
|
||||
self.compile(&CompilerInput::new(path)?)
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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::*;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue