refactor(contract): derive procedural macros (#2152)

* refactor: use Result<_, Error>

* fix: report both errors during parsing

* refactor: abigen derive results

* update Event derive

* refactor: derive utils

* fmt Display derive

* fmt Codec derive

* refactor: derives

* fix artifacts

* chore: clippy
This commit is contained in:
DaniPopes 2023-02-14 04:54:00 +01:00 committed by GitHub
parent 37acf65dae
commit d8597202ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 315 additions and 351 deletions

1
Cargo.lock generated
View File

@ -1392,6 +1392,7 @@ version = "1.0.2"
dependencies = [
"ethers-contract-abigen",
"ethers-core",
"eyre",
"hex",
"proc-macro2",
"quote",

View File

@ -20,12 +20,14 @@ proc-macro = true
ethers-core = { version = "^1.0.0", path = "../../ethers-core" }
ethers-contract-abigen = { version = "^1.0.0", path = "../ethers-contract-abigen", default-features = false }
serde_json = "1.0.53"
hex = { version = "0.4.3", default-features = false, features = ["std"] }
proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0.12"
serde_json = "1.0.53"
hex = { version = "0.4.3", default-features = false, features = ["std"] }
eyre = "0.6"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

View File

@ -4,42 +4,32 @@ use crate::utils;
use ethers_core::macros::ethers_core_crate;
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{quote, quote_spanned};
use syn::{parse::Error, spanned::Spanned as _, Data, DeriveInput, Fields, Variant};
use syn::{parse::Error, spanned::Spanned, Data, DeriveInput, Fields, Variant};
/// Generates the tokenize implementation
pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
let core_crate = ethers_core_crate();
pub fn derive_tokenizeable_impl(input: &DeriveInput) -> Result<TokenStream, Error> {
let ethers_core = ethers_core_crate();
let name = &input.ident;
let generic_params = input.generics.params.iter().map(|p| quote! { #p });
let generic_params = quote! { #(#generic_params,)* };
let generic_args = input.generics.type_params().map(|p| {
let name = &p.ident;
quote_spanned! { p.ident.span() => #name }
});
let generic_args = quote! { #(#generic_args,)* };
let generic_predicates = match input.generics.where_clause {
Some(ref clause) => {
let predicates = clause.predicates.iter().map(|p| quote! { #p });
quote! { #(#predicates,)* }
}
None => quote! {},
};
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let generic_predicates = where_clause.map(|c| &c.predicates);
let (tokenize_predicates, params_len, init_struct_impl, into_token_impl) = match input.data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => {
let tokenize_predicates = fields.named.iter().map(|f| {
let ty = &f.ty;
quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize }
quote_spanned! { f.span() => #ty: #ethers_core::abi::Tokenize }
});
let tokenize_predicates = quote! { #(#tokenize_predicates,)* };
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("The iter is guaranteed to be something due to the size check"))? }
quote_spanned! { f.span() =>
#name: #ethers_core::abi::Tokenizable::from_token(
iter.next().expect("The iter is guaranteed to be something due to the size check")
)?
}
});
let init_struct_impl = quote! { Self { #(#assignments,)* } };
@ -54,12 +44,16 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
Fields::Unnamed(ref fields) => {
let tokenize_predicates = fields.unnamed.iter().map(|f| {
let ty = &f.ty;
quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize }
quote_spanned! { f.span() => #ty: #ethers_core::abi::Tokenize }
});
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("The iter is guaranteed to be something due to the size check"))? }
quote_spanned! { f.span() =>
#ethers_core::abi::Tokenizable::from_token(
iter.next().expect("The iter is guaranteed to be something due to the size check")
)?
}
});
let init_struct_impl = quote! { Self(#(#assignments,)* ) };
@ -71,17 +65,11 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
(tokenize_predicates, fields.unnamed.len(), init_struct_impl, into_token_impl)
}
Fields::Unit => return tokenize_unit_type(&input.ident),
Fields::Unit => return Ok(tokenize_unit_type(&input.ident)),
},
Data::Enum(ref data) => {
return match tokenize_enum(name, data.variants.iter()) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
}
Data::Enum(ref data) => return tokenize_enum(name, data.variants.iter()),
Data::Union(_) => {
return Error::new(input.span(), "EthAbiType cannot be derived for unions")
.to_compile_error()
return Err(Error::new(input.span(), "EthAbiType cannot be derived for unions"))
}
};
@ -95,14 +83,14 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
// can't encode an empty struct
// TODO: panic instead?
quote! {
#core_crate::abi::Token::Tuple(Vec::new())
#ethers_core::abi::Token::Tuple(Vec::new())
},
),
_ => {
let from_token = quote! {
if let #core_crate::abi::Token::Tuple(tokens) = token {
if let #ethers_core::abi::Token::Tuple(tokens) = token {
if tokens.len() != #params_len {
return Err(#core_crate::abi::InvalidOutputType(::std::format!(
return Err(#ethers_core::abi::InvalidOutputType(::std::format!(
"Expected {} tokens, got {}: {:?}",
#params_len,
tokens.len(),
@ -114,7 +102,7 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
Ok(#init_struct_impl)
} else {
Err(#core_crate::abi::InvalidOutputType(::std::format!(
Err(#ethers_core::abi::InvalidOutputType(::std::format!(
"Expected Tuple, got {:?}",
token
)))
@ -122,7 +110,7 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream
};
let into_token = quote! {
#core_crate::abi::Token::Tuple(
#ethers_core::abi::Token::Tuple(
::std::vec![
#into_token_impl
]
@ -132,50 +120,51 @@ 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! {
let params = utils::derive_param_type_with_abi_type(input, "EthAbiType")?;
impl<#generic_params> #core_crate::abi::AbiType for #name<#generic_args> {
fn param_type() -> #core_crate::abi::ParamType {
Ok(quote! {
impl #impl_generics #ethers_core::abi::AbiType for #name #ty_generics #where_clause {
fn param_type() -> #ethers_core::abi::ParamType {
#params
}
}
impl<#generic_params> #core_crate::abi::AbiArrayType for #name<#generic_args> {}
impl #impl_generics #ethers_core::abi::AbiArrayType for #name #ty_generics #where_clause {}
impl<#generic_params> #core_crate::abi::Tokenizable for #name<#generic_args>
impl #impl_generics #ethers_core::abi::Tokenizable for #name #ty_generics
where
#generic_predicates
#tokenize_predicates
{
fn from_token(token: #core_crate::abi::Token) -> ::std::result::Result<Self, #core_crate::abi::InvalidOutputType> where
Self: Sized {
fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result<Self, #ethers_core::abi::InvalidOutputType>
where
Self: Sized,
{
#from_token_impl
}
fn into_token(self) -> #core_crate::abi::Token {
fn into_token(self) -> #ethers_core::abi::Token {
#into_token_impl
}
}
impl<#generic_params> #core_crate::abi::TokenizableItem for #name<#generic_args>
impl #impl_generics #ethers_core::abi::TokenizableItem for #name #ty_generics
where
#generic_predicates
#tokenize_predicates
{ }
}
{}
})
}
fn tokenize_unit_type(name: &Ident) -> TokenStream {
let ethers_core = ethers_core_crate();
quote! {
impl #ethers_core::abi::Tokenizable for #name {
fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result<Self, #ethers_core::abi::InvalidOutputType> where
Self: Sized {
fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result<Self, #ethers_core::abi::InvalidOutputType>
where
Self: Sized,
{
if let #ethers_core::abi::Token::Tuple(tokens) = token {
if !tokens.is_empty() {
Err(#ethers_core::abi::InvalidOutputType(::std::format!(
@ -197,7 +186,8 @@ fn tokenize_unit_type(name: &Ident) -> TokenStream {
#ethers_core::abi::Token::Tuple(::std::vec::Vec::new())
}
}
impl #ethers_core::abi::TokenizableItem for #name { }
impl #ethers_core::abi::TokenizableItem for #name {}
}
}
@ -210,7 +200,7 @@ fn tokenize_unit_type(name: &Ident) -> TokenStream {
fn tokenize_enum<'a>(
enum_name: &Ident,
variants: impl Iterator<Item = &'a Variant> + 'a,
) -> ::std::result::Result<TokenStream, Error> {
) -> Result<TokenStream, Error> {
let ethers_core = ethers_core_crate();
let mut into_tokens = TokenStream::new();
@ -251,9 +241,10 @@ fn tokenize_enum<'a>(
Ok(quote! {
impl #ethers_core::abi::Tokenizable for #enum_name {
fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result<Self, #ethers_core::abi::InvalidOutputType> where
Self: Sized {
fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result<Self, #ethers_core::abi::InvalidOutputType>
where
Self: Sized,
{
#from_tokens
Err(#ethers_core::abi::InvalidOutputType("Failed to decode all type variants".to_string()))
}
@ -264,6 +255,7 @@ fn tokenize_enum<'a>(
}
}
}
impl #ethers_core::abi::TokenizableItem for #enum_name { }
impl #ethers_core::abi::TokenizableItem for #enum_name {}
})
}

View File

@ -8,14 +8,15 @@ use ethers_contract_abigen::{
Abigen,
};
use ethers_core::abi::{Function, FunctionExt, Param, StateMutability};
use eyre::Result;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::ToTokens;
use std::{collections::HashSet, error::Error};
use std::collections::HashSet;
use syn::{
braced,
ext::IdentExt,
parenthesized,
parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult},
parse::{Error, Parse, ParseStream, Result as ParseResult},
Ident, LitStr, Path, Token,
};
@ -26,13 +27,13 @@ pub(crate) struct Contracts {
}
impl Contracts {
pub(crate) fn expand(self) -> ::std::result::Result<TokenStream2, syn::Error> {
pub(crate) fn expand(self) -> Result<TokenStream2, Error> {
let mut expansions = Vec::with_capacity(self.inner.len());
// expand all contracts
for (span, contract) in self.inner {
let contract = Self::expand_contract(contract)
.map_err(|err| syn::Error::new(span, err.to_string()))?;
let contract =
Self::expand_contract(contract).map_err(|err| Error::new(span, err.to_string()))?;
expansions.push(contract);
}
@ -40,10 +41,8 @@ impl Contracts {
Ok(MultiExpansion::new(expansions).expand_inplace())
}
fn expand_contract(
contract: ContractArgs,
) -> Result<(ExpandedContract, Context), Box<dyn Error>> {
Ok(contract.into_builder()?.expand()?)
fn expand_contract(contract: ContractArgs) -> Result<(ExpandedContract, Context)> {
contract.into_builder()?.expand()
}
}
@ -66,7 +65,7 @@ pub(crate) struct ContractArgs {
}
impl ContractArgs {
fn into_builder(self) -> Result<Abigen, Box<dyn Error>> {
fn into_builder(self) -> Result<Abigen> {
let mut builder = Abigen::new(&self.name, &self.abi)?;
for parameter in self.parameters.into_iter() {
@ -151,13 +150,13 @@ impl Parse for Parameter {
let mut aliases = HashSet::new();
for method in parsed {
if !signatures.insert(method.signature.clone()) {
return Err(ParseError::new(
return Err(Error::new(
method.span(),
"duplicate method signature in `abigen!` macro invocation",
))
}
if !aliases.insert(method.alias.clone()) {
return Err(ParseError::new(
return Err(Error::new(
method.span(),
"duplicate method alias in `abigen!` macro invocation",
))
@ -181,10 +180,7 @@ impl Parse for Parameter {
Parameter::Derives(derives)
}
_ => {
return Err(ParseError::new(
name.span(),
format!("unexpected named parameter `{name}`"),
))
return Err(Error::new(name.span(), format!("unexpected named parameter `{name}`")))
}
};
@ -211,7 +207,7 @@ impl Parse for Method {
.iter()
.map(|ident| {
let kind = serde_json::from_value(serde_json::json!(&ident.to_string()))
.map_err(|err| ParseError::new(ident.span(), err))?;
.map_err(|err| Error::new(ident.span(), err))?;
Ok(Param { name: "".into(), kind, internal_type: None })
})
.collect::<ParseResult<Vec<_>>>()?;

View File

@ -10,50 +10,40 @@ use quote::quote;
use syn::{parse::Error, DeriveInput};
/// Generates the `ethcall` trait support
pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
let attributes = match parse_calllike_attributes(&input, "ethcall") {
Ok(attributes) => attributes,
Err(errors) => return errors,
};
pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> Result<TokenStream, Error> {
let attributes = parse_calllike_attributes(&input, "ethcall")?;
let function_call_name =
attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
let mut function = if let Some((src, span)) = attributes.abi {
let raw_function_sig = src.trim_start_matches("function ").trim_start();
let mut function = if let Some((abi, span)) = attributes.abi {
let sig = abi.trim_start_matches("function ").trim_start();
// try to parse as solidity function
if let Ok(fun) = HumanReadableParser::parse_function(&src) {
fun
} else {
// try to determine the abi by using its fields at runtime
return match derive_trait_impls_with_abi_type(
&input,
&function_call_name,
Some(raw_function_sig),
) {
Ok(derived) => derived,
Err(err) => {
Error::new(span, format!("Unable to determine ABI for `{src}` : {err}"))
.to_compile_error()
}
match HumanReadableParser::parse_function(&abi) {
Ok(fun) => fun,
Err(parse_err) => {
return derive_trait_impls_with_abi_type(&input, &function_call_name, Some(sig))
.map_err(|e| {
let mut error = Error::new(span, parse_err);
error.combine(Error::new(span, e));
error
})
}
}
} else {
// try to determine the abi by using its fields at runtime
return match derive_trait_impls_with_abi_type(&input, &function_call_name, None) {
Ok(derived) => derived,
Err(err) => err.to_compile_error(),
}
return derive_trait_impls_with_abi_type(&input, &function_call_name, None)
};
function.name = function_call_name.clone();
let abi = function.abi_signature();
let sig = function.abi_signature();
let selector = utils::selector(function.selector());
let decode_impl = derive_decode_impl_from_params(&function.inputs, ident("EthCall"));
derive_trait_impls(
&input,
&function_call_name,
quote! {#abi.into()},
quote!(#sig.into()),
Some(selector),
decode_impl,
)
@ -65,17 +55,14 @@ fn derive_trait_impls_with_abi_type(
function_call_name: &str,
abi_signature: Option<&str>,
) -> Result<TokenStream, Error> {
let abi_signature = if let Some(abi) = abi_signature {
quote! {#abi}
let mut abi_signature = if let Some(sig) = abi_signature {
quote!(#sig)
} else {
utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthCall")?
};
let abi_signature = quote! {
#abi_signature.into()
utils::abi_signature_with_abi_type(input, function_call_name, "EthCall")?
};
abi_signature.extend(quote!(.into()));
let decode_impl = derive_decode_impl_with_abi_type(input, ident("EthCall"))?;
Ok(derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl))
derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl)
}
/// Generates the EthCall implementation
@ -85,26 +72,25 @@ pub fn derive_trait_impls(
abi_signature: TokenStream,
selector: Option<TokenStream>,
decode_impl: TokenStream,
) -> TokenStream {
) -> Result<TokenStream, Error> {
// the ethers crates to use
let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate();
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let struct_name = &input.ident;
let selector = selector.unwrap_or_else(|| {
quote! {
#core_crate::utils::id(Self::abi_signature())
#ethers_core::utils::id(Self::abi_signature())
}
});
let ethcall_impl = quote! {
impl #contract_crate::EthCall for #struct_name {
impl #ethers_contract::EthCall for #struct_name {
fn function_name() -> ::std::borrow::Cow<'static, str> {
#function_call_name.into()
}
fn selector() -> #core_crate::types::Selector {
fn selector() -> #ethers_core::types::Selector {
#selector
}
@ -113,10 +99,10 @@ pub fn derive_trait_impls(
}
}
};
let codec_impl = derive_codec_impls(input, decode_impl, ident("EthCall"));
let codec_impl = derive_codec_impls(input, decode_impl, ident("EthCall"))?;
quote! {
Ok(quote! {
#ethcall_impl
#codec_impl
}
})
}

View File

@ -7,7 +7,7 @@ use ethers_core::{
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{parse::Error, spanned::Spanned as _, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
use syn::{parse::Error, spanned::Spanned, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
/// All the attributes the `EthCall`/`EthError` macro supports
#[derive(Default)]
@ -20,7 +20,7 @@ pub struct EthCalllikeAttributes {
pub fn parse_calllike_attributes(
input: &DeriveInput,
attr_name: &str,
) -> Result<EthCalllikeAttributes, TokenStream> {
) -> Result<EthCalllikeAttributes, Error> {
let mut result = EthCalllikeAttributes::default();
for a in input.attrs.iter() {
if let AttrStyle::Outer = a.style {
@ -33,15 +33,13 @@ pub fn parse_calllike_attributes(
return Err(Error::new(
path.span(),
format!("unrecognized {attr_name} parameter"),
)
.to_compile_error())
))
}
Meta::List(meta) => {
return Err(Error::new(
meta.path.span(),
format!("unrecognized {attr_name} parameter"),
)
.to_compile_error())
))
}
Meta::NameValue(meta) => {
if meta.path.is_ident("name") {
@ -53,15 +51,13 @@ pub fn parse_calllike_attributes(
return Err(Error::new(
meta.span(),
"name already specified",
)
.to_compile_error())
))
}
} else {
return Err(Error::new(
meta.span(),
"name must be a string",
)
.to_compile_error())
))
}
} else if meta.path.is_ident("abi") {
if let Lit::Str(ref lit_str) = meta.lit {
@ -72,22 +68,19 @@ pub fn parse_calllike_attributes(
return Err(Error::new(
meta.span(),
"abi already specified",
)
.to_compile_error())
))
}
} else {
return Err(Error::new(
meta.span(),
"abi must be a string",
)
.to_compile_error())
))
}
} else {
return Err(Error::new(
meta.span(),
format!("unrecognized {attr_name} parameter"),
)
.to_compile_error())
))
}
}
}
@ -105,7 +98,7 @@ pub fn derive_decode_impl_with_abi_type(
input: &DeriveInput,
trait_ident: Ident,
) -> Result<TokenStream, Error> {
let datatypes_array = utils::derive_abi_parameters_array(input, &trait_ident.to_string())?;
let datatypes_array = utils::abi_parameters_array(input, &trait_ident.to_string())?;
Ok(derive_decode_impl(datatypes_array, trait_ident))
}
@ -117,18 +110,18 @@ pub fn derive_decode_impl_from_params(params: &[Param], trait_ident: Ident) -> T
}
pub fn derive_decode_impl(datatypes_array: TokenStream, trait_ident: Ident) -> TokenStream {
let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate();
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let data_types_init = quote! {let data_types = #datatypes_array;};
quote! {
let bytes = bytes.as_ref();
if bytes.len() < 4 || bytes[..4] != <Self as #contract_crate::#trait_ident>::selector() {
return Err(#contract_crate::AbiError::WrongSelector);
if bytes.len() < 4 || bytes[..4] != <Self as #ethers_contract::#trait_ident>::selector() {
return Err(#ethers_contract::AbiError::WrongSelector);
}
#data_types_init
let data_tokens = #core_crate::abi::decode(&data_types, &bytes[4..])?;
Ok(<Self as #core_crate::abi::Tokenizable>::from_token( #core_crate::abi::Token::Tuple(data_tokens))?)
let data_tokens = #ethers_core::abi::decode(&data_types, &bytes[4..])?;
Ok(<Self as #ethers_core::abi::Tokenizable>::from_token(#ethers_core::abi::Token::Tuple(data_tokens))?)
}
}
@ -137,25 +130,24 @@ pub fn derive_codec_impls(
input: &DeriveInput,
decode_impl: TokenStream,
trait_ident: Ident,
) -> TokenStream {
) -> Result<TokenStream, Error> {
// the ethers crates to use
let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate();
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let struct_name = &input.ident;
let codec_impl = quote! {
impl #core_crate::abi::AbiDecode for #struct_name {
fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #core_crate::abi::AbiError> {
impl #ethers_core::abi::AbiDecode for #struct_name {
fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #ethers_core::abi::AbiError> {
#decode_impl
}
}
impl #core_crate::abi::AbiEncode for #struct_name {
impl #ethers_core::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::#trait_ident>::selector();
let encoded = #core_crate::abi::encode(&tokens);
let tokens = #ethers_core::abi::Tokenize::into_tokens(self);
let selector = <Self as #ethers_contract::#trait_ident>::selector();
let encoded = #ethers_core::abi::encode(&tokens);
selector
.iter()
.copied()
@ -165,10 +157,10 @@ pub fn derive_codec_impls(
}
};
let tokenize_impl = abi_ty::derive_tokenizeable_impl(input);
let tokenize_impl = abi_ty::derive_tokenizeable_impl(input)?;
quote! {
Ok(quote! {
#tokenize_impl
#codec_impl
}
})
}

View File

@ -1,32 +1,32 @@
//! Helper functions for deriving `EthAbiType`
use ethers_core::macros::ethers_core_crate;
use quote::quote;
use syn::DeriveInput;
/// Generates the `AbiEncode` + `AbiDecode` implementation
pub fn derive_codec_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
let name = &input.ident;
let core_crate = ethers_core_crate();
let ethers_core = ethers_core_crate();
quote! {
impl #core_crate::abi::AbiDecode for #name {
fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #core_crate::abi::AbiError> {
if let #core_crate::abi::ParamType::Tuple(params) = <Self as #core_crate::abi::AbiType>::param_type() {
let tokens = #core_crate::abi::decode(&params, bytes.as_ref())?;
Ok(<Self as #core_crate::abi::Tokenizable>::from_token(#core_crate::abi::Token::Tuple(tokens))?)
impl #ethers_core::abi::AbiDecode for #name {
fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #ethers_core::abi::AbiError> {
if let #ethers_core::abi::ParamType::Tuple(params) = <Self as #ethers_core::abi::AbiType>::param_type() {
let tokens = #ethers_core::abi::decode(&params, bytes.as_ref())?;
Ok(<Self as #ethers_core::abi::Tokenizable>::from_token(#ethers_core::abi::Token::Tuple(tokens))?)
} else {
Err(
#core_crate::abi::InvalidOutputType("Expected tuple".to_string()).into()
#ethers_core::abi::InvalidOutputType("Expected tuple".to_string()).into()
)
}
}
}
impl #core_crate::abi::AbiEncode for #name {
impl #ethers_core::abi::AbiEncode for #name {
fn encode(self) -> ::std::vec::Vec<u8> {
let tokens = #core_crate::abi::Tokenize::into_tokens(self);
#core_crate::abi::encode(&tokens)
let tokens = #ethers_core::abi::Tokenize::into_tokens(self);
#ethers_core::abi::encode(&tokens)
}
}
}

View File

@ -1,22 +1,18 @@
//! Helper functions for deriving `Display`
use crate::utils;
use ethers_core::{abi::ParamType, macros::ethers_core_crate};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse::Error, spanned::Spanned as _, Data, DeriveInput, Fields, Index};
use ethers_core::{abi::ParamType, macros::ethers_core_crate};
use crate::utils;
use syn::{parse::Error, spanned::Spanned, Data, DeriveInput, Fields, Index};
/// Derive `fmt::Display` for the given type
pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result<TokenStream, Error> {
let fields: Vec<_> = match input.data {
let fields = match input.data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => fields.named.iter().collect(),
Fields::Unnamed(ref fields) => fields.unnamed.iter().collect(),
Fields::Unit => {
vec![]
}
Fields::Unit => vec![],
},
Data::Enum(_) => {
return Err(Error::new(input.span(), "Enum types are not supported by EthDisplay"))
@ -25,8 +21,10 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result<TokenStream,
return Err(Error::new(input.span(), "Union types are not supported by EthDisplay"))
}
};
let core_crate = ethers_core_crate();
let hex_encode = quote! {#core_crate::utils::hex::encode};
let ethers_core = ethers_core_crate();
let hex_encode = quote! {#ethers_core::utils::hex::encode};
let mut fmts = TokenStream::new();
for (idx, field) in fields.iter().enumerate() {
let ident = field.ident.clone().map(|id| quote! {#id}).unwrap_or_else(|| {
@ -92,6 +90,7 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result<TokenStream,
fmts.extend(quote! { write!(f, ", ")?;});
}
}
let name = &input.ident;
Ok(quote! {
impl ::std::fmt::Display for #name {

View File

@ -10,46 +10,45 @@ use quote::quote;
use syn::{parse::Error, DeriveInput};
/// Generates the `EthError` trait support
pub(crate) fn derive_eth_error_impl(input: DeriveInput) -> TokenStream {
let attributes = match parse_calllike_attributes(&input, "etherror") {
Ok(attributes) => attributes,
Err(errors) => return errors,
};
pub(crate) fn derive_eth_error_impl(input: DeriveInput) -> Result<TokenStream, Error> {
let attributes = parse_calllike_attributes(&input, "etherror")?;
let error_name = attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
let mut error = if let Some((src, span)) = attributes.abi {
let raw_function_sig = src.trim_start_matches("error ").trim_start();
// try to parse as solidity error
if let Ok(fun) = HumanReadableParser::parse_error(&src) {
fun
} else {
// try to determine the abi by using its fields at runtime
match HumanReadableParser::parse_error(&src) {
Ok(solidity_error) => solidity_error,
Err(parse_err) => {
return match derive_trait_impls_with_abi_type(
&input,
&error_name,
Some(raw_function_sig),
) {
Ok(derived) => derived,
Ok(derived) => Ok(derived),
Err(err) => {
Error::new(span, format!("Unable to determine ABI for `{src}` : {err}"))
.to_compile_error()
Err(Error::new(span, format!("Unable to determine ABI for `{src}`: {err}")))
}
.map_err(|e| {
let mut error = Error::new(span, parse_err);
error.combine(Error::new(span, e));
error
}),
}
}
}
} else {
// try to determine the abi by using its fields at runtime
return match derive_trait_impls_with_abi_type(&input, &error_name, None) {
Ok(derived) => derived,
Err(err) => err.to_compile_error(),
}
return derive_trait_impls_with_abi_type(&input, &error_name, None)
};
error.name = error_name.clone();
let abi = error.abi_signature();
let sig = error.abi_signature();
let selector = utils::selector(error.selector());
let decode_impl = derive_decode_impl_from_params(&error.inputs, ident("EthError"));
derive_trait_impls(&input, &error_name, quote! {#abi.into()}, Some(selector), decode_impl)
derive_trait_impls(&input, &error_name, quote!(#sig.into()), Some(selector), decode_impl)
}
/// Use the `AbiType` trait to determine the correct `ParamType` and signature at runtime
@ -58,17 +57,14 @@ fn derive_trait_impls_with_abi_type(
function_call_name: &str,
abi_signature: Option<&str>,
) -> Result<TokenStream, Error> {
let abi_signature = if let Some(abi) = abi_signature {
quote! {#abi}
let mut abi_signature = if let Some(sig) = abi_signature {
quote!(#sig)
} else {
utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthError")?
};
let abi_signature = quote! {
#abi_signature.into()
utils::abi_signature_with_abi_type(input, function_call_name, "EthError")?
};
abi_signature.extend(quote!(.into()));
let decode_impl = derive_decode_impl_with_abi_type(input, ident("EthError"))?;
Ok(derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl))
derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl)
}
/// Generates the EthError implementation
@ -78,26 +74,25 @@ pub fn derive_trait_impls(
abi_signature: TokenStream,
selector: Option<TokenStream>,
decode_impl: TokenStream,
) -> TokenStream {
) -> Result<TokenStream, Error> {
// the ethers crates to use
let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate();
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let struct_name = &input.ident;
let selector = selector.unwrap_or_else(|| {
quote! {
#core_crate::utils::id(Self::abi_signature())
#ethers_core::utils::id(Self::abi_signature())
}
});
let etherror_impl = quote! {
impl #contract_crate::EthError for #struct_name {
impl #ethers_contract::EthError for #struct_name {
fn error_name() -> ::std::borrow::Cow<'static, str> {
#function_call_name.into()
}
fn selector() -> #core_crate::types::Selector {
fn selector() -> #ethers_core::types::Selector {
#selector
}
@ -105,12 +100,11 @@ pub fn derive_trait_impls(
#abi_signature
}
}
};
let codec_impl = derive_codec_impls(input, decode_impl, ident("EthError"));
let codec_impl = derive_codec_impls(input, decode_impl, ident("EthError"))?;
quote! {
Ok(quote! {
#etherror_impl
#codec_impl
}
})
}

View File

@ -4,7 +4,7 @@ use ethers_contract_abigen::Source;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
parse::Error, spanned::Spanned as _, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta,
parse::Error, spanned::Spanned, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta,
NestedMeta,
};
@ -42,8 +42,9 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, E
}
Err(source_err) => {
// Return both error messages
let message = format!("Failed parsing ABI: {parse_err} ({source_err})");
Err(Error::new(span, message))
let mut error = Error::new(span, parse_err);
error.combine(Error::new(span, source_err));
Err(error)
}
}
}
@ -102,7 +103,7 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, E
}
};
let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input);
let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input)?;
Ok(quote! {
#tokenize_impl

View File

@ -112,7 +112,11 @@ pub fn abigen(input: TokenStream) -> TokenStream {
#[proc_macro_derive(EthAbiType)]
pub fn derive_abi_type(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(abi_ty::derive_tokenizeable_impl(&input))
match abi_ty::derive_tokenizeable_impl(&input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
.into()
}
/// Derives the `AbiEncode`, `AbiDecode` and traits for the labeled type.
@ -174,9 +178,10 @@ pub fn derive_abi_codec(input: TokenStream) -> TokenStream {
pub fn derive_eth_display(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match display::derive_eth_display_impl(input) {
Ok(tokens) => TokenStream::from(tokens),
Err(err) => err.to_compile_error().into(),
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
.into()
}
/// Derives the `EthEvent` and `Tokenizeable` trait for the labeled type.
@ -225,9 +230,10 @@ pub fn derive_eth_display(input: TokenStream) -> TokenStream {
pub fn derive_abi_event(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match event::derive_eth_event_impl(input) {
Ok(tokens) => TokenStream::from(tokens),
Err(err) => err.to_compile_error().into(),
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
.into()
}
/// Derives the `EthCall` and `Tokenizeable` trait for the labeled type.
@ -291,7 +297,11 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
#[proc_macro_derive(EthCall, attributes(ethcall))]
pub fn derive_abi_call(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(call::derive_eth_call_impl(input))
match call::derive_eth_call_impl(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
.into()
}
/// Derives the `EthError` and `Tokenizeable` trait for the labeled type.
@ -328,5 +338,9 @@ pub fn derive_abi_call(input: TokenStream) -> TokenStream {
#[proc_macro_derive(EthError, attributes(etherror))]
pub fn derive_abi_error(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(error::derive_eth_error_impl(input))
match error::derive_eth_error_impl(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
.into()
}

View File

@ -1,8 +1,8 @@
use ethers_core::{abi::ParamType, macros::ethers_core_crate, types::Selector};
use proc_macro2::{Ident, Literal, Span};
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::{
parse::Error, spanned::Spanned as _, Data, DeriveInput, Expr, Fields, GenericArgument, Lit,
parse::Error, spanned::Spanned, Data, DeriveInput, Expr, Fields, GenericArgument, Lit,
PathArguments, Type,
};
@ -10,26 +10,29 @@ pub fn ident(name: &str) -> Ident {
Ident::new(name, Span::call_site())
}
pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
let core_crate = ethers_core_crate();
pub fn signature(hash: &[u8]) -> TokenStream {
let ethers_core = ethers_core_crate();
let bytes = hash.iter().copied().map(Literal::u8_unsuffixed);
quote! {#core_crate::types::H256([#( #bytes ),*])}
quote! {#ethers_core::types::H256([#( #bytes ),*])}
}
pub fn selector(selector: Selector) -> proc_macro2::TokenStream {
pub fn selector(selector: Selector) -> TokenStream {
let bytes = selector.iter().copied().map(Literal::u8_unsuffixed);
quote! {[#( #bytes ),*]}
}
/// Parses an int type from its string representation
pub fn parse_int_param_type(s: &str) -> Option<ParamType> {
let size = s.chars().skip(1).collect::<String>().parse::<usize>().ok()?;
if s.starts_with('u') {
match s.chars().next() {
Some(c @ 'u') | Some(c @ 'i') => {
let size = s[1..].parse::<usize>().ok()?;
if c == 'u' {
Some(ParamType::Uint(size))
} else if s.starts_with('i') {
Some(ParamType::Int(size))
} else {
None
Some(ParamType::Int(size))
}
}
_ => None,
}
}
@ -37,64 +40,58 @@ pub fn parse_int_param_type(s: &str) -> Option<ParamType> {
// This applies to strings, arrays, structs and bytes to follow the encoding of
// these indexed param types according to
// <https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters>
pub fn topic_param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
let core_crate = ethers_core_crate();
pub fn topic_param_type_quote(kind: &ParamType) -> TokenStream {
let ethers_core = ethers_core_crate();
match kind {
ParamType::String |
ParamType::Bytes |
ParamType::Array(_) |
ParamType::FixedArray(_, _) |
ParamType::Tuple(_) => quote! {#core_crate::abi::ParamType::FixedBytes(32)},
ParamType::Tuple(_) => quote! {#ethers_core::abi::ParamType::FixedBytes(32)},
ty => param_type_quote(ty),
}
}
/// Returns the rust type for the given parameter
pub fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
let core_crate = ethers_core_crate();
pub fn param_type_quote(kind: &ParamType) -> TokenStream {
let ethers_core = ethers_core_crate();
match kind {
ParamType::Address => {
quote! {#core_crate::abi::ParamType::Address}
quote! {#ethers_core::abi::ParamType::Address}
}
ParamType::Bytes => {
quote! {#core_crate::abi::ParamType::Bytes}
quote! {#ethers_core::abi::ParamType::Bytes}
}
ParamType::Int(size) => {
let size = Literal::usize_suffixed(*size);
quote! {#core_crate::abi::ParamType::Int(#size)}
quote! {#ethers_core::abi::ParamType::Int(#size)}
}
ParamType::Uint(size) => {
let size = Literal::usize_suffixed(*size);
quote! {#core_crate::abi::ParamType::Uint(#size)}
quote! {#ethers_core::abi::ParamType::Uint(#size)}
}
ParamType::Bool => {
quote! {#core_crate::abi::ParamType::Bool}
quote! {#ethers_core::abi::ParamType::Bool}
}
ParamType::String => {
quote! {#core_crate::abi::ParamType::String}
quote! {#ethers_core::abi::ParamType::String}
}
ParamType::Array(ty) => {
let ty = param_type_quote(ty);
quote! {#core_crate::abi::ParamType::Array(Box::new(#ty))}
quote! {#ethers_core::abi::ParamType::Array(Box::new(#ty))}
}
ParamType::FixedBytes(size) => {
let size = Literal::usize_suffixed(*size);
quote! {#core_crate::abi::ParamType::FixedBytes(#size)}
quote! {#ethers_core::abi::ParamType::FixedBytes(#size)}
}
ParamType::FixedArray(ty, size) => {
let ty = param_type_quote(ty);
let size = Literal::usize_suffixed(*size);
quote! {#core_crate::abi::ParamType::FixedArray(Box::new(#ty),#size)}
quote! {#ethers_core::abi::ParamType::FixedArray(Box::new(#ty), #size)}
}
ParamType::Tuple(tuple) => {
let elements = tuple.iter().map(param_type_quote);
quote! {
#core_crate::abi::ParamType::Tuple(
::std::vec![
#( #elements ),*
]
)
}
quote!(#ethers_core::abi::ParamType::Tuple(::std::vec![#( #elements ),*]))
}
}
}
@ -120,8 +117,8 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
if let PathArguments::AngleBracketed(ref args) = ty.path.segments[0].arguments {
if args.args.len() == 1 {
if let GenericArgument::Type(ref ty) = args.args.iter().next().unwrap() {
let kind = find_parameter_type(ty)?;
return Ok(ParamType::Array(Box::new(kind)))
return find_parameter_type(ty)
.map(|kind| ParamType::Array(Box::new(kind)))
}
}
}
@ -148,10 +145,12 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
}
Err(Error::new(ty.span(), "Failed to derive proper ABI from fields"))
}
Type::Tuple(ty) => {
let params = ty.elems.iter().map(find_parameter_type).collect::<Result<Vec<_>, _>>()?;
Ok(ParamType::Tuple(params))
}
Type::Tuple(ty) => ty
.elems
.iter()
.map(find_parameter_type)
.collect::<Result<Vec<_>, _>>()
.map(ParamType::Tuple),
_ => Err(Error::new(ty.span(), "Failed to derive proper ABI from fields")),
}
}
@ -200,22 +199,22 @@ pub fn derive_abi_inputs_from_fields(
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)?;
) -> Result<TokenStream, Error> {
let ethers_core = ethers_core_crate();
let params = abi_parameters_array(input, trait_name)?;
Ok(quote! {
#core_crate::abi::ParamType::Tuple(::std::vec!#params)
#ethers_core::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(
/// `String`.
pub fn 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)?;
) -> Result<TokenStream, Error> {
let params = abi_parameters_array(input, trait_name)?;
Ok(quote! {
{
let params: String = #params
@ -231,30 +230,13 @@ pub fn derive_abi_signature_with_abi_type(
/// 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();
pub fn abi_parameters_array(input: &DeriveInput, trait_name: &str) -> Result<TokenStream, Error> {
let ethers_core = ethers_core_crate();
let param_types: Vec<_> = match input.data {
let fields = 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::Named(ref fields) => &fields.named,
Fields::Unnamed(ref fields) => &fields.unnamed,
Fields::Unit => {
return Err(Error::new(
input.span(),
@ -276,7 +258,12 @@ pub fn derive_abi_parameters_array(
}
};
let iter = fields.iter().map(|f| {
let ty = &f.ty;
quote_spanned!(f.span() => <#ty as #ethers_core::abi::AbiType>::param_type())
});
Ok(quote! {
[#( #param_types ),*]
[#( #iter ),*]
})
}