Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(contract): report all errors during parsing #2149

Merged
merged 2 commits into from Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
144 changes: 62 additions & 82 deletions ethers-contract/ethers-contract-derive/src/event.rs
Expand Up @@ -17,85 +17,80 @@ 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<TokenStream, Error> {
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::<Source>().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::<Source>().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
}

fn abi_signature() -> ::std::borrow::Cow<'static, str> {
#abi.into()
}

fn decode_log(log: &#core_crate::abi::RawLog) -> ::std::result::Result<Self, #core_crate::abi::Error> where Self: Sized {
fn decode_log(log: &#ethers_core::abi::RawLog) -> ::std::result::Result<Self, #ethers_core::abi::Error> where Self: Sized {
#decode_log_impl
}

Expand All @@ -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
Expand All @@ -130,7 +125,7 @@ fn derive_decode_from_log_impl(
input: &DeriveInput,
event: &Event,
) -> Result<proc_macro2::TokenStream, Error> {
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 {
Expand Down Expand Up @@ -213,24 +208,24 @@ 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! {
let flat_topics = topics.iter().skip(1).flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
},
quote! {
if topic_tokens.len() != topics.len() - 1 {
return Err(#core_crate::abi::Error::InvalidData);
return Err(#ethers_core::abi::Error::InvalidData);
}
},
)
Expand All @@ -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 {
Expand All @@ -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

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

Expand Down Expand Up @@ -355,9 +350,7 @@ struct EthEventAttributes {
}

/// extracts the attributes from the struct annotated with `EthEvent`
fn parse_event_attributes(
input: &DeriveInput,
) -> Result<EthEventAttributes, proc_macro2::TokenStream> {
fn parse_event_attributes(input: &DeriveInput) -> Result<EthEventAttributes, Error> {
let mut result = EthEventAttributes::default();
for a in input.attrs.iter() {
if let AttrStyle::Outer = a.style {
Expand All @@ -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") {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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())
))
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion ethers-contract/ethers-contract-derive/src/lib.rs
Expand Up @@ -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.
Expand Down