feat(abigen): use AbiType when parsing Function abi signature fails at compile time (#647)

* refactor: improved from token impl

* feat: add AbiType support

* feat: no need for expect

* feat: add missing abiarraytype impl

* test: add struct derive test

* chore: rustfmt

* chore: update changelog

* chore: rustfmt
This commit is contained in:
Matthias Seitz 2021-12-04 05:19:00 +01:00 committed by GitHub
parent 0f6d3688c2
commit 2a3fcbbb40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 208 additions and 42 deletions

View File

@ -92,6 +92,8 @@
### Unreleased ### Unreleased
- Add AbiType implementation during EthAbiType expansion
[#647](https://github.com/gakonst/ethers-rs/pull/647)
- fix Etherscan conditional HTTP support - fix Etherscan conditional HTTP support
[#632](https://github.com/gakonst/ethers-rs/pull/632) [#632](https://github.com/gakonst/ethers-rs/pull/632)
- use `CARGO_MANIFEST_DIR` as root for relative paths in abigen - use `CARGO_MANIFEST_DIR` as root for relative paths in abigen

View File

@ -1,5 +1,6 @@
//! Helper functions for deriving `EthAbiType` //! Helper functions for deriving `EthAbiType`
use crate::utils;
use ethers_core::macros::ethers_core_crate; use ethers_core::macros::ethers_core_crate;
use proc_macro2::{Ident, Literal, TokenStream}; use proc_macro2::{Ident, Literal, TokenStream};
use quote::{quote, quote_spanned}; use quote::{quote, quote_spanned};
@ -38,7 +39,7 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
let assignments = fields.named.iter().map(|f| { let assignments = fields.named.iter().map(|f| {
let name = f.ident.as_ref().expect("Named fields have names"); let name = f.ident.as_ref().expect("Named fields have names");
quote_spanned! { f.span() => #name: #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? } quote_spanned! { f.span() => #name: #core_crate::abi::Tokenizable::from_token(iter.next().unwrap())? }
}); });
let init_struct_impl = quote! { Self { #(#assignments,)* } }; let init_struct_impl = quote! { Self { #(#assignments,)* } };
@ -58,7 +59,7 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
let tokenize_predicates = quote! { #(#tokenize_predicates,)* }; let tokenize_predicates = quote! { #(#tokenize_predicates,)* };
let assignments = fields.unnamed.iter().map(|f| { let assignments = fields.unnamed.iter().map(|f| {
quote_spanned! { f.span() => #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? } quote_spanned! { f.span() => #core_crate::abi::Tokenizable::from_token(iter.next().unwrap())? }
}); });
let init_struct_impl = quote! { Self(#(#assignments,)* ) }; let init_struct_impl = quote! { Self(#(#assignments,)* ) };
@ -131,7 +132,20 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
} }
}; };
let params = match utils::derive_param_type_with_abi_type(input, "EthAbiType") {
Ok(params) => params,
Err(err) => return err.to_compile_error(),
};
quote! { quote! {
impl<#generic_params> #core_crate::abi::AbiType for #name<#generic_args> {
fn param_type() -> #core_crate::abi::ParamType {
#params
}
}
impl<#generic_params> #core_crate::abi::AbiArrayType for #name<#generic_args> {}
impl<#generic_params> #core_crate::abi::Tokenizable for #name<#generic_args> impl<#generic_params> #core_crate::abi::Tokenizable for #name<#generic_args>
where where
#generic_predicates #generic_predicates

View File

@ -13,11 +13,6 @@ use crate::{abi_ty, utils};
/// Generates the `ethcall` trait support /// Generates the `ethcall` trait support
pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream { pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
// the ethers crates to use
let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate();
let name = &input.ident;
let attributes = match parse_call_attributes(&input) { let attributes = match parse_call_attributes(&input) {
Ok(attributes) => attributes, Ok(attributes) => attributes,
Err(errors) => return errors, Err(errors) => return errors,
@ -56,27 +51,58 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
state_mutability: Default::default(), state_mutability: Default::default(),
} }
} else { } else {
return Error::new(span, format!("Unable to determine ABI: {}", src)) // try to determine the abi by using its fields at runtime
.to_compile_error() return match derive_trait_impls_with_abi_type(&input, &function_call_name) {
Ok(derived) => derived,
Err(err) => {
Error::new(span, format!("Unable to determine ABI for `{}` : {}", src, err))
.to_compile_error()
}
}
} }
} }
} else { } else {
// // try to determine the abi from the fields // try to determine the abi by using its fields at runtime
match derive_abi_function_from_fields(&input) { return match derive_trait_impls_with_abi_type(&input, &function_call_name) {
Ok(event) => event, Ok(derived) => derived,
Err(err) => return err.to_compile_error(), Err(err) => err.to_compile_error(),
} }
}; };
function.name = function_call_name.clone(); function.name = function_call_name.clone();
let abi = function.abi_signature(); let abi = function.abi_signature();
let selector = utils::selector(function.selector()); let selector = utils::selector(function.selector());
let decode_impl = derive_decode_impl_from_function(&function);
let decode_impl = derive_decode_impl(&function); derive_trait_impls(
&input,
&function_call_name,
quote! {#abi.into()},
Some(selector),
decode_impl,
)
}
/// Generates the EthCall implementation
pub fn derive_trait_impls(
input: &DeriveInput,
function_call_name: &str,
abi_signature: TokenStream,
selector: Option<TokenStream>,
decode_impl: TokenStream,
) -> TokenStream {
// the ethers crates to use
let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate();
let struct_name = &input.ident;
let selector = selector.unwrap_or_else(|| {
quote! {
#core_crate::utils::id(Self::abi_signature())
}
});
let ethcall_impl = quote! { let ethcall_impl = quote! {
impl #contract_crate::EthCall for #name { impl #contract_crate::EthCall for #struct_name {
fn function_name() -> ::std::borrow::Cow<'static, str> { fn function_name() -> ::std::borrow::Cow<'static, str> {
#function_call_name.into() #function_call_name.into()
@ -87,17 +113,17 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
} }
fn abi_signature() -> ::std::borrow::Cow<'static, str> { fn abi_signature() -> ::std::borrow::Cow<'static, str> {
#abi.into() #abi_signature
} }
} }
impl #core_crate::abi::AbiDecode for #name { impl #core_crate::abi::AbiDecode for #struct_name {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, #core_crate::abi::AbiError> { fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, #core_crate::abi::AbiError> {
#decode_impl #decode_impl
} }
} }
impl #core_crate::abi::AbiEncode for #name { impl #core_crate::abi::AbiEncode for #struct_name {
fn encode(self) -> ::std::vec::Vec<u8> { fn encode(self) -> ::std::vec::Vec<u8> {
let tokens = #core_crate::abi::Tokenize::into_tokens(self); let tokens = #core_crate::abi::Tokenize::into_tokens(self);
let selector = <Self as #contract_crate::EthCall>::selector(); let selector = <Self as #contract_crate::EthCall>::selector();
@ -111,7 +137,7 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
} }
}; };
let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input); let tokenize_impl = abi_ty::derive_tokenizeable_impl(input);
quote! { quote! {
#tokenize_impl #tokenize_impl
@ -119,12 +145,23 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
} }
} }
fn derive_decode_impl(function: &Function) -> TokenStream { /// Generates the decode implementation based on the function's input types
fn derive_decode_impl_from_function(function: &Function) -> TokenStream {
let datatypes = function.inputs.iter().map(|input| utils::param_type_quote(&input.kind));
let datatypes_array = quote! {[#( #datatypes ),*]};
derive_decode_impl(datatypes_array)
}
/// Generates the decode implementation based on the function's runtime `AbiType` impl
fn derive_decode_impl_with_abi_type(input: &DeriveInput) -> Result<TokenStream, Error> {
let datatypes_array = utils::derive_abi_parameters_array(input, "EthCall")?;
Ok(derive_decode_impl(datatypes_array))
}
fn derive_decode_impl(datatypes_array: TokenStream) -> TokenStream {
let core_crate = ethers_core_crate(); let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate(); let contract_crate = ethers_contract_crate();
let data_types = function.inputs.iter().map(|input| utils::param_type_quote(&input.kind)); let data_types_init = quote! {let data_types = #datatypes_array;};
let data_types_init = quote! {let data_types = [#( #data_types ),*];};
quote! { quote! {
let bytes = bytes.as_ref(); let bytes = bytes.as_ref();
@ -137,20 +174,18 @@ fn derive_decode_impl(function: &Function) -> TokenStream {
} }
} }
/// Determine the function's ABI by parsing the AST /// Use the `AbiType` trait to determine the correct `ParamType` and signature at runtime
fn derive_abi_function_from_fields(input: &DeriveInput) -> Result<Function, Error> { fn derive_trait_impls_with_abi_type(
#[allow(deprecated)] input: &DeriveInput,
let function = Function { function_call_name: &str,
name: "".to_string(), ) -> Result<TokenStream, Error> {
inputs: utils::derive_abi_inputs_from_fields(input, "EthCall")? let abi_signature =
.into_iter() utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthCall")?;
.map(|(name, kind)| Param { name, kind, internal_type: None }) let abi_signature = quote! {
.collect(), ::std::borrow::Cow::Owned(#abi_signature)
outputs: vec![],
constant: false,
state_mutability: Default::default(),
}; };
Ok(function) let decode_impl = derive_decode_impl_with_abi_type(input)?;
Ok(derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl))
} }
/// All the attributes the `EthCall` macro supports /// All the attributes the `EthCall` macro supports

View File

@ -1,6 +1,6 @@
use ethers_core::{abi::ParamType, macros::ethers_core_crate, types::Selector}; use ethers_core::{abi::ParamType, macros::ethers_core_crate, types::Selector};
use proc_macro2::Literal; use proc_macro2::Literal;
use quote::quote; use quote::{quote, quote_spanned};
use syn::{ use syn::{
parse::Error, spanned::Spanned as _, Data, DeriveInput, Expr, Fields, GenericArgument, Lit, parse::Error, spanned::Spanned as _, Data, DeriveInput, Expr, Fields, GenericArgument, Lit,
PathArguments, Type, PathArguments, Type,
@ -187,3 +187,88 @@ pub fn derive_abi_inputs_from_fields(
}) })
.collect() .collect()
} }
/// Use `AbiType::param_type` fo each field to construct the input types own param type
pub fn derive_param_type_with_abi_type(
input: &DeriveInput,
trait_name: &str,
) -> Result<proc_macro2::TokenStream, Error> {
let core_crate = ethers_core_crate();
let params = derive_abi_parameters_array(input, trait_name)?;
Ok(quote! {
#core_crate::abi::ParamType::Tuple(::std::vec!#params)
})
}
/// Use `AbiType::param_type` fo each field to construct the whole signature `<name>(<params,>*)` as
/// `String`
pub fn derive_abi_signature_with_abi_type(
input: &DeriveInput,
function_name: &str,
trait_name: &str,
) -> Result<proc_macro2::TokenStream, Error> {
let params = derive_abi_parameters_array(input, trait_name)?;
Ok(quote! {
{
let params: String = #params
.iter()
.map(|p| p.to_string())
.collect::<::std::vec::Vec<_>>()
.join(",");
let function_name = #function_name;
format!("{}({})", function_name, params)
}
})
}
/// Use `AbiType::param_type` fo each field to construct the signature's parameters as runtime array
/// `[param1, param2,...]`
pub fn derive_abi_parameters_array(
input: &DeriveInput,
trait_name: &str,
) -> Result<proc_macro2::TokenStream, Error> {
let core_crate = ethers_core_crate();
let param_types: Vec<_> = match input.data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => fields
.named
.iter()
.map(|f| {
let ty = &f.ty;
quote_spanned! { f.span() => <#ty as #core_crate::abi::AbiType>::param_type() }
})
.collect(),
Fields::Unnamed(ref fields) => fields
.unnamed
.iter()
.map(|f| {
let ty = &f.ty;
quote_spanned! { f.span() => <#ty as #core_crate::abi::AbiType>::param_type() }
})
.collect(),
Fields::Unit => {
return Err(Error::new(
input.span(),
format!("{} cannot be derived for empty structs and unit", trait_name),
))
}
},
Data::Enum(_) => {
return Err(Error::new(
input.span(),
format!("{} cannot be derived for enums", trait_name),
))
}
Data::Union(_) => {
return Err(Error::new(
input.span(),
format!("{} cannot be derived for unions", trait_name),
))
}
};
Ok(quote! {
[#( #param_types ),*]
})
}

View File

@ -1,6 +1,6 @@
#![cfg(feature = "abigen")] #![cfg(feature = "abigen")]
//! Test cases to validate the `abigen!` macro //! Test cases to validate the `abigen!` macro
use ethers_contract::{abigen, EthEvent}; use ethers_contract::{abigen, EthCall, EthEvent};
use ethers_core::{ use ethers_core::{
abi::{AbiDecode, AbiEncode, Address, Tokenizable}, abi::{AbiDecode, AbiEncode, Address, Tokenizable},
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256}, types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
@ -375,3 +375,30 @@ fn can_handle_duplicates_with_same_name() {
fn can_abigen_console_sol() { fn can_abigen_console_sol() {
abigen!(Console, "ethers-contract/tests/solidity-contracts/console.json",); abigen!(Console, "ethers-contract/tests/solidity-contracts/console.json",);
} }
#[test]
fn can_generate_nested_types() {
abigen!(
Test,
r#"[
struct Outer {Inner inner; uint256[] arr;}
struct Inner {uint256 inner;}
function myfun(Outer calldata a)
]"#,
);
assert_eq!(MyfunCall::abi_signature(), "myfun(((uint256),uint256[]))");
let (client, _mock) = Provider::mocked();
let contract = Test::new(Address::default(), Arc::new(client));
let inner = Inner { inner: 100u64.into() };
let a = Outer { inner, arr: vec![101u64.into()] };
let _ = contract.myfun(a.clone());
let call = MyfunCall { a: a.clone() };
let encoded_call = contract.encode("myfun", (a,)).unwrap();
assert_eq!(encoded_call, call.clone().encode().into());
let decoded_call = MyfunCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);
}

View File

@ -22,7 +22,7 @@ pub use error::{AbiError, ParseError};
mod human_readable; mod human_readable;
pub use human_readable::{parse as parse_abi, parse_str as parse_abi_str, AbiParser}; pub use human_readable::{parse as parse_abi, parse_str as parse_abi_str, AbiParser};
use crate::types::{H256, H512, U128, U256, U64}; use crate::types::{H256, H512, I256, U128, U256, U64};
/// Extension trait for `ethabi::Function`. /// Extension trait for `ethabi::Function`.
pub trait FunctionExt { pub trait FunctionExt {
@ -98,6 +98,8 @@ impl<T: AbiArrayType, const N: usize> AbiType for [T; N] {
} }
} }
impl<T: AbiArrayType, const N: usize> AbiArrayType for [T; N] {}
impl<const N: usize> AbiType for [u8; N] { impl<const N: usize> AbiType for [u8; N] {
fn param_type() -> ParamType { fn param_type() -> ParamType {
ParamType::FixedBytes(N) ParamType::FixedBytes(N)
@ -138,7 +140,8 @@ impl_abi_type!(
i16 => Int(16), i16 => Int(16),
i32 => Int(32), i32 => Int(32),
i64 => Int(64), i64 => Int(64),
i128 => Int(128) i128 => Int(128),
I256 => Int(256)
); );
macro_rules! impl_abi_type_tuple { macro_rules! impl_abi_type_tuple {