Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

ABI Encoder v2 + ABI Spec v6.6 #17

Merged
merged 5 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{types, util, Context};
use ethers_core::{
abi::{Function, FunctionExt, Param},
abi::{Function, FunctionExt, Param, StateMutability},
types::Selector,
};

Expand Down Expand Up @@ -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> }
Expand Down
13 changes: 11 additions & 2 deletions ethers-contract/ethers-contract-abigen/src/contract/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,)*) })
}
}
}
4 changes: 4 additions & 0 deletions ethers-contract/ethers-contract-abigen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions ethers-contract/ethers-contract-derive/src/abigen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion ethers-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
11 changes: 7 additions & 4 deletions ethers-core/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()",
),
] {
Expand Down
51 changes: 29 additions & 22 deletions ethers-core/src/abi/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
49 changes: 36 additions & 13 deletions ethers-core/src/utils/solc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 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();

let name = name
.rsplit(':')
.next()
.expect("could not strip fname")
.to_owned();
(name, CompiledContract { abi, bytecode })
})
.collect::<HashMap<String, CompiledContract>>();
Expand Down Expand Up @@ -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 changes: 30 additions & 0 deletions ethers/examples/abigen.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
2 changes: 1 addition & 1 deletion ethers/examples/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);

Expand Down
12 changes: 7 additions & 5 deletions ethers/tests/major_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -28,7 +30,7 @@ abigen!(
}
);

// Abi Encoder v2 not yet supported :(
// // Abi Encoder v2 is still buggy
// abigen!(
// DyDxLimitOrders,
// "etherscan:0xDEf136D9884528e1EB302f39457af0E4d3AD24EB"
Expand Down