From fefaf7433fb0b9f4f062a2e0f61e40a4ad9a7d86 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 14 Feb 2023 02:08:46 +0100 Subject: [PATCH] fix(contract): report all errors during parsing (#2149) * refactor: use Result<_, Error> * fix: report both errors during parsing --- .../ethers-contract-derive/src/event.rs | 144 ++++++++---------- .../ethers-contract-derive/src/lib.rs | 5 +- 2 files changed, 66 insertions(+), 83 deletions(-) diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs index e2a1b7ec..8fc5ebfa 100644 --- a/ethers-contract/ethers-contract-derive/src/event.rs +++ b/ethers-contract/ethers-contract-derive/src/event.rs @@ -17,77 +17,72 @@ use hex::FromHex; use crate::{abi_ty, utils}; /// Generates the `EthEvent` trait support -pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream { - // the ethers crates to use - let core_crate = ethers_core_crate(); - let contract_crate = ethers_contract_crate(); - +pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result { let name = &input.ident; - let attributes = match parse_event_attributes(&input) { - Ok(attributes) => attributes, - Err(errors) => return errors, - }; + let attributes = parse_event_attributes(&input)?; let event_name = attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string()); let mut event = if let Some((src, span)) = attributes.abi { - // try to parse as solidity event - if let Ok(event) = HumanReadableParser::parse_event(&src) { - event - } else { - match src.parse::().and_then(|s| s.get()) { - Ok(abi) => { - // try to derive the signature from the abi from the parsed abi - // TODO(mattsse): this will fail for events that contain other non - // elementary types in their abi because the parser - // doesn't know how to substitute the types - // this could be mitigated by getting the ABI of each non elementary type - // at runtime and computing the the signature as - // `static Lazy::...` - match HumanReadableParser::parse_event(&abi) { - Ok(event) => event, - Err(err) => return Error::new(span, err).to_compile_error(), + // try to parse as a Solidity event + match HumanReadableParser::parse_event(&src) { + Ok(event) => Ok(event), + Err(parse_err) => { + match src.parse::().and_then(|s| s.get()) { + Ok(abi) => { + // try to derive the signature from the abi from the parsed abi + // TODO(mattsse): this will fail for events that contain other non + // elementary types in their abi because the parser + // doesn't know how to substitute the types. + // This could be mitigated by getting the ABI of each non elementary type + // at runtime and computing the the signature as a Lazy static. + match HumanReadableParser::parse_event(&abi) { + Ok(event) => Ok(event), + // Ignore parse_err since this is a valid [Source] + Err(err) => Err(Error::new(span, err)), + } + } + Err(source_err) => { + // Return both error messages + let message = format!("Failed parsing ABI: {parse_err} ({source_err})"); + Err(Error::new(span, message)) } } - Err(err) => return Error::new(span, err).to_compile_error(), } } } else { // try to determine the abi from the fields - match derive_abi_event_from_fields(&input) { - Ok(event) => event, - Err(err) => return err.to_compile_error(), - } - }; + derive_abi_event_from_fields(&input) + }?; event.name = event_name.clone(); if let Some((anon, _)) = attributes.anonymous.as_ref() { event.anonymous = *anon; } - let decode_log_impl = match derive_decode_from_log_impl(&input, &event) { - Ok(log) => log, - Err(err) => return err.to_compile_error(), - }; + let decode_log_impl = derive_decode_from_log_impl(&input, &event)?; - let (abi, hash) = (event.abi_signature(), event.signature()); + let (abi, event_sig) = (event.abi_signature(), event.signature()); let signature = if let Some((hash, _)) = attributes.signature_hash { utils::signature(&hash) } else { - utils::signature(hash.as_bytes()) + utils::signature(event_sig.as_bytes()) }; let anon = attributes.anonymous.map(|(b, _)| b).unwrap_or_default(); + let ethers_core = ethers_core_crate(); + let ethers_contract = ethers_contract_crate(); + let ethevent_impl = quote! { - impl #contract_crate::EthEvent for #name { + impl #ethers_contract::EthEvent for #name { fn name() -> ::std::borrow::Cow<'static, str> { #event_name.into() } - fn signature() -> #core_crate::types::H256 { + fn signature() -> #ethers_core::types::H256 { #signature } @@ -95,7 +90,7 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream { #abi.into() } - fn decode_log(log: &#core_crate::abi::RawLog) -> ::std::result::Result where Self: Sized { + fn decode_log(log: &#ethers_core::abi::RawLog) -> ::std::result::Result where Self: Sized { #decode_log_impl } @@ -107,10 +102,10 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream { let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input); - quote! { + Ok(quote! { #tokenize_impl #ethevent_impl - } + }) } /// Internal helper type for an event/log @@ -130,7 +125,7 @@ fn derive_decode_from_log_impl( input: &DeriveInput, event: &Event, ) -> Result { - let core_crate = ethers_core_crate(); + let ethers_core = ethers_core_crate(); let fields: Vec<_> = match input.data { Data::Struct(ref data) => match data.fields { @@ -213,16 +208,16 @@ fn derive_decode_from_log_impl( }, quote! { if topic_tokens.len() != topics.len() { - return Err(#core_crate::abi::Error::InvalidData); + return Err(#ethers_core::abi::Error::InvalidData); } }, ) } else { ( quote! { - let event_signature = topics.get(0).ok_or(#core_crate::abi::Error::InvalidData)?; + let event_signature = topics.get(0).ok_or(#ethers_core::abi::Error::InvalidData)?; if event_signature != &Self::signature() { - return Err(#core_crate::abi::Error::InvalidData); + return Err(#ethers_core::abi::Error::InvalidData); } }, quote! { @@ -230,7 +225,7 @@ fn derive_decode_from_log_impl( }, quote! { if topic_tokens.len() != topics.len() - 1 { - return Err(#core_crate::abi::Error::InvalidData); + return Err(#ethers_core::abi::Error::InvalidData); } }, ) @@ -244,9 +239,9 @@ fn derive_decode_from_log_impl( .all(|(idx, f)| f.index == idx) { quote! { - let topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?; + let topic_tokens = #ethers_core::abi::decode(&topic_types, &flat_topics)?; #topic_tokens_len_check - let data_tokens = #core_crate::abi::decode(&data_types, data)?; + let data_tokens = #ethers_core::abi::decode(&data_types, data)?; let tokens:Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect(); } } else { @@ -259,16 +254,16 @@ fn derive_decode_from_log_impl( }); quote! { - let mut topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?; + let mut topic_tokens = #ethers_core::abi::decode(&topic_types, &flat_topics)?; #topic_tokens_len_check - let mut data_tokens = #core_crate::abi::decode(&data_types, &data)?; + let mut data_tokens = #ethers_core::abi::decode(&data_types, &data)?; let mut tokens = Vec::with_capacity(topics.len() + data_tokens.len()); #( tokens.push(#swap_tokens); )* } }; Ok(quote! { - let #core_crate::abi::RawLog {data, topics} = log; + let #ethers_core::abi::RawLog {data, topics} = log; #signature_check @@ -279,7 +274,7 @@ fn derive_decode_from_log_impl( #tokens_init - #core_crate::abi::Tokenizable::from_token(#core_crate::abi::Token::Tuple(tokens)).map_err(|_|#core_crate::abi::Error::InvalidData) + #ethers_core::abi::Tokenizable::from_token(#ethers_core::abi::Token::Tuple(tokens)).map_err(|_|#ethers_core::abi::Error::InvalidData) }) } @@ -355,9 +350,7 @@ struct EthEventAttributes { } /// extracts the attributes from the struct annotated with `EthEvent` -fn parse_event_attributes( - input: &DeriveInput, -) -> Result { +fn parse_event_attributes(input: &DeriveInput) -> Result { let mut result = EthEventAttributes::default(); for a in input.attrs.iter() { if let AttrStyle::Outer = a.style { @@ -376,23 +369,20 @@ fn parse_event_attributes( return Err(Error::new( name.span(), "anonymous already specified", - ) - .to_compile_error()) + )) } } } return Err(Error::new( path.span(), "unrecognized ethevent parameter", - ) - .to_compile_error()) + )) } Meta::List(meta) => { return Err(Error::new( meta.path.span(), "unrecognized ethevent parameter", - ) - .to_compile_error()) + )) } Meta::NameValue(meta) => { if meta.path.is_ident("anonymous") { @@ -404,15 +394,13 @@ fn parse_event_attributes( return Err(Error::new( meta.span(), "anonymous 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("name") { if let Lit::Str(ref lit_str) = meta.lit { @@ -423,15 +411,13 @@ fn parse_event_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 { @@ -442,15 +428,13 @@ fn parse_event_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 if meta.path.is_ident("signature") { if let Lit::Str(ref lit_str) = meta.lit { @@ -466,30 +450,26 @@ fn parse_event_attributes( format!( "Expected hex signature: {err:?}" ), - ) - .to_compile_error()) + )) } } } else { return Err(Error::new( meta.span(), "signature already specified", - ) - .to_compile_error()) + )) } } else { return Err(Error::new( meta.span(), "signature must be a hex string", - ) - .to_compile_error()) + )) } } else { return Err(Error::new( meta.span(), "unrecognized ethevent parameter", - ) - .to_compile_error()) + )) } } } diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs index 81f3c773..891026e0 100644 --- a/ethers-contract/ethers-contract-derive/src/lib.rs +++ b/ethers-contract/ethers-contract-derive/src/lib.rs @@ -217,7 +217,10 @@ pub fn derive_eth_display(input: TokenStream) -> TokenStream { #[proc_macro_derive(EthEvent, attributes(ethevent))] pub fn derive_abi_event(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - TokenStream::from(event::derive_eth_event_impl(input)) + match event::derive_eth_event_impl(input) { + Ok(tokens) => TokenStream::from(tokens), + Err(err) => err.to_compile_error().into(), + } } /// Derives the `EthCall` and `Tokenizeable` trait for the labeled type.