diff --git a/Cargo.lock b/Cargo.lock index 39a85304..60e65146 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 28df0a57..4494b747 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -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) -> Result } } else { quote! { ContractCall<'a, P, S, H256> } diff --git a/ethers-contract/ethers-contract-abigen/src/contract/types.rs b/ethers-contract/ethers-contract-abigen/src/contract/types.rs index b36c9f66..30efaac2 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/types.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/types.rs @@ -43,7 +43,16 @@ pub(crate) fn expand(kind: &ParamType) -> Result { 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::, _>>()?; + Ok(quote! { (#(#members,)*) }) + } } } diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs index 929ed8c0..e3f69fd4 100644 --- a/ethers-contract/ethers-contract-abigen/src/lib.rs +++ b/ethers-contract/ethers-contract-abigen/src/lib.rs @@ -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 diff --git a/ethers-contract/ethers-contract-derive/src/abigen.rs b/ethers-contract/ethers-contract-derive/src/abigen.rs index afc98400..2d095562 100644 --- a/ethers-contract/ethers-contract-derive/src/abigen.rs +++ b/ethers-contract/ethers-contract-derive/src/abigen.rs @@ -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(); diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index ca9305a7..08b11b26 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -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" } diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs index 1c5184ad..437717e5 100644 --- a/ethers-core/src/abi/mod.rs +++ b/ethers-core/src/abi/mod.rs @@ -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()", ), ] { diff --git a/ethers-core/src/abi/tokens.rs b/ethers-core/src/abi/tokens.rs index 525396c2..a425e663 100644 --- a/ethers-core/src/abi/tokens.rs +++ b/ethers-core/src/abi/tokens.rs @@ -21,6 +21,15 @@ pub trait Detokenize { Self: Sized; } +impl Detokenize for () { + fn from_tokens(_: Vec) -> std::result::Result + where + Self: Sized, + { + Ok(()) + } +} + impl Detokenize for T { fn from_tokens(mut tokens: Vec) -> Result { 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 { diff --git a/ethers-core/src/utils/solc.rs b/ethers-core/src/utils/solc.rs index 3944a582..71adecee 100644 --- a/ethers-core/src/utils/solc.rs +++ b/ethers-core/src/utils/solc.rs @@ -85,8 +85,8 @@ impl Solc { } } - /// Builds the contracts and returns a hashmap for each named contract - pub fn build(self) -> Result> { + /// Gets the ABI for the contracts + pub fn build_raw(self) -> Result> { 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::>() - .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> { + // 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::>() + .expect("solc did not produce valid bytecode") + .into(); (name, CompiledContract { abi, bytecode }) }) .collect::>(); @@ -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, } diff --git a/ethers/examples/abigen.rs b/ethers/examples/abigen.rs new file mode 100644 index 00000000..4ea39bc6 --- /dev/null +++ b/ethers/examples/abigen.rs @@ -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(()) +} diff --git a/ethers/examples/contract.rs b/ethers/examples/contract.rs index ebc24af1..8dde1a29 100644 --- a/ethers/examples/contract.rs +++ b/ethers/examples/contract.rs @@ -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) ); diff --git a/ethers/tests/major_contracts.rs b/ethers/tests/major_contracts.rs index 5d67abe9..b9d28863 100644 --- a/ethers/tests/major_contracts.rs +++ b/ethers/tests/major_contracts.rs @@ -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"