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:
parent
5629c1f25e
commit
570b45eb10
|
@ -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",
|
||||
|
|
|
@ -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> }
|
||||
|
|
|
@ -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,)*) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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()",
|
||||
),
|
||||
] {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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)
|
||||
);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue