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-middleware",
|
||||||
"ethers-providers",
|
"ethers-providers",
|
||||||
"ethers-signers",
|
"ethers-signers",
|
||||||
|
"ethers-solc",
|
||||||
"hex",
|
"hex",
|
||||||
"rand 0.8.4",
|
"rand 0.8.4",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -875,6 +876,7 @@ dependencies = [
|
||||||
"ethers-middleware",
|
"ethers-middleware",
|
||||||
"ethers-providers",
|
"ethers-providers",
|
||||||
"ethers-signers",
|
"ethers-signers",
|
||||||
|
"ethers-solc",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hex",
|
"hex",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -932,7 +934,6 @@ dependencies = [
|
||||||
"ethabi",
|
"ethabi",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"generic-array 0.14.4",
|
"generic-array 0.14.4",
|
||||||
"glob",
|
|
||||||
"hex",
|
"hex",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"k256",
|
"k256",
|
||||||
|
@ -942,7 +943,6 @@ dependencies = [
|
||||||
"rand 0.8.4",
|
"rand 0.8.4",
|
||||||
"rlp",
|
"rlp",
|
||||||
"rlp-derive",
|
"rlp-derive",
|
||||||
"semver 1.0.4",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"syn",
|
"syn",
|
||||||
|
@ -988,6 +988,7 @@ dependencies = [
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
"ethers-providers",
|
"ethers-providers",
|
||||||
"ethers-signers",
|
"ethers-signers",
|
||||||
|
"ethers-solc",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hex",
|
"hex",
|
||||||
"instant",
|
"instant",
|
||||||
|
@ -1074,6 +1075,7 @@ name = "ethers-solc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"colored",
|
"colored",
|
||||||
|
"ethers-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hex",
|
"hex",
|
||||||
"md-5",
|
"md-5",
|
||||||
|
@ -1308,12 +1310,6 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
|
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "glob"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "group"
|
name = "group"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
|
|
@ -61,7 +61,6 @@ legacy = [
|
||||||
|
|
||||||
# individual features per sub-crate
|
# individual features per sub-crate
|
||||||
## core
|
## core
|
||||||
setup = ["ethers-core/setup"]
|
|
||||||
eip712 = ["ethers-contract/eip712", "ethers-core/eip712"]
|
eip712 = ["ethers-contract/eip712", "ethers-core/eip712"]
|
||||||
## providers
|
## providers
|
||||||
ws = ["ethers-providers/ws"]
|
ws = ["ethers-providers/ws"]
|
||||||
|
@ -77,10 +76,11 @@ abigen = ["ethers-contract/abigen"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers-contract = { version = "^0.5.0", default-features = false, path = "./ethers-contract" }
|
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-providers = { version = "^0.5.0", default-features = false, path = "./ethers-providers" }
|
||||||
ethers-signers = { version = "^0.5.0", default-features = false, path = "./ethers-signers" }
|
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-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]
|
[dev-dependencies]
|
||||||
ethers-contract = { version = "^0.5.0", default-features = false, path = "./ethers-contract", features = ["abigen", "eip712"] }
|
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-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-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-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]
|
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||||
tokio = { version = "1.5", default-features = false, features = ["macros"] }
|
tokio = { version = "1.5", default-features = false, features = ["macros"] }
|
||||||
|
|
|
@ -67,7 +67,6 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use ethers_core::{
|
/// use ethers_core::{
|
||||||
/// abi::Abi,
|
/// abi::Abi,
|
||||||
/// utils::Solc,
|
|
||||||
/// types::{Address, H256},
|
/// types::{Address, H256},
|
||||||
/// };
|
/// };
|
||||||
/// use ethers_contract::Contract;
|
/// use ethers_contract::Contract;
|
||||||
|
|
|
@ -90,7 +90,7 @@ impl<M: Middleware> Deployer<M> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use ethers_core::utils::Solc;
|
/// use ethers_solc::Solc;
|
||||||
/// use ethers_contract::ContractFactory;
|
/// use ethers_contract::ContractFactory;
|
||||||
/// use ethers_providers::{Provider, Http};
|
/// use ethers_providers::{Provider, Http};
|
||||||
/// use ethers_signers::Wallet;
|
/// use ethers_signers::Wallet;
|
||||||
|
@ -99,9 +99,9 @@ impl<M: Middleware> Deployer<M> {
|
||||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// // first we'll compile the contract (you can alternatively compile it yourself
|
/// // first we'll compile the contract (you can alternatively compile it yourself
|
||||||
/// // and pass the ABI/Bytecode
|
/// // 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
|
/// let contract = compiled
|
||||||
/// .get("SimpleStorage")
|
/// .get("./tests/contract.sol", "SimpleStorage")
|
||||||
/// .expect("could not find contract");
|
/// .expect("could not find contract");
|
||||||
///
|
///
|
||||||
/// // connect to the network
|
/// // connect to the network
|
||||||
|
@ -109,7 +109,7 @@ impl<M: Middleware> Deployer<M> {
|
||||||
/// let client = std::sync::Arc::new(client);
|
/// let client = std::sync::Arc::new(client);
|
||||||
///
|
///
|
||||||
/// // create a factory which will be used to deploy instances of the contract
|
/// // create a factory which will be used to deploy instances of the contract
|
||||||
/// let factory = ContractFactory::new(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
|
/// // The deployer created by the `deploy` call exposes a builder which gets consumed
|
||||||
/// // by the async `send` call
|
/// // by the async `send` call
|
||||||
|
|
|
@ -4,9 +4,9 @@ use ethers_contract::{abigen, EthEvent};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{AbiDecode, AbiEncode, Address, Tokenizable},
|
abi::{AbiDecode, AbiEncode, Address, Tokenizable},
|
||||||
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
|
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
|
||||||
utils::Solc,
|
|
||||||
};
|
};
|
||||||
use ethers_providers::Provider;
|
use ethers_providers::Provider;
|
||||||
|
use ethers_solc::Solc;
|
||||||
use std::{convert::TryFrom, sync::Arc};
|
use std::{convert::TryFrom, sync::Arc};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -287,11 +287,13 @@ async fn can_handle_underscore_functions() {
|
||||||
.interval(std::time::Duration::from_millis(10));
|
.interval(std::time::Duration::from_millis(10));
|
||||||
let client = Arc::new(provider);
|
let client = Arc::new(provider);
|
||||||
|
|
||||||
let compiled = Solc::new("./tests/solidity-contracts/SimpleStorage.sol").build().unwrap();
|
let contract = "SimpleStorage";
|
||||||
let compiled = compiled.get("SimpleStorage").unwrap();
|
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(
|
let factory = ethers_contract::ContractFactory::new(
|
||||||
compiled.abi.clone(),
|
compiled.abi.unwrap().clone(),
|
||||||
compiled.bytecode.clone(),
|
compiled.bin.unwrap().clone(),
|
||||||
client.clone(),
|
client.clone(),
|
||||||
);
|
);
|
||||||
let addr = factory.deploy("hi".to_string()).unwrap().legacy().send().await.unwrap().address();
|
let addr = factory.deploy("hi".to_string()).unwrap().legacy().send().await.unwrap().address();
|
||||||
|
|
|
@ -10,12 +10,9 @@ use ethers_contract::EthEvent;
|
||||||
mod derive;
|
mod derive;
|
||||||
|
|
||||||
use ethers_contract::{Contract, ContractFactory};
|
use ethers_contract::{Contract, ContractFactory};
|
||||||
use ethers_core::{
|
use ethers_core::{abi::Abi, types::Bytes, utils::GanacheInstance};
|
||||||
abi::Abi,
|
|
||||||
types::Bytes,
|
|
||||||
utils::{GanacheInstance, Solc},
|
|
||||||
};
|
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
|
use ethers_solc::Solc;
|
||||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||||
|
|
||||||
// Note: The `EthEvent` derive macro implements the necessary conversion between `Tokens` and
|
// 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
|
/// compiles the given contract and returns the ABI and Bytecode
|
||||||
pub fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) {
|
pub fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) {
|
||||||
let compiled = Solc::new(&format!("./tests/solidity-contracts/{}", filename)).build().unwrap();
|
let path = format!("./tests/solidity-contracts/{}", filename);
|
||||||
let contract = compiled.get(name).expect("could not find contract");
|
let compiled = Solc::default().compile_source(&path).unwrap();
|
||||||
(contract.abi.clone(), contract.bytecode.clone())
|
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
|
/// 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 = { version = "1.0.124", default-features = false, features = ["derive"] }
|
||||||
serde_json = { version = "1.0.64", default-features = false }
|
serde_json = { version = "1.0.64", default-features = false }
|
||||||
thiserror = { version = "1.0.30", 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"] }
|
bytes = { version = "1.1.0", features = ["serde"] }
|
||||||
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
||||||
semver = "1.0.4"
|
|
||||||
once_cell = "1.8.0"
|
once_cell = "1.8.0"
|
||||||
|
|
||||||
# eip712 feature enabled dependencies
|
# eip712 feature enabled dependencies
|
||||||
|
|
|
@ -33,8 +33,7 @@
|
||||||
//! ## Utilities
|
//! ## Utilities
|
||||||
//!
|
//!
|
||||||
//! The crate provides utilities for launching local Ethereum testnets by using `ganache-cli`
|
//! 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
|
//! via the `GanacheBuilder` struct.
|
||||||
//! filesystem by providing a glob to their path, using the `Solc` struct.
|
|
||||||
//!
|
//!
|
||||||
//! # Features
|
//! # Features
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -10,20 +10,6 @@ mod geth;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use geth::{Geth, GethInstance};
|
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;
|
mod hash;
|
||||||
pub use hash::{hash_message, id, keccak256, serialize};
|
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 }
|
rand = { version = "0.8.4", default-features = false }
|
||||||
ethers-providers = { version = "^0.5.0", path = "../ethers-providers", default-features = false, features = ["ws", "rustls"] }
|
ethers-providers = { version = "^0.5.0", path = "../ethers-providers", default-features = false, features = ["ws", "rustls"] }
|
||||||
once_cell = "1.8.0"
|
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]
|
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||||
tokio = { version = "1.5", default-features = false, features = ["rt", "macros", "time"] }
|
tokio = { version = "1.5", default-features = false, features = ["rt", "macros", "time"] }
|
||||||
|
|
|
@ -214,18 +214,19 @@ async fn deploy_and_call_contract() {
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::Abi,
|
abi::Abi,
|
||||||
types::{BlockNumber, Bytes, H256, U256},
|
types::{BlockNumber, Bytes, H256, U256},
|
||||||
utils::Solc,
|
|
||||||
};
|
};
|
||||||
|
use ethers_solc::Solc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) {
|
// compiles the given contract and returns the ABI and Bytecode
|
||||||
let compiled =
|
fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
|
||||||
Solc::new(&format!("./tests/solidity-contracts/{}", filename)).build().unwrap();
|
let path = format!("./tests/solidity-contracts/{}", path);
|
||||||
let contract = compiled.get(name).expect("could not find contract");
|
let compiled = Solc::default().compile_source(&path).unwrap();
|
||||||
(contract.abi.clone(), contract.bytecode.clone())
|
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
|
// Celo testnet
|
||||||
let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
use ethers_contract::{BaseContract, ContractFactory};
|
use ethers_contract::{BaseContract, ContractFactory};
|
||||||
use ethers_core::{
|
use ethers_core::{abi::Abi, types::*, utils::Ganache};
|
||||||
types::*,
|
|
||||||
utils::{Ganache, Solc},
|
|
||||||
};
|
|
||||||
use ethers_middleware::{
|
use ethers_middleware::{
|
||||||
transformer::{DsProxy, TransformerMiddleware},
|
transformer::{DsProxy, TransformerMiddleware},
|
||||||
SignerMiddleware,
|
SignerMiddleware,
|
||||||
};
|
};
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
use ethers_signers::{LocalWallet, Signer};
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
|
use ethers_solc::Solc;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||||
|
|
||||||
type HttpWallet = SignerMiddleware<Provider<Http>, LocalWallet>;
|
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]
|
#[tokio::test]
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
async fn ds_proxy_transformer() {
|
async fn ds_proxy_transformer() {
|
||||||
|
@ -35,15 +41,8 @@ async fn ds_proxy_transformer() {
|
||||||
let provider = Arc::new(signer_middleware.clone());
|
let provider = Arc::new(signer_middleware.clone());
|
||||||
|
|
||||||
// deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
|
// deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
|
||||||
let compiled = Solc::new("./tests/solidity-contracts/DSProxy.sol")
|
let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory");
|
||||||
.build()
|
let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider));
|
||||||
.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 ds_proxy_factory = factory.deploy(()).unwrap().legacy();
|
let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
|
||||||
let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
|
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();
|
let ds_proxy_addr = ds_proxy.address();
|
||||||
|
|
||||||
// deploy SimpleStorage and try to update its value via transformer middleware.
|
// deploy SimpleStorage and try to update its value via transformer middleware.
|
||||||
let compiled = Solc::new("./tests/solidity-contracts/SimpleStorage.sol")
|
let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
|
||||||
.build()
|
let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider));
|
||||||
.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 deployer = factory.deploy(()).unwrap().legacy();
|
let deployer = factory.deploy(()).unwrap().legacy();
|
||||||
let simple_storage = deployer.send().await.unwrap();
|
let simple_storage = deployer.send().await.unwrap();
|
||||||
|
|
||||||
|
@ -108,15 +100,8 @@ async fn ds_proxy_code() {
|
||||||
let provider = Arc::new(signer_middleware.clone());
|
let provider = Arc::new(signer_middleware.clone());
|
||||||
|
|
||||||
// deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
|
// deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
|
||||||
let compiled = Solc::new("./tests/solidity-contracts/DSProxy.sol")
|
let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory");
|
||||||
.build()
|
let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider));
|
||||||
.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 ds_proxy_factory = factory.deploy(()).unwrap().legacy();
|
let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
|
||||||
let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
|
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();
|
let ds_proxy_addr = ds_proxy.address();
|
||||||
|
|
||||||
// compile the SimpleStorage contract which we will use to interact via DsProxy.
|
// compile the SimpleStorage contract which we will use to interact via DsProxy.
|
||||||
let compiled = Solc::new("./tests/solidity-contracts/SimpleStorage.sol")
|
let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
|
||||||
.build()
|
let ss_base_contract: BaseContract = abi.into();
|
||||||
.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 expected_value: u64 = rng.gen();
|
let expected_value: u64 = rng.gen();
|
||||||
let calldata = ss_base_contract
|
let calldata = ss_base_contract
|
||||||
.encode("setValue", U256::from(expected_value))
|
.encode("setValue", U256::from(expected_value))
|
||||||
|
@ -145,7 +127,7 @@ async fn ds_proxy_code() {
|
||||||
ds_proxy
|
ds_proxy
|
||||||
.execute::<HttpWallet, Arc<HttpWallet>, Bytes>(
|
.execute::<HttpWallet, Arc<HttpWallet>, Bytes>(
|
||||||
Arc::clone(&provider),
|
Arc::clone(&provider),
|
||||||
ss.bytecode.clone(),
|
bytecode.clone(),
|
||||||
calldata,
|
calldata,
|
||||||
)
|
)
|
||||||
.expect("could not construct DSProxy contract call")
|
.expect("could not construct DSProxy contract call")
|
||||||
|
|
|
@ -11,9 +11,10 @@ homepage = "https://docs.rs/ethers"
|
||||||
description = """
|
description = """
|
||||||
Utilites for working with solc
|
Utilites for working with solc
|
||||||
"""
|
"""
|
||||||
keywords = ["ethereum", "web3", "etherscan", "ethers"]
|
keywords = ["ethereum", "web3", "solc", "solidity", "ethers"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ethers-core = { version = "^0.5.0", path = "../ethers-core", default-features = false }
|
||||||
serde_json = "1.0.68"
|
serde_json = "1.0.68"
|
||||||
serde = { version = "1.0.130", features = ["derive"] }
|
serde = { version = "1.0.130", features = ["derive"] }
|
||||||
semver = "1.0.4"
|
semver = "1.0.4"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//! Solc artifact types
|
//! Solc artifact types
|
||||||
|
use ethers_core::{abi::Abi, types::Bytes};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use md5::Digest;
|
use md5::Digest;
|
||||||
|
@ -11,10 +12,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{compile::*, utils};
|
use crate::{compile::*, utils};
|
||||||
use serde::{
|
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
de::{self, Visitor},
|
|
||||||
Deserialize, Deserializer, Serialize, Serializer,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An ordered list of files and their source
|
/// An ordered list of files and their source
|
||||||
pub type Sources = BTreeMap<PathBuf, Source>;
|
pub type Sources = BTreeMap<PathBuf, Source>;
|
||||||
|
@ -366,7 +364,7 @@ impl AsRef<str> for Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Output type `solc` produces
|
/// Output type `solc` produces
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||||
pub struct CompilerOutput {
|
pub struct CompilerOutput {
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub errors: Vec<Error>,
|
pub errors: Vec<Error>,
|
||||||
|
@ -385,6 +383,15 @@ impl CompilerOutput {
|
||||||
pub fn diagnostics<'a>(&'a self, ignored_error_codes: &'a [u64]) -> OutputDiagnostics {
|
pub fn diagnostics<'a>(&'a self, ignored_error_codes: &'a [u64]) -> OutputDiagnostics {
|
||||||
OutputDiagnostics { errors: &self.errors, ignored_error_codes }
|
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
|
/// 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 {
|
pub struct Contract {
|
||||||
/// The Ethereum Contract ABI. If empty, it is represented as an empty
|
/// The Ethereum Contract ABI.
|
||||||
/// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
|
/// See https://docs.soliditylang.org/en/develop/abi-spec.html
|
||||||
pub abi: Vec<serde_json::Value>,
|
pub abi: Option<Abi>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub metadata: Option<String>,
|
pub metadata: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -443,15 +450,19 @@ pub struct Contract {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimal representation of a contract's abi with bytecode
|
/// 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 {
|
pub struct CompactContract {
|
||||||
/// The Ethereum Contract ABI. If empty, it is represented as an empty
|
/// The Ethereum Contract ABI. If empty, it is represented as an empty
|
||||||
/// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
|
/// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
|
||||||
pub abi: Vec<serde_json::Value>,
|
pub abi: Option<Abi>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(
|
||||||
pub bin: Option<String>,
|
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")]
|
#[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 {
|
impl From<Contract> for CompactContract {
|
||||||
|
@ -469,25 +480,25 @@ impl From<Contract> for CompactContract {
|
||||||
/// Helper type to serialize while borrowing from `Contract`
|
/// Helper type to serialize while borrowing from `Contract`
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct CompactContractRef<'a> {
|
pub struct CompactContractRef<'a> {
|
||||||
pub abi: &'a [serde_json::Value],
|
pub abi: Option<&'a Abi>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[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")]
|
#[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> {
|
impl<'a> From<&'a Contract> for CompactContractRef<'a> {
|
||||||
fn from(c: &'a Contract) -> Self {
|
fn from(c: &'a Contract) -> Self {
|
||||||
let (bin, bin_runtime) = if let Some(ref evm) = c.evm {
|
let (bin, bin_runtime) = if let Some(ref evm) = c.evm {
|
||||||
(
|
(
|
||||||
Some(evm.bytecode.object.as_str()),
|
Some(&evm.bytecode.object),
|
||||||
evm.deployed_bytecode.bytecode.as_ref().map(|evm| evm.object.as_str()),
|
evm.deployed_bytecode.bytecode.as_ref().map(|evm| &evm.object),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
(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")]
|
#[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
|
||||||
pub function_debug_data: BTreeMap<String, FunctionDebugData>,
|
pub function_debug_data: BTreeMap<String, FunctionDebugData>,
|
||||||
/// The bytecode as a hex string.
|
/// The bytecode as a hex string.
|
||||||
pub object: String,
|
#[serde(deserialize_with = "deserialize_bytes")]
|
||||||
|
pub object: Bytes,
|
||||||
/// Opcodes list (string)
|
/// Opcodes list (string)
|
||||||
pub opcodes: String,
|
pub opcodes: String,
|
||||||
/// The source mapping as a string. See the source mapping definition.
|
/// The source mapping as a string. See the source mapping definition.
|
||||||
|
@ -660,26 +672,12 @@ pub struct Error {
|
||||||
pub r#type: String,
|
pub r#type: String,
|
||||||
pub component: String,
|
pub component: String,
|
||||||
pub severity: Severity,
|
pub severity: Severity,
|
||||||
#[serde(default, deserialize_with = "from_optional_str")]
|
#[serde(default, with = "display_from_str_opt")]
|
||||||
pub error_code: Option<u64>,
|
pub error_code: Option<u64>,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub formatted_message: Option<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 {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(msg) = &self.formatted_message {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -53,7 +53,7 @@ impl Solc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function for compiling all sources under the given path
|
/// 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)?)
|
self.compile(&CompilerInput::new(path)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -206,6 +206,8 @@ impl SolcConfigBuilder {
|
||||||
|
|
||||||
/// Determines how to handle compiler output
|
/// Determines how to handle compiler output
|
||||||
pub enum ArtifactOutput {
|
pub enum ArtifactOutput {
|
||||||
|
/// No-op, does not write the artifacts to disk.
|
||||||
|
Nothing,
|
||||||
/// Creates a single json artifact with
|
/// Creates a single json artifact with
|
||||||
/// ```json
|
/// ```json
|
||||||
/// {
|
/// {
|
||||||
|
@ -225,6 +227,7 @@ impl ArtifactOutput {
|
||||||
/// Is expected to handle the output and where to store it
|
/// Is expected to handle the output and where to store it
|
||||||
pub fn on_output(&self, output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> {
|
pub fn on_output(&self, output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
|
ArtifactOutput::Nothing => Ok(()),
|
||||||
ArtifactOutput::MinimalCombined => {
|
ArtifactOutput::MinimalCombined => {
|
||||||
fs::create_dir_all(&layout.artifacts)?;
|
fs::create_dir_all(&layout.artifacts)?;
|
||||||
|
|
||||||
|
@ -254,6 +257,9 @@ impl Default for ArtifactOutput {
|
||||||
impl fmt::Debug for ArtifactOutput {
|
impl fmt::Debug for ArtifactOutput {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
ArtifactOutput::Nothing => {
|
||||||
|
write!(f, "Nothing")
|
||||||
|
}
|
||||||
ArtifactOutput::MinimalCombined => {
|
ArtifactOutput::MinimalCombined => {
|
||||||
write!(f, "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> {
|
pub enum ProjectCompileOutput<'a> {
|
||||||
/// Nothing to compile because unchanged sources
|
/// Nothing to compile because unchanged sources
|
||||||
Unchanged,
|
Unchanged,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use ethers::{contract::Abigen, utils::Solc};
|
use ethers::{contract::Abigen, solc::Solc};
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let mut args = std::env::args();
|
let mut args = std::env::args();
|
||||||
|
@ -9,10 +9,11 @@ fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
println!("Generating bindings for {}\n", contract);
|
println!("Generating bindings for {}\n", contract);
|
||||||
|
|
||||||
// compile it if needed
|
// compile it
|
||||||
let abi = if contract.ends_with(".sol") {
|
let abi = if contract.ends_with(".sol") {
|
||||||
let contracts = Solc::new(&contract).build_raw()?;
|
let contracts = Solc::default().compile_source(&contract)?;
|
||||||
contracts.get(&contract_name).unwrap().abi.clone()
|
let abi = contracts.get(&contract, &contract_name).unwrap().abi.unwrap();
|
||||||
|
serde_json::to_string(abi).unwrap()
|
||||||
} else {
|
} else {
|
||||||
contract
|
contract
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ethers::{
|
use ethers::{prelude::*, utils::Ganache};
|
||||||
prelude::*,
|
use ethers_solc::{ArtifactOutput, Project, ProjectCompileOutput, ProjectPathsConfig};
|
||||||
utils::{compile_and_launch_ganache, Ganache, Solc},
|
use std::{convert::TryFrom, path::PathBuf, sync::Arc, time::Duration};
|
||||||
};
|
|
||||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
|
||||||
|
|
||||||
// Generate the type-safe contract bindings by providing the ABI
|
// Generate the type-safe contract bindings by providing the ABI
|
||||||
// definition in human readable format
|
// definition in human readable format
|
||||||
|
@ -19,13 +17,30 @@ abigen!(
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// 1. compile the contract (note this requires that you are inside the `examples` directory) and
|
// the directory we use is root-dir/examples
|
||||||
// launch ganache
|
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples");
|
||||||
let (compiled, ganache) =
|
// we use `root` for both the project root and for where to search for contracts since
|
||||||
compile_and_launch_ganache(Solc::new("**/contract.sol"), Ganache::new()).await?;
|
// everything is in the same directory
|
||||||
let contract = compiled.get("SimpleStorage").expect("could not find contract");
|
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();
|
let wallet: LocalWallet = ganache.keys()[0].clone().into();
|
||||||
|
|
||||||
// 3. connect to the network
|
// 3. connect to the network
|
||||||
|
@ -37,8 +52,11 @@ async fn main() -> Result<()> {
|
||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
|
|
||||||
// 5. create a factory which will be used to deploy instances of the contract
|
// 5. create a factory which will be used to deploy instances of the contract
|
||||||
let factory =
|
let factory = ContractFactory::new(
|
||||||
ContractFactory::new(contract.abi.clone(), contract.bytecode.clone(), client.clone());
|
contract.abi.unwrap().clone(),
|
||||||
|
contract.bin.unwrap().clone(),
|
||||||
|
client.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
// 6. deploy it with the constructor arguments
|
// 6. deploy it with the constructor arguments
|
||||||
let contract = factory.deploy("initial value".to_string())?.legacy().send().await?;
|
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_middleware as middleware;
|
||||||
pub use ethers_providers as providers;
|
pub use ethers_providers as providers;
|
||||||
pub use ethers_signers as signers;
|
pub use ethers_signers as signers;
|
||||||
|
pub use ethers_solc as solc;
|
||||||
|
|
||||||
// Re-export ethers_core::utils/types/abi
|
// Re-export ethers_core::utils/types/abi
|
||||||
// We hide these docs so that the rustdoc links send the visitor
|
// 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_middleware::*;
|
||||||
pub use ethers_providers::*;
|
pub use ethers_providers::*;
|
||||||
pub use ethers_signers::*;
|
pub use ethers_signers::*;
|
||||||
|
pub use ethers_solc::*;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue