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
|
### 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ),*]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue