ABI Encoder v2 + ABI Spec v6.6 (#17)

* feat(core): update ethabi and enable more Toeknize impls

* feat(contract/abigen): implement simple AbiEncoderV2

* tests(ethers): add abigen example

* fix(core): fix abi tests

* chore: make clippy happy
This commit is contained in:
Georgios Konstantopoulos 2020-06-16 15:08:42 +03:00 committed by GitHub
parent 5629c1f25e
commit 570b45eb10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 138 additions and 57 deletions

3
Cargo.lock generated
View File

@ -234,8 +234,7 @@ dependencies = [
[[package]]
name = "ethabi"
version = "12.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052a565e3de82944527d6d10a465697e6bb92476b772ca7141080c901f6a63c6"
source = "git+https://github.com/gakonst/ethabi#bd8214a5897f323b4832cd56430eb74bc6e06cda"
dependencies = [
"ethereum-types",
"rustc-hex",

View File

@ -1,6 +1,6 @@
use super::{types, util, Context};
use ethers_core::{
abi::{Function, FunctionExt, Param},
abi::{Function, FunctionExt, Param, StateMutability},
types::Selector,
};
@ -40,7 +40,11 @@ fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStr
let outputs = expand_fn_outputs(&function.outputs)?;
let result = if function.constant {
let is_mutable = matches!(
function.state_mutability,
StateMutability::Nonpayable | StateMutability::Payable
);
let result = if !is_mutable {
quote! { ContractCall<'a, P, S, #outputs> }
} else {
quote! { ContractCall<'a, P, S, H256> }

View File

@ -43,7 +43,16 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
let size = Literal::usize_unsuffixed(*n);
Ok(quote! { [#inner; #size] })
}
// TODO: Implement abiencoder v2
ParamType::Tuple(_) => Err(anyhow!("ABIEncoderV2 is currently not supported")),
ParamType::Tuple(members) => {
if members.is_empty() {
return Err(anyhow!("Tuple must have at least 1 member"));
}
let members = members
.iter()
.map(|member| expand(member))
.collect::<Result<Vec<_>, _>>()?;
Ok(quote! { (#(#members,)*) })
}
}
}

View File

@ -29,6 +29,10 @@ use std::{collections::HashMap, fs::File, io::Write, path::Path};
/// Builder struct for generating type-safe bindings from a contract's ABI
///
/// Note: Your contract's ABI must contain the `stateMutability` field. This is
/// [still not supported by Vyper](https://github.com/vyperlang/vyper/issues/1931), so you must adjust your ABIs and replace
/// `constant` functions with `view` or `pure`.
///
/// # Example
///
/// Running the command below will generate a file called `token.rs` containing the

View File

@ -3,7 +3,7 @@
use crate::spanned::{ParseInner, Spanned};
use ethers_contract_abigen::Abigen;
use ethers_core::abi::{Function, FunctionExt, Param};
use ethers_core::abi::{Function, FunctionExt, Param, StateMutability};
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::ToTokens;
@ -180,7 +180,7 @@ impl Parse for Method {
// NOTE: The output types and const-ness of the function do not
// affect its signature.
outputs: vec![],
constant: false,
state_mutability: StateMutability::Nonpayable,
}
};
let signature = function.abi_signature();

View File

@ -8,7 +8,7 @@ edition = "2018"
# ethereum related
ethereum-types = { version = "0.9.2", default-features = false, features = ["serialize"] }
rlp = { version = "0.4.5", default-features = false }
ethabi = { version = "12.0.0", default-features = false, optional = true }
ethabi = { git = "https://github.com/gakonst/ethabi", version = "12.0.0", default-features = false, optional = true }
# crypto
secp256k1 = { package = "libsecp256k1", version = "0.3.5" }

View File

@ -63,17 +63,20 @@ mod tests {
#[test]
fn format_function_signature() {
for (f, expected) in &[
(r#"{"name":"foo","inputs":[],"outputs":[]}"#, "foo()"),
(
r#"{"name":"bar","inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"bool"}],"outputs":[]}"#,
r#"{"name":"foo","inputs":[],"outputs":[], "stateMutability": "nonpayable"}"#,
"foo()",
),
(
r#"{"name":"bar","inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"bool"}],"outputs":[], "stateMutability": "nonpayable"}"#,
"bar(uint256,bool)",
),
(
r#"{"name":"baz","inputs":[{"name":"a","type":"uint256"}],"outputs":[{"name":"b","type":"bool"}]}"#,
r#"{"name":"baz","inputs":[{"name":"a","type":"uint256"}],"outputs":[{"name":"b","type":"bool"}], "stateMutability": "nonpayable"}"#,
"baz(uint256)",
),
(
r#"{"name":"bax","inputs":[],"outputs":[{"name":"a","type":"uint256"},{"name":"b","type":"bool"}]}"#,
r#"{"name":"bax","inputs":[],"outputs":[{"name":"a","type":"uint256"},{"name":"b","type":"bool"}], "stateMutability": "nonpayable"}"#,
"bax()",
),
] {

View File

@ -21,6 +21,15 @@ pub trait Detokenize {
Self: Sized;
}
impl Detokenize for () {
fn from_tokens(_: Vec<Token>) -> std::result::Result<Self, InvalidOutputType>
where
Self: Sized,
{
Ok(())
}
}
impl<T: Tokenizable> Detokenize for T {
fn from_tokens(mut tokens: Vec<Token>) -> Result<Self, InvalidOutputType> {
if tokens.len() != 1 {
@ -69,17 +78,17 @@ impl_output!(2, A, B,);
impl_output!(3, A, B, C,);
impl_output!(4, A, B, C, D,);
impl_output!(5, A, B, C, D, E,);
// impl_output!(6, A, B, C, D, E, F,);
// impl_output!(7, A, B, C, D, E, F, G,);
// impl_output!(8, A, B, C, D, E, F, G, H,);
// impl_output!(9, A, B, C, D, E, F, G, H, I,);
// impl_output!(10, A, B, C, D, E, F, G, H, I, J,);
// impl_output!(11, A, B, C, D, E, F, G, H, I, J, K,);
// impl_output!(12, A, B, C, D, E, F, G, H, I, J, K, L,);
// impl_output!(13, A, B, C, D, E, F, G, H, I, J, K, L, M,);
// impl_output!(14, A, B, C, D, E, F, G, H, I, J, K, L, M, N,);
// impl_output!(15, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O,);
// impl_output!(16, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P,);
impl_output!(6, A, B, C, D, E, F,);
impl_output!(7, A, B, C, D, E, F, G,);
impl_output!(8, A, B, C, D, E, F, G, H,);
impl_output!(9, A, B, C, D, E, F, G, H, I,);
impl_output!(10, A, B, C, D, E, F, G, H, I, J,);
impl_output!(11, A, B, C, D, E, F, G, H, I, J, K,);
impl_output!(12, A, B, C, D, E, F, G, H, I, J, K, L,);
impl_output!(13, A, B, C, D, E, F, G, H, I, J, K, L, M,);
impl_output!(14, A, B, C, D, E, F, G, H, I, J, K, L, M, N,);
impl_output!(15, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O,);
impl_output!(16, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P,);
/// Tokens conversion trait
pub trait Tokenize {
@ -128,17 +137,15 @@ impl_tokens!(A:0, B:1, C:2, D:3, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, );
// Commented out macros to reduce codegen time. Re-enable if needed.
// impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, );
// impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, );
// impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, );
// impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, );
// impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, );
// impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, );
// impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, );
// impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, );
// impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, );
/// Simplified output type for single value.
pub trait Tokenizable {

View File

@ -85,8 +85,8 @@ impl Solc {
}
}
/// Builds the contracts and returns a hashmap for each named contract
pub fn build(self) -> Result<HashMap<String, CompiledContract>> {
/// Gets the ABI for the contracts
pub fn build_raw(self) -> Result<HashMap<String, CompiledContractStr>> {
let mut command = Command::new(SOLC);
command
@ -110,25 +110,46 @@ impl Solc {
// Deserialize the output
let output: SolcOutput = serde_json::from_slice(&command.stdout)?;
// Get the data in the correct format
// remove the semi-colon and the name
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,
CompiledContractStr {
abi: contract.abi,
bin: contract.bin,
},
)
})
.collect();
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 = contract
.bin
.from_hex::<Vec<u8>>()
.expect("solc did not produce valid bytecode")
.into();
(name, CompiledContract { abi, bytecode })
})
.collect::<HashMap<String, CompiledContract>>();
@ -212,8 +233,10 @@ struct SolcOutput {
}
#[derive(Clone, Debug, Serialize, Deserialize)]
// Helper struct for deserializing the solc string outputs
struct CompiledContractStr {
abi: String,
bin: String,
/// 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,
}

30
ethers/examples/abigen.rs Normal file
View File

@ -0,0 +1,30 @@
use ethers::{contract::Abigen, utils::Solc};
fn main() -> anyhow::Result<()> {
let mut args = std::env::args();
args.next().unwrap(); // skip program name
let contract_name = args.next().unwrap();
let contract: String = args.next().unwrap();
println!("Generating bindings for {}\n", contract);
// compile it if needed
let abi = if contract.ends_with(".sol") {
let contracts = Solc::new(&contract).build_raw()?;
contracts.get(&contract_name).unwrap().abi.clone()
} else {
contract
};
let bindings = Abigen::new(&contract_name, abi)?.generate()?;
// print to stdout if no output arg is given
if let Some(output_path) = args.next() {
bindings.write_to_file(&output_path)?;
} else {
bindings.write(std::io::stdout())?;
}
Ok(())
}

View File

@ -8,7 +8,7 @@ use std::convert::TryFrom;
// Generate the type-safe 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"}]"#,
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","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#,
event_derives(serde::Deserialize, serde::Serialize)
);

View File

@ -10,10 +10,12 @@ abigen!(
Comptroller,
"etherscan:0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b"
);
abigen!(
Curve,
"etherscan:0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56"
);
// https://github.com/vyperlang/vyper/issues/1931
// abigen!(
// Curve,
// "etherscan:0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56"
// );
abigen!(
UmaAdmin,
"etherscan:0x4E6CCB1dA3C7844887F9A5aF4e8450d9fd90317A"
@ -28,7 +30,7 @@ abigen!(
}
);
// Abi Encoder v2 not yet supported :(
// // Abi Encoder v2 is still buggy
// abigen!(
// DyDxLimitOrders,
// "etherscan:0xDEf136D9884528e1EB302f39457af0E4d3AD24EB"