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:
parent
0f6d3688c2
commit
2a3fcbbb40
|
@ -92,6 +92,8 @@
|
|||
|
||||
### Unreleased
|
||||
|
||||
- Add AbiType implementation during EthAbiType expansion
|
||||
[#647](https://github.com/gakonst/ethers-rs/pull/647)
|
||||
- fix Etherscan conditional HTTP support
|
||||
[#632](https://github.com/gakonst/ethers-rs/pull/632)
|
||||
- use `CARGO_MANIFEST_DIR` as root for relative paths in abigen
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Helper functions for deriving `EthAbiType`
|
||||
|
||||
use crate::utils;
|
||||
use ethers_core::macros::ethers_core_crate;
|
||||
use proc_macro2::{Ident, Literal, TokenStream};
|
||||
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 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,)* } };
|
||||
|
||||
|
@ -58,7 +59,7 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
|
|||
let tokenize_predicates = quote! { #(#tokenize_predicates,)* };
|
||||
|
||||
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,)* ) };
|
||||
|
||||
|
@ -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! {
|
||||
|
||||
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>
|
||||
where
|
||||
#generic_predicates
|
||||
|
|
|
@ -13,11 +13,6 @@ use crate::{abi_ty, utils};
|
|||
|
||||
/// Generates the `ethcall` trait support
|
||||
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) {
|
||||
Ok(attributes) => attributes,
|
||||
Err(errors) => return errors,
|
||||
|
@ -56,27 +51,58 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
|
|||
state_mutability: Default::default(),
|
||||
}
|
||||
} else {
|
||||
return Error::new(span, format!("Unable to determine ABI: {}", src))
|
||||
.to_compile_error()
|
||||
// try to determine the abi by using its fields at runtime
|
||||
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 {
|
||||
// // try to determine the abi from the fields
|
||||
match derive_abi_function_from_fields(&input) {
|
||||
Ok(event) => event,
|
||||
Err(err) => return err.to_compile_error(),
|
||||
// try to determine the abi by using its fields at runtime
|
||||
return match derive_trait_impls_with_abi_type(&input, &function_call_name) {
|
||||
Ok(derived) => derived,
|
||||
Err(err) => err.to_compile_error(),
|
||||
}
|
||||
};
|
||||
|
||||
function.name = function_call_name.clone();
|
||||
|
||||
let abi = function.abi_signature();
|
||||
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! {
|
||||
impl #contract_crate::EthCall for #name {
|
||||
impl #contract_crate::EthCall for #struct_name {
|
||||
|
||||
fn function_name() -> ::std::borrow::Cow<'static, str> {
|
||||
#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> {
|
||||
#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> {
|
||||
#decode_impl
|
||||
}
|
||||
}
|
||||
|
||||
impl #core_crate::abi::AbiEncode for #name {
|
||||
impl #core_crate::abi::AbiEncode for #struct_name {
|
||||
fn encode(self) -> ::std::vec::Vec<u8> {
|
||||
let tokens = #core_crate::abi::Tokenize::into_tokens(self);
|
||||
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! {
|
||||
#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 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 = [#( #data_types ),*];};
|
||||
let data_types_init = quote! {let data_types = #datatypes_array;};
|
||||
|
||||
quote! {
|
||||
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
|
||||
fn derive_abi_function_from_fields(input: &DeriveInput) -> Result<Function, Error> {
|
||||
#[allow(deprecated)]
|
||||
let function = Function {
|
||||
name: "".to_string(),
|
||||
inputs: utils::derive_abi_inputs_from_fields(input, "EthCall")?
|
||||
.into_iter()
|
||||
.map(|(name, kind)| Param { name, kind, internal_type: None })
|
||||
.collect(),
|
||||
outputs: vec![],
|
||||
constant: false,
|
||||
state_mutability: Default::default(),
|
||||
/// Use the `AbiType` trait to determine the correct `ParamType` and signature at runtime
|
||||
fn derive_trait_impls_with_abi_type(
|
||||
input: &DeriveInput,
|
||||
function_call_name: &str,
|
||||
) -> Result<TokenStream, Error> {
|
||||
let abi_signature =
|
||||
utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthCall")?;
|
||||
let abi_signature = quote! {
|
||||
::std::borrow::Cow::Owned(#abi_signature)
|
||||
};
|
||||
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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use ethers_core::{abi::ParamType, macros::ethers_core_crate, types::Selector};
|
||||
use proc_macro2::Literal;
|
||||
use quote::quote;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{
|
||||
parse::Error, spanned::Spanned as _, Data, DeriveInput, Expr, Fields, GenericArgument, Lit,
|
||||
PathArguments, Type,
|
||||
|
@ -187,3 +187,88 @@ pub fn derive_abi_inputs_from_fields(
|
|||
})
|
||||
.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 ),*]
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![cfg(feature = "abigen")]
|
||||
//! Test cases to validate the `abigen!` macro
|
||||
use ethers_contract::{abigen, EthEvent};
|
||||
use ethers_contract::{abigen, EthCall, EthEvent};
|
||||
use ethers_core::{
|
||||
abi::{AbiDecode, AbiEncode, Address, Tokenizable},
|
||||
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
|
||||
|
@ -375,3 +375,30 @@ fn can_handle_duplicates_with_same_name() {
|
|||
fn can_abigen_console_sol() {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ pub use error::{AbiError, ParseError};
|
|||
mod human_readable;
|
||||
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`.
|
||||
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] {
|
||||
fn param_type() -> ParamType {
|
||||
ParamType::FixedBytes(N)
|
||||
|
@ -138,7 +140,8 @@ impl_abi_type!(
|
|||
i16 => Int(16),
|
||||
i32 => Int(32),
|
||||
i64 => Int(64),
|
||||
i128 => Int(128)
|
||||
i128 => Int(128),
|
||||
I256 => Int(256)
|
||||
);
|
||||
|
||||
macro_rules! impl_abi_type_tuple {
|
||||
|
|
Loading…
Reference in New Issue