feat(types): add solc bindings
This commit is contained in:
parent
bdda7d0883
commit
d4bc43ee5b
|
@ -319,6 +319,7 @@ dependencies = [
|
|||
"ethabi 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethereum-types 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethers-utils 0.1.0",
|
||||
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rlp 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -437,6 +438,11 @@ dependencies = [
|
|||
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.2.5"
|
||||
|
@ -1554,6 +1560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626"
|
||||
"checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6"
|
||||
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
"checksum h2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff"
|
||||
"checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
|
||||
"checksum http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
|
||||
|
|
|
@ -90,7 +90,7 @@ where
|
|||
|
||||
/// Deploys an instance of the contract with the provider constructor arguments
|
||||
/// and returns the contract's instance
|
||||
pub async fn deploy<T: Tokenize>(
|
||||
pub fn deploy<T: Tokenize>(
|
||||
&self,
|
||||
constructor_args: T,
|
||||
) -> Result<Deployer<'a, P, N, S>, ContractError<P>> {
|
||||
|
|
|
@ -19,9 +19,11 @@ zeroize = { version = "1.1.0", default-features = false }
|
|||
|
||||
# misc
|
||||
serde = { version = "1.0.110", default-features = false, features = ["derive"] }
|
||||
serde_json = { version = "1.0.53", default-features = false, features = ["alloc"] }
|
||||
rustc-hex = { version = "2.1.0", default-features = false }
|
||||
thiserror = { version = "1.0.19", default-features = false }
|
||||
arrayvec = { version = "0.5.1", default-features = false, optional = true }
|
||||
glob = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = { version = "1.0.53", default-features = false }
|
||||
|
|
|
@ -46,3 +46,6 @@ pub mod abi;
|
|||
|
||||
// Convenience re-export
|
||||
pub use ethers_utils as utils;
|
||||
|
||||
mod solc;
|
||||
pub use solc::Solc;
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
//! Solidity Compiler Bindings
|
||||
//!
|
||||
//! Assumes that `solc` is installed and available in the caller's $PATH. Any calls
|
||||
//! will fail otherwise.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! // Give it a glob
|
||||
//! let contracts = Solc::new("./contracts/*")
|
||||
//! .optimizer(200)
|
||||
//! .build();
|
||||
//! let contract = contracts.get("SimpleStorage").unwrap();
|
||||
//! ```
|
||||
use crate::{abi::Abi, Bytes};
|
||||
use glob::glob;
|
||||
use rustc_hex::FromHex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fmt, io::BufRead, path::PathBuf, process::Command};
|
||||
use thiserror::Error;
|
||||
|
||||
/// The name of the `solc` binary on the system
|
||||
const SOLC: &str = "solc";
|
||||
|
||||
type Result<T> = std::result::Result<T, SolcError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SolcError {
|
||||
/// Internal solc error
|
||||
#[error("Solc Error: {0}")]
|
||||
SolcError(String),
|
||||
/// Deserialization error
|
||||
#[error(transparent)]
|
||||
SerdeJson(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// The result of a solc compilation
|
||||
pub struct CompiledContract {
|
||||
/// The contract's ABI
|
||||
pub abi: Abi,
|
||||
/// The contract's bytecode
|
||||
pub bytecode: Bytes,
|
||||
}
|
||||
|
||||
/// Solc builder
|
||||
pub struct Solc {
|
||||
/// The path where contracts will be read from
|
||||
pub paths: Vec<String>,
|
||||
|
||||
/// Number of runs
|
||||
pub optimizer: usize,
|
||||
|
||||
/// Evm Version
|
||||
pub evm_version: EvmVersion,
|
||||
|
||||
/// Paths for importing other libraries
|
||||
pub allowed_paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Solc {
|
||||
/// Instantiates the Solc builder for the provided paths
|
||||
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 {
|
||||
paths,
|
||||
optimizer: 200, // default optimizer runs = 200
|
||||
evm_version: EvmVersion::Istanbul,
|
||||
allowed_paths: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the contracts and returns a hashmap for each named contract
|
||||
pub fn build(self) -> Result<HashMap<String, CompiledContract>> {
|
||||
let mut command = Command::new(SOLC);
|
||||
|
||||
command
|
||||
.arg("--evm-version")
|
||||
.arg(self.evm_version.to_string())
|
||||
.arg("--combined-json")
|
||||
.arg("abi,bin");
|
||||
|
||||
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
|
||||
let output: SolcOutput = serde_json::from_slice(&command.stdout)?;
|
||||
|
||||
// Get the data in the correct format
|
||||
let contracts = output
|
||||
.contracts
|
||||
.into_iter()
|
||||
.map(|(name, contract)| {
|
||||
let abi = serde_json::from_str(&contract.abi)
|
||||
.expect("could not parse `solc` abi, this should never happen");
|
||||
|
||||
let bytecode = contract
|
||||
.bin
|
||||
.from_hex::<Vec<u8>>()
|
||||
.expect("solc did not produce valid bytecode")
|
||||
.into();
|
||||
|
||||
let name = name
|
||||
.rsplit(":")
|
||||
.next()
|
||||
.expect("could not strip fname")
|
||||
.to_owned();
|
||||
(name, CompiledContract { abi, bytecode })
|
||||
})
|
||||
.collect::<HashMap<String, CompiledContract>>();
|
||||
|
||||
Ok(contracts)
|
||||
}
|
||||
|
||||
/// Returns the output of `solc --version`
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `solc` is not in the user's $PATH
|
||||
pub fn version() -> String {
|
||||
let command_output = Command::new(SOLC)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.expect(&format!("`{}` not in user's $PATH", SOLC));
|
||||
|
||||
let version = command_output
|
||||
.stdout
|
||||
.lines()
|
||||
.last()
|
||||
.expect("expected version in solc output")
|
||||
.expect("could not get solc version");
|
||||
|
||||
// Return the version trimmed
|
||||
version.replace("Version: ", "")
|
||||
}
|
||||
|
||||
/// Sets the EVM version for compilation
|
||||
pub fn evm_version(mut self, version: EvmVersion) -> Self {
|
||||
self.evm_version = version;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the optimizer runs (default = 200)
|
||||
pub fn optimizer_runs(mut self, runs: 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
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EvmVersion {
|
||||
Homestead,
|
||||
TangerineWhistle,
|
||||
SpuriusDragon,
|
||||
Constantinople,
|
||||
Petersburg,
|
||||
Istanbul,
|
||||
Berlin,
|
||||
}
|
||||
|
||||
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",
|
||||
};
|
||||
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
|
||||
struct CompiledContractStr {
|
||||
abi: String,
|
||||
bin: String,
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
use anyhow::Result;
|
||||
use ethers::{providers::HttpProvider, signers::MainnetWallet, types::Address};
|
||||
use ethers::{
|
||||
contract::{abigen, ContractFactory},
|
||||
providers::HttpProvider,
|
||||
signers::MainnetWallet,
|
||||
types::Solc,
|
||||
utils::ganache::GanacheBuilder,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use ethers::contract::abigen;
|
||||
|
||||
// Generate the contract code
|
||||
// Generate the contract bindings by providing the ABI
|
||||
abigen!(
|
||||
SimpleContract,
|
||||
r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","constant": true, "type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#,
|
||||
|
@ -13,27 +17,48 @@ abigen!(
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// connect to the network
|
||||
let provider = HttpProvider::try_from("http://localhost:8545")?;
|
||||
// 1. compile the contract (note this requires that you are inside the `ethers/examples` directory)
|
||||
let compiled = Solc::new("./contract.sol").build()?;
|
||||
let contract = compiled
|
||||
.get("SimpleStorage")
|
||||
.expect("could not find contract");
|
||||
|
||||
// create a wallet and connect it to the provider
|
||||
let client = "ea878d94d9b1ffc78b45fc7bfc72ec3d1ce6e51e80c8e376c3f7c9a861f7c214"
|
||||
.parse::<MainnetWallet>()?
|
||||
.connect(&provider);
|
||||
// 2. launch ganache
|
||||
let port = 8546u64;
|
||||
let url = format!("http://localhost:{}", port).to_string();
|
||||
let _ganache = GanacheBuilder::new().port(port)
|
||||
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
||||
.spawn();
|
||||
|
||||
// Contract should take both provider or a signer
|
||||
// 3. instantiate our wallet
|
||||
let wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
|
||||
.parse::<MainnetWallet>()?;
|
||||
|
||||
// get the contract's address
|
||||
let addr = "ebBe15d9C365fC8a04a82E06644d6B39aF20cC31".parse::<Address>()?;
|
||||
// 4. connect to the network
|
||||
let provider = HttpProvider::try_from(url.as_str())?;
|
||||
|
||||
// instantiate it
|
||||
let contract = SimpleContract::new(addr, &client);
|
||||
// 5. instantiate the client with the wallet
|
||||
let client = wallet.connect(&provider);
|
||||
|
||||
// call the method
|
||||
// 6. create a factory which will be used to deploy instances of the contract
|
||||
let factory = ContractFactory::new(&client, &contract.abi, &contract.bytecode);
|
||||
|
||||
// 7. deploy it with the constructor arguments
|
||||
let contract = factory.deploy("initial value".to_string())?.send().await?;
|
||||
|
||||
// 8. get the contract's address
|
||||
let addr = contract.address();
|
||||
|
||||
// 9. instantiate the contract
|
||||
let contract = SimpleContract::new(addr.clone(), &client);
|
||||
|
||||
// 10. call the `setValue` method
|
||||
let _tx_hash = contract.set_value("hi".to_owned()).send().await?;
|
||||
|
||||
// 11. get all events
|
||||
let logs = contract.value_changed().from_block(0u64).query().await?;
|
||||
|
||||
// 12. get the new value
|
||||
let value = contract.get_value().call().await?;
|
||||
|
||||
println!("Value: {}. Logs: {}", value, serde_json::to_string(&logs)?);
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
pragma solidity >=0.4.24;
|
||||
|
||||
contract SimpleStorage {
|
||||
|
||||
event ValueChanged(address indexed author, string oldValue, string newValue);
|
||||
|
||||
string _value;
|
||||
|
||||
constructor(string memory value) public {
|
||||
emit ValueChanged(msg.sender, _value, value);
|
||||
_value = value;
|
||||
}
|
||||
|
||||
function getValue() view public returns (string memory) {
|
||||
return _value;
|
||||
}
|
||||
|
||||
function setValue(string memory value) public {
|
||||
emit ValueChanged(msg.sender, _value, value);
|
||||
_value = value;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue