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

View File

@ -1,6 +1,6 @@
use super::{types, util, Context}; use super::{types, util, Context};
use ethers_core::{ use ethers_core::{
abi::{Function, FunctionExt, Param}, abi::{Function, FunctionExt, Param, StateMutability},
types::Selector, types::Selector,
}; };
@ -40,7 +40,11 @@ fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStr
let outputs = expand_fn_outputs(&function.outputs)?; 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> } quote! { ContractCall<'a, P, S, #outputs> }
} else { } else {
quote! { ContractCall<'a, P, S, H256> } 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); let size = Literal::usize_unsuffixed(*n);
Ok(quote! { [#inner; #size] }) Ok(quote! { [#inner; #size] })
} }
// TODO: Implement abiencoder v2 ParamType::Tuple(members) => {
ParamType::Tuple(_) => Err(anyhow!("ABIEncoderV2 is currently not supported")), 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 /// 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 /// # Example
/// ///
/// Running the command below will generate a file called `token.rs` containing the /// 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 crate::spanned::{ParseInner, Spanned};
use ethers_contract_abigen::Abigen; 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 proc_macro2::{Span, TokenStream as TokenStream2};
use quote::ToTokens; use quote::ToTokens;
@ -180,7 +180,7 @@ impl Parse for Method {
// NOTE: The output types and const-ness of the function do not // NOTE: The output types and const-ness of the function do not
// affect its signature. // affect its signature.
outputs: vec![], outputs: vec![],
constant: false, state_mutability: StateMutability::Nonpayable,
} }
}; };
let signature = function.abi_signature(); let signature = function.abi_signature();

View File

@ -8,7 +8,7 @@ edition = "2018"
# ethereum related # ethereum related
ethereum-types = { version = "0.9.2", default-features = false, features = ["serialize"] } ethereum-types = { version = "0.9.2", default-features = false, features = ["serialize"] }
rlp = { version = "0.4.5", default-features = false } 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 # crypto
secp256k1 = { package = "libsecp256k1", version = "0.3.5" } secp256k1 = { package = "libsecp256k1", version = "0.3.5" }

View File

@ -63,17 +63,20 @@ mod tests {
#[test] #[test]
fn format_function_signature() { fn format_function_signature() {
for (f, expected) in &[ 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)", "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)", "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()", "bax()",
), ),
] { ] {

View File

@ -21,6 +21,15 @@ pub trait Detokenize {
Self: Sized; 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 { impl<T: Tokenizable> Detokenize for T {
fn from_tokens(mut tokens: Vec<Token>) -> Result<Self, InvalidOutputType> { fn from_tokens(mut tokens: Vec<Token>) -> Result<Self, InvalidOutputType> {
if tokens.len() != 1 { if tokens.len() != 1 {
@ -69,17 +78,17 @@ impl_output!(2, A, B,);
impl_output!(3, A, B, C,); impl_output!(3, A, B, C,);
impl_output!(4, A, B, C, D,); impl_output!(4, A, B, C, D,);
impl_output!(5, A, B, C, D, E,); impl_output!(5, A, B, C, D, E,);
// impl_output!(6, A, B, C, D, E, F,); impl_output!(6, A, B, C, D, E, F,);
// impl_output!(7, A, B, C, D, E, F, G,); impl_output!(7, A, B, C, D, E, F, G,);
// impl_output!(8, A, B, C, D, E, F, G, H,); 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!(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!(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!(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!(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!(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!(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!(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!(16, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P,);
/// Tokens conversion trait /// Tokens conversion trait
pub trait Tokenize { 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, );
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, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, ); impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, );
impl_tokens!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, );
// 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, I:8, );
// 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, J:9, );
// 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, K:10, );
// 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, 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, ); 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, ); 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, ); 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, ); 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, 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. /// Simplified output type for single value.
pub trait Tokenizable { pub trait Tokenizable {

View File

@ -85,8 +85,8 @@ impl Solc {
} }
} }
/// Builds the contracts and returns a hashmap for each named contract /// Gets the ABI for the contracts
pub fn build(self) -> Result<HashMap<String, CompiledContract>> { pub fn build_raw(self) -> Result<HashMap<String, CompiledContractStr>> {
let mut command = Command::new(SOLC); let mut command = Command::new(SOLC);
command command
@ -110,25 +110,46 @@ impl Solc {
// Deserialize the output // Deserialize the output
let output: SolcOutput = serde_json::from_slice(&command.stdout)?; 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 let contracts = output
.contracts .contracts
.into_iter() .into_iter()
.map(|(name, contract)| { .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 let name = name
.rsplit(':') .rsplit(':')
.next() .next()
.expect("could not strip fname") .expect("could not strip fname")
.to_owned(); .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 }) (name, CompiledContract { abi, bytecode })
}) })
.collect::<HashMap<String, CompiledContract>>(); .collect::<HashMap<String, CompiledContract>>();
@ -212,8 +233,10 @@ struct SolcOutput {
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
// Helper struct for deserializing the solc string outputs /// Helper struct for deserializing the solc string outputs
struct CompiledContractStr { pub struct CompiledContractStr {
abi: String, /// The contract's raw ABI
bin: String, 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 // Generate the type-safe contract bindings by providing the ABI
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","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#,
event_derives(serde::Deserialize, serde::Serialize) event_derives(serde::Deserialize, serde::Serialize)
); );

View File

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