//! Helper functions for deriving `EthEvent` use ethers_contract_abigen::{ethers_contract_crate, ethers_core_crate, Source}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::spanned::Spanned as _; use syn::{parse::Error, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta, NestedMeta}; use ethers_core::abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType}; use hex::FromHex; use crate::abi_ty; use crate::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(); let name = &input.ident; let attributes = match parse_event_attributes(&input) { Ok(attributes) => attributes, Err(errors) => return errors, }; 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) = parse_event(&src) { event } else { // try as tuple if let Some(inputs) = Reader::read( src.trim_start_matches("event ") .trim_start() .trim_start_matches(&event_name), ) .ok() .and_then(|param| match param { ParamType::Tuple(params) => Some( params .into_iter() .map(|kind| EventParam { name: "".to_string(), indexed: false, kind, }) .collect(), ), _ => None, }) { Event { name: event_name.clone(), inputs, anonymous: false, } } 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 parse_event(&abi) { Ok(event) => event, Err(err) => return Error::new(span, err).to_compile_error(), } } 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(), } }; 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 (abi, hash) = (event.abi_signature(), event.signature()); let signature = if let Some((hash, _)) = attributes.signature_hash { utils::signature(&hash) } else { utils::signature(hash.as_bytes()) }; let anon = attributes.anonymous.map(|(b, _)| b).unwrap_or_default(); let ethevent_impl = quote! { impl #contract_crate::EthEvent for #name { fn name() -> ::std::borrow::Cow<'static, str> { #event_name.into() } fn signature() -> #core_crate::types::H256 { #signature } fn abi_signature() -> ::std::borrow::Cow<'static, str> { #abi.into() } fn decode_log(log: &#core_crate::abi::RawLog) -> Result where Self: Sized { #decode_log_impl } fn is_anonymous() -> bool { #anon } } }; let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input); quote! { #tokenize_impl #ethevent_impl } } /// Internal helper type for an event/log struct EventField { topic_name: Option, index: usize, param: EventParam, } impl EventField { fn is_indexed(&self) -> bool { self.topic_name.is_some() } } fn derive_decode_from_log_impl( input: &DeriveInput, event: &Event, ) -> Result { let core_crate = ethers_core_crate(); let fields: Vec<_> = match input.data { Data::Struct(ref data) => match data.fields { Fields::Named(ref fields) => { if fields.named.len() != event.inputs.len() { return Err(Error::new( fields.span(), format!( "EthEvent {}'s fields length don't match with signature inputs {}", event.name, event.abi_signature() ), )); } fields.named.iter().collect() } Fields::Unnamed(ref fields) => { if fields.unnamed.len() != event.inputs.len() { return Err(Error::new( fields.span(), format!( "EthEvent {}'s fields length don't match with signature inputs {}", event.name, event.abi_signature() ), )); } fields.unnamed.iter().collect() } Fields::Unit => { return Err(Error::new( input.span(), "EthEvent cannot be derived for empty structs and unit", )); } }, Data::Enum(_) => { return Err(Error::new( input.span(), "EthEvent cannot be derived for enums", )); } Data::Union(_) => { return Err(Error::new( input.span(), "EthEvent cannot be derived for unions", )); } }; let mut event_fields = Vec::with_capacity(fields.len()); for (index, field) in fields.iter().enumerate() { let mut param = event.inputs[index].clone(); let (topic_name, indexed) = parse_field_attributes(field)?; if indexed { param.indexed = true; } let topic_name = param .indexed .then(|| topic_name.or_else(|| Some(param.name.clone()))) .flatten(); event_fields.push(EventField { topic_name, index, param, }); } // convert fields to params list let topic_types = event_fields .iter() .filter(|f| f.is_indexed()) .map(|f| utils::topic_param_type_quote(&f.param.kind)); let topic_types_init = quote! {let topic_types = ::std::vec![#( #topic_types ),*];}; let data_types = event_fields .iter() .filter(|f| !f.is_indexed()) .map(|f| utils::param_type_quote(&f.param.kind)); let data_types_init = quote! {let data_types = [#( #data_types ),*];}; // decode let (signature_check, flat_topics_init, topic_tokens_len_check) = if event.anonymous { ( quote! {}, quote! { let flat_topics = topics.iter().flat_map(|t| t.as_ref().to_vec()).collect::>(); }, quote! { if topic_tokens.len() != topics.len() { return Err(#core_crate::abi::Error::InvalidData); } }, ) } else { ( quote! { let event_signature = topics.get(0).ok_or(#core_crate::abi::Error::InvalidData)?; if event_signature != &Self::signature() { return Err(#core_crate::abi::Error::InvalidData); } }, quote! { let flat_topics = topics.iter().skip(1).flat_map(|t| t.as_ref().to_vec()).collect::>(); }, quote! { if topic_tokens.len() != topics.len() - 1 { return Err(#core_crate::abi::Error::InvalidData); } }, ) }; // check if indexed are sorted let tokens_init = if event_fields .iter() .filter(|f| f.is_indexed()) .enumerate() .all(|(idx, f)| f.index == idx) { quote! { let topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?; #topic_tokens_len_check let data_tokens = #core_crate::abi::decode(&data_types, data)?; let tokens:Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect(); } } else { let swap_tokens = event_fields.iter().map(|field| { if field.is_indexed() { quote! { topic_tokens.remove(0) } } else { quote! { data_tokens.remove(0) } } }); quote! { let mut topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?; #topic_tokens_len_check let mut data_tokens = #core_crate::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; #signature_check #topic_types_init #data_types_init #flat_topics_init #tokens_init #core_crate::abi::Tokenizable::from_token(#core_crate::abi::Token::Tuple(tokens)).map_err(|_|#core_crate::abi::Error::InvalidData) }) } /// Determine the event's ABI by parsing the AST fn derive_abi_event_from_fields(input: &DeriveInput) -> Result { let event = Event { name: "".to_string(), inputs: utils::derive_abi_inputs_from_fields(input, "EthEvent")? .into_iter() .map(|(name, kind)| EventParam { name, kind, indexed: false, }) .collect(), anonymous: false, }; Ok(event) } fn parse_field_attributes(field: &Field) -> Result<(Option, bool), Error> { let mut indexed = false; let mut topic_name = None; for a in field.attrs.iter() { if let AttrStyle::Outer = a.style { if let Ok(Meta::List(meta)) = a.parse_meta() { if meta.path.is_ident("ethevent") { for n in meta.nested.iter() { if let NestedMeta::Meta(meta) = n { match meta { Meta::Path(path) => { if path.is_ident("indexed") { indexed = true; } else { return Err(Error::new( path.span(), "unrecognized ethevent parameter", )); } } Meta::List(meta) => { return Err(Error::new( meta.path.span(), "unrecognized ethevent parameter", )); } Meta::NameValue(meta) => { if meta.path.is_ident("name") { if let Lit::Str(ref lit_str) = meta.lit { topic_name = Some(lit_str.value()); } else { return Err(Error::new( meta.span(), "name attribute must be a string", )); } } } } } } } } } } Ok((topic_name, indexed)) } fn parse_event(abi: &str) -> Result { let abi = if !abi.trim_start().starts_with("event ") { format!("event {}", abi) } else { abi.to_string() }; AbiParser::default() .parse_event(&abi) .map_err(|err| format!("Failed to parse the event ABI: {:?}", err)) } /// All the attributes the `EthEvent` macro supports #[derive(Default)] struct EthEventAttributes { name: Option<(String, Span)>, abi: Option<(String, Span)>, signature_hash: Option<(Vec, Span)>, anonymous: Option<(bool, Span)>, } /// extracts the attributes from the struct annotated with `EthEvent` fn parse_event_attributes( input: &DeriveInput, ) -> Result { let mut result = EthEventAttributes::default(); for a in input.attrs.iter() { if let AttrStyle::Outer = a.style { if let Ok(Meta::List(meta)) = a.parse_meta() { if meta.path.is_ident("ethevent") { for n in meta.nested.iter() { if let NestedMeta::Meta(meta) = n { match meta { Meta::Path(path) => { if let Some(name) = path.get_ident() { if &*name.to_string() == "anonymous" { if result.anonymous.is_none() { result.anonymous = Some((true, name.span())); continue; } else { 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") { if let Lit::Bool(ref bool_lit) = meta.lit { if result.anonymous.is_none() { result.anonymous = Some((bool_lit.value, bool_lit.span())); } else { 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 { if result.name.is_none() { result.name = Some((lit_str.value(), lit_str.span())); } else { 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 { if result.abi.is_none() { result.abi = Some((lit_str.value(), lit_str.span())); } else { 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 { if result.signature_hash.is_none() { match Vec::from_hex(lit_str.value()) { Ok(sig) => { result.signature_hash = Some((sig, lit_str.span())) } Err(err) => { return Err(Error::new( meta.span(), 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()); } } } } } } } } } Ok(result) }