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)",
|
"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)",
|
"ethereum-types 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ethers-utils 0.1.0",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.2.5"
|
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-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 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 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 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 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"
|
"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
|
/// Deploys an instance of the contract with the provider constructor arguments
|
||||||
/// and returns the contract's instance
|
/// and returns the contract's instance
|
||||||
pub async fn deploy<T: Tokenize>(
|
pub fn deploy<T: Tokenize>(
|
||||||
&self,
|
&self,
|
||||||
constructor_args: T,
|
constructor_args: T,
|
||||||
) -> Result<Deployer<'a, P, N, S>, ContractError<P>> {
|
) -> Result<Deployer<'a, P, N, S>, ContractError<P>> {
|
||||||
|
|
|
@ -19,9 +19,11 @@ zeroize = { version = "1.1.0", default-features = false }
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
serde = { version = "1.0.110", default-features = false, features = ["derive"] }
|
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 }
|
rustc-hex = { version = "2.1.0", default-features = false }
|
||||||
thiserror = { version = "1.0.19", default-features = false }
|
thiserror = { version = "1.0.19", default-features = false }
|
||||||
arrayvec = { version = "0.5.1", default-features = false, optional = true }
|
arrayvec = { version = "0.5.1", default-features = false, optional = true }
|
||||||
|
glob = "0.3.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = { version = "1.0.53", default-features = false }
|
serde_json = { version = "1.0.53", default-features = false }
|
||||||
|
|
|
@ -46,3 +46,6 @@ pub mod abi;
|
||||||
|
|
||||||
// Convenience re-export
|
// Convenience re-export
|
||||||
pub use ethers_utils as utils;
|
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 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 std::convert::TryFrom;
|
||||||
|
|
||||||
use ethers::contract::abigen;
|
// Generate the contract bindings by providing the ABI
|
||||||
|
|
||||||
// Generate the contract code
|
|
||||||
abigen!(
|
abigen!(
|
||||||
SimpleContract,
|
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"}]"#,
|
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]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// connect to the network
|
// 1. compile the contract (note this requires that you are inside the `ethers/examples` directory)
|
||||||
let provider = HttpProvider::try_from("http://localhost:8545")?;
|
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
|
// 2. launch ganache
|
||||||
let client = "ea878d94d9b1ffc78b45fc7bfc72ec3d1ce6e51e80c8e376c3f7c9a861f7c214"
|
let port = 8546u64;
|
||||||
.parse::<MainnetWallet>()?
|
let url = format!("http://localhost:{}", port).to_string();
|
||||||
.connect(&provider);
|
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
|
// 4. connect to the network
|
||||||
let addr = "ebBe15d9C365fC8a04a82E06644d6B39aF20cC31".parse::<Address>()?;
|
let provider = HttpProvider::try_from(url.as_str())?;
|
||||||
|
|
||||||
// instantiate it
|
// 5. instantiate the client with the wallet
|
||||||
let contract = SimpleContract::new(addr, &client);
|
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?;
|
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?;
|
let logs = contract.value_changed().from_block(0u64).query().await?;
|
||||||
|
|
||||||
|
// 12. get the new value
|
||||||
let value = contract.get_value().call().await?;
|
let value = contract.get_value().call().await?;
|
||||||
|
|
||||||
println!("Value: {}. Logs: {}", value, serde_json::to_string(&logs)?);
|
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