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

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

Merged
merged 9 commits into from
Dec 4, 2021
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 16 additions & 2 deletions ethers-contract/ethers-contract-derive/src/abi_ty.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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,)* } };

Expand All @@ -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,)* ) };

Expand Down Expand Up @@ -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
Expand Down
107 changes: 71 additions & 36 deletions ethers-contract/ethers-contract-derive/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);

derive_trait_impls(
&input,
&function_call_name,
quote! {#abi.into()},
Some(selector),
decode_impl,
)
}

let decode_impl = derive_decode_impl(&function);
/// 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()
Expand All @@ -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();
Expand All @@ -111,20 +137,31 @@ 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
#ethcall_impl
}
}

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();
Expand All @@ -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
Expand Down
87 changes: 86 additions & 1 deletion ethers-contract/ethers-contract-derive/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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 ),*]
})
}
29 changes: 28 additions & 1 deletion ethers-contract/tests/abigen.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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);
}
7 changes: 5 additions & 2 deletions ethers-core/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down