diff --git a/Cargo.lock b/Cargo.lock index 8259ceb7..fb1b6977 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1392,6 +1392,7 @@ version = "1.0.2" dependencies = [ "ethers-contract-abigen", "ethers-core", + "eyre", "hex", "proc-macro2", "quote", diff --git a/ethers-contract/ethers-contract-derive/Cargo.toml b/ethers-contract/ethers-contract-derive/Cargo.toml index 0191d2f5..90ca8620 100644 --- a/ethers-contract/ethers-contract-derive/Cargo.toml +++ b/ethers-contract/ethers-contract-derive/Cargo.toml @@ -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"] diff --git a/ethers-contract/ethers-contract-derive/src/abi_ty.rs b/ethers-contract/ethers-contract-derive/src/abi_ty.rs index b6e6dfda..f25fd4b8 100644 --- a/ethers-contract/ethers-contract-derive/src/abi_ty.rs +++ b/ethers-contract/ethers-contract-derive/src/abi_ty.rs @@ -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 { + 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,56 +120,57 @@ 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> - where - #generic_predicates - #tokenize_predicates - { - - fn from_token(token: #core_crate::abi::Token) -> ::std::result::Result where - Self: Sized { + impl #impl_generics #ethers_core::abi::Tokenizable for #name #ty_generics + where + #generic_predicates + #tokenize_predicates + { + fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result + 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> - where - #generic_predicates - #tokenize_predicates - { } - } + 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 where - Self: Sized { + impl #ethers_core::abi::Tokenizable for #name { + fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result + where + Self: Sized, + { if let #ethers_core::abi::Token::Tuple(tokens) = token { if !tokens.is_empty() { - Err(#ethers_core::abi::InvalidOutputType(::std::format!( - "Expected empty tuple, got {:?}", - tokens - ))) + Err(#ethers_core::abi::InvalidOutputType(::std::format!( + "Expected empty tuple, got {:?}", + tokens + ))) } else { Ok(#name{}) } @@ -194,10 +183,11 @@ fn tokenize_unit_type(name: &Ident) -> TokenStream { } fn into_token(self) -> #ethers_core::abi::Token { - #ethers_core::abi::Token::Tuple(::std::vec::Vec::new()) + #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 + 'a, -) -> ::std::result::Result { +) -> Result { let ethers_core = ethers_core_crate(); let mut into_tokens = TokenStream::new(); @@ -225,12 +215,12 @@ fn tokenize_enum<'a>( } else if variant.fields.is_empty() { let value = Literal::u8_unsuffixed(idx as u8); from_tokens.extend(quote! { - if let Ok(#value) = u8::from_token(token.clone()) { + if let Ok(#value) = u8::from_token(token.clone()) { return Ok(#enum_name::#var_ident) } }); into_tokens.extend(quote! { - #enum_name::#var_ident => #value.into_token(), + #enum_name::#var_ident => #value.into_token(), }); } else if let Some(field) = variant.fields.iter().next() { let ty = &field.ty; @@ -240,30 +230,32 @@ fn tokenize_enum<'a>( } }); into_tokens.extend(quote! { - #enum_name::#var_ident(element) => element.into_token(), + #enum_name::#var_ident(element) => element.into_token(), }); } else { into_tokens.extend(quote! { - #enum_name::#var_ident(element) => # ethers_core::abi::Token::Tuple(::std::vec::Vec::new()), - }); + #enum_name::#var_ident(element) => # ethers_core::abi::Token::Tuple(::std::vec::Vec::new()), + }); } } Ok(quote! { - impl #ethers_core::abi::Tokenizable for #enum_name { - - fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result where - Self: Sized { + impl #ethers_core::abi::Tokenizable for #enum_name { + fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result + where + Self: Sized, + { #from_tokens Err(#ethers_core::abi::InvalidOutputType("Failed to decode all type variants".to_string())) } fn into_token(self) -> #ethers_core::abi::Token { match self { - #into_tokens + #into_tokens } } - } - impl #ethers_core::abi::TokenizableItem for #enum_name { } + } + + impl #ethers_core::abi::TokenizableItem for #enum_name {} }) } diff --git a/ethers-contract/ethers-contract-derive/src/abigen.rs b/ethers-contract/ethers-contract-derive/src/abigen.rs index 6d0de4b8..5fcabf9f 100644 --- a/ethers-contract/ethers-contract-derive/src/abigen.rs +++ b/ethers-contract/ethers-contract-derive/src/abigen.rs @@ -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 { + pub(crate) fn expand(self) -> Result { 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> { - 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> { + fn into_builder(self) -> Result { 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::>>()?; diff --git a/ethers-contract/ethers-contract-derive/src/call.rs b/ethers-contract/ethers-contract-derive/src/call.rs index ac89afb8..f0ff15a0 100644 --- a/ethers-contract/ethers-contract-derive/src/call.rs +++ b/ethers-contract/ethers-contract-derive/src/call.rs @@ -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 { + 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 { - 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, decode_impl: TokenStream, -) -> TokenStream { +) -> Result { // 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 - } + }) } diff --git a/ethers-contract/ethers-contract-derive/src/calllike.rs b/ethers-contract/ethers-contract-derive/src/calllike.rs index 4f54057f..6cac69e3 100644 --- a/ethers-contract/ethers-contract-derive/src/calllike.rs +++ b/ethers-contract/ethers-contract-derive/src/calllike.rs @@ -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 { +) -> Result { 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 { - 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] != ::selector() { - return Err(#contract_crate::AbiError::WrongSelector); + if bytes.len() < 4 || bytes[..4] != ::selector() { + return Err(#ethers_contract::AbiError::WrongSelector); } #data_types_init - let data_tokens = #core_crate::abi::decode(&data_types, &bytes[4..])?; - Ok(::from_token( #core_crate::abi::Token::Tuple(data_tokens))?) + let data_tokens = #ethers_core::abi::decode(&data_types, &bytes[4..])?; + Ok(::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 { // 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 { + impl #ethers_core::abi::AbiDecode for #struct_name { + fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result { #decode_impl } } - impl #core_crate::abi::AbiEncode for #struct_name { + impl #ethers_core::abi::AbiEncode for #struct_name { fn encode(self) -> ::std::vec::Vec { - let tokens = #core_crate::abi::Tokenize::into_tokens(self); - let selector = ::selector(); - let encoded = #core_crate::abi::encode(&tokens); + let tokens = #ethers_core::abi::Tokenize::into_tokens(self); + let selector = ::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 - } + }) } diff --git a/ethers-contract/ethers-contract-derive/src/codec.rs b/ethers-contract/ethers-contract-derive/src/codec.rs index 15472fc2..35348db8 100644 --- a/ethers-contract/ethers-contract-derive/src/codec.rs +++ b/ethers-contract/ethers-contract-derive/src/codec.rs @@ -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 { - if let #core_crate::abi::ParamType::Tuple(params) = ::param_type() { - let tokens = #core_crate::abi::decode(¶ms, bytes.as_ref())?; - Ok(::from_token(#core_crate::abi::Token::Tuple(tokens))?) + impl #ethers_core::abi::AbiDecode for #name { + fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result { + if let #ethers_core::abi::ParamType::Tuple(params) = ::param_type() { + let tokens = #ethers_core::abi::decode(¶ms, bytes.as_ref())?; + Ok(::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 { - 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) } } } diff --git a/ethers-contract/ethers-contract-derive/src/display.rs b/ethers-contract/ethers-contract-derive/src/display.rs index 8fc613f1..b8597fd9 100644 --- a/ethers-contract/ethers-contract-derive/src/display.rs +++ b/ethers-contract/ethers-contract-derive/src/display.rs @@ -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 { - 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 Result { quote! { - write!(f, "{:?}", self.#ident)?; + write!(f, "{:?}", self.#ident)?; } } ParamType::Bytes => { quote! { - write!(f, "0x{}", #hex_encode(&self.#ident))?; + write!(f, "0x{}", #hex_encode(&self.#ident))?; } } ParamType::Bool | ParamType::String => { quote! { - self.#ident.fmt(f)?; + self.#ident.fmt(f)?; } } ParamType::Tuple(_) => { @@ -59,32 +57,32 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result { quote! { - write!(f, "0x{}", #hex_encode(&self.#ident))?; + write!(f, "0x{}", #hex_encode(&self.#ident))?; } } } } else { // could not detect the parameter type and rely on using debug fmt quote! { - write!(f, "{:?}", &self.#ident)?; + write!(f, "{:?}", &self.#ident)?; } }; fmts.extend(tokens); @@ -92,6 +90,7 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result 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 { + 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 - return match derive_trait_impls_with_abi_type( - &input, - &error_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_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) => Ok(derived), + Err(err) => { + 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 { - 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, decode_impl: TokenStream, -) -> TokenStream { +) -> Result { // 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 - } + }) } diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs index 34c4b0a2..dd0d467a 100644 --- a/ethers-contract/ethers-contract-derive/src/event.rs +++ b/ethers-contract/ethers-contract-derive/src/event.rs @@ -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 { // 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 { #[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() } diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs index f1b1fd38..969ea99b 100644 --- a/ethers-contract/ethers-contract-derive/src/utils.rs +++ b/ethers-contract/ethers-contract-derive/src/utils.rs @@ -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 { - let size = s.chars().skip(1).collect::().parse::().ok()?; - if s.starts_with('u') { - Some(ParamType::Uint(size)) - } else if s.starts_with('i') { - Some(ParamType::Int(size)) - } else { - None + match s.chars().next() { + Some(c @ 'u') | Some(c @ 'i') => { + let size = s[1..].parse::().ok()?; + if c == 'u' { + Some(ParamType::Uint(size)) + } else { + Some(ParamType::Int(size)) + } + } + _ => None, } } @@ -37,64 +40,58 @@ pub fn parse_int_param_type(s: &str) -> Option { // This applies to strings, arrays, structs and bytes to follow the encoding of // these indexed param types according to // -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 { 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 { } 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::, _>>()?; - Ok(ParamType::Tuple(params)) - } + Type::Tuple(ty) => ty + .elems + .iter() + .map(find_parameter_type) + .collect::, _>>() + .map(ParamType::Tuple), _ => Err(Error::new(ty.span(), "Failed to derive proper ABI from fields")), } } @@ -200,29 +199,29 @@ pub fn derive_abi_inputs_from_fields( pub fn derive_param_type_with_abi_type( input: &DeriveInput, trait_name: &str, -) -> Result { - let core_crate = ethers_core_crate(); - let params = derive_abi_parameters_array(input, trait_name)?; +) -> Result { + 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 `(*)` 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 { - let params = derive_abi_parameters_array(input, trait_name)?; +) -> Result { + let params = abi_parameters_array(input, trait_name)?; Ok(quote! { { let params: String = #params - .iter() - .map(|p| p.to_string()) - .collect::<::std::vec::Vec<_>>() - .join(","); + .iter() + .map(|p| p.to_string()) + .collect::<::std::vec::Vec<_>>() + .join(","); let function_name = #function_name; format!("{}({})", function_name, 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 { - let core_crate = ethers_core_crate(); +pub fn abi_parameters_array(input: &DeriveInput, trait_name: &str) -> Result { + 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 ),*] }) }