From b8142a3bd3ec38ccf1efd2c3fd4c5df494b85f4b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 15 Oct 2021 22:14:39 +0200 Subject: [PATCH 1/9] chore: move proc macro implementation to separate modules --- .../ethers-contract-derive/src/abi_ty.rs | 171 ++++ .../ethers-contract-derive/src/event.rs | 594 +++++++++++ .../ethers-contract-derive/src/lib.rs | 958 +----------------- .../ethers-contract-derive/src/utils.rs | 159 +++ 4 files changed, 959 insertions(+), 923 deletions(-) create mode 100644 ethers-contract/ethers-contract-derive/src/abi_ty.rs create mode 100644 ethers-contract/ethers-contract-derive/src/event.rs create mode 100644 ethers-contract/ethers-contract-derive/src/utils.rs diff --git a/ethers-contract/ethers-contract-derive/src/abi_ty.rs b/ethers-contract/ethers-contract-derive/src/abi_ty.rs new file mode 100644 index 000000000..69818dad7 --- /dev/null +++ b/ethers-contract/ethers-contract-derive/src/abi_ty.rs @@ -0,0 +1,171 @@ +//! Helper functions for deriving `EthAbiType` + +use ethers_contract_abigen::ethers_core_crate; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned as _; +use syn::{parse::Error, Data, DeriveInput, Fields}; + +/// Generates the tokenize implementation +pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { + let core_crate = 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 (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 } + }); + 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("tokens size is sufficient qed").into_token())? } + }); + let init_struct_impl = quote! { Self { #(#assignments,)* } }; + + let into_token = fields.named.iter().map(|f| { + let name = f.ident.as_ref().expect("Named fields have names"); + quote_spanned! { f.span() => self.#name.into_token() } + }); + let into_token_impl = quote! { #(#into_token,)* }; + + ( + tokenize_predicates, + fields.named.len(), + init_struct_impl, + into_token_impl, + ) + } + 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 } + }); + 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("tokens size is sufficient qed").into_token())? } + }); + let init_struct_impl = quote! { Self(#(#assignments,)* ) }; + + let into_token = fields.unnamed.iter().enumerate().map(|(i, f)| { + let idx = syn::Index::from(i); + quote_spanned! { f.span() => self.#idx.into_token() } + }); + let into_token_impl = quote! { #(#into_token,)* }; + + ( + tokenize_predicates, + fields.unnamed.len(), + init_struct_impl, + into_token_impl, + ) + } + Fields::Unit => { + return Error::new( + input.span(), + "EthAbiType cannot be derived for empty structs and unit", + ) + .to_compile_error(); + } + }, + Data::Enum(_) => { + return Error::new(input.span(), "EthAbiType cannot be derived for enums") + .to_compile_error(); + } + Data::Union(_) => { + return Error::new(input.span(), "EthAbiType cannot be derived for unions") + .to_compile_error(); + } + }; + + // there might be the case that the event has only 1 params, which is not a + // tuple + let (from_token_impl, into_token_impl) = match params_len { + 0 => ( + quote! { + Ok(#init_struct_impl) + }, + // can't encode an empty struct + // TODO: panic instead? + quote! { + #core_crate::abi::Token::Tuple(Vec::new()) + }, + ), + _ => { + let from_token = quote! { + if let #core_crate::abi::Token::Tuple(tokens) = token { + if tokens.len() != #params_len { + return Err(#core_crate::abi::InvalidOutputType(::std::format!( + "Expected {} tokens, got {}: {:?}", + #params_len, + tokens.len(), + tokens + ))); + } + + let mut iter = tokens.into_iter(); + + Ok(#init_struct_impl) + } else { + Err(#core_crate::abi::InvalidOutputType(::std::format!( + "Expected Tuple, got {:?}", + token + ))) + } + }; + + let into_token = quote! { + #core_crate::abi::Token::Tuple( + ::std::vec![ + #into_token_impl + ] + ) + }; + (from_token, into_token) + } + }; + + quote! { + impl<#generic_params> #core_crate::abi::Tokenizable for #name<#generic_args> + where + #generic_predicates + #tokenize_predicates + { + + fn from_token(token: #core_crate::abi::Token) -> Result where + Self: Sized { + #from_token_impl + } + + fn into_token(self) -> #core_crate::abi::Token { + #into_token_impl + } + } + + impl<#generic_params> #core_crate::abi::TokenizableItem for #name<#generic_args> + where + #generic_predicates + #tokenize_predicates + { } + } +} diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs new file mode 100644 index 000000000..78b8b5a8f --- /dev/null +++ b/ethers-contract/ethers-contract-derive/src/event.rs @@ -0,0 +1,594 @@ +//! 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); + + // parse attributes abi into source + 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) + }) +} + +fn derive_abi_event_from_fields(input: &DeriveInput) -> Result { + let fields: Vec<_> = 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 => { + 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 inputs = fields + .iter() + .map(|f| { + let name = f + .ident + .as_ref() + .map(|name| name.to_string()) + .unwrap_or_else(|| "".to_string()); + utils::find_parameter_type(&f.ty).map(|ty| (name, ty)) + }) + .collect::, _>>()?; + + let event = Event { + name: "".to_string(), + inputs: inputs + .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) +} diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs index c0ba36d25..37a1010c8 100644 --- a/ethers-contract/ethers-contract-derive/src/lib.rs +++ b/ethers-contract/ethers-contract-derive/src/lib.rs @@ -1,27 +1,21 @@ //! Implementation of procedural macro for generating type-safe bindings to an //! ethereum smart contract. -#![deny(missing_docs, unsafe_code)] +#![deny(missing_docs, unsafe_code, unused)] -use ethers_contract_abigen::{ethers_contract_crate, ethers_core_crate, Source}; use proc_macro::TokenStream; -use proc_macro2::{Literal, Span}; -use quote::{quote, quote_spanned}; -use syn::spanned::Spanned as _; -use syn::{ - parse::Error, parse_macro_input, AttrStyle, Data, DeriveInput, Expr, Field, Fields, - GenericArgument, Lit, Meta, NestedMeta, PathArguments, Type, -}; +use syn::{parse_macro_input, DeriveInput}; use abigen::Contracts; -use ethers_core::abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType}; -use hex::FromHex; +pub(crate) mod abi_ty; mod abigen; +mod event; mod spanned; +pub(crate) mod utils; -/// Proc macro to generate type-safe bindings to a contract(s). This macro accepts -/// one or more Ethereum contract ABI or a path. Note that this path is rooted in -/// the crate's root `CARGO_MANIFEST_DIR`. +/// Proc macro to generate type-safe bindings to a contract(s). This macro +/// accepts one or more Ethereum contract ABI or a path. Note that this path is +/// rooted in the crate's root `CARGO_MANIFEST_DIR`. /// /// # Examples /// @@ -68,7 +62,9 @@ mod spanned; /// ``` /// /// `abigen!` supports multiple abigen definitions separated by a semicolon `;` -/// This is useful if the contracts use ABIEncoderV2 structs. In which case `abigen!` bundles all type duplicates so that all rust contracts also use the same rust types. +/// This is useful if the contracts use ABIEncoderV2 structs. In which case +/// `abigen!` bundles all type duplicates so that all rust contracts also use +/// the same rust types. /// /// # Example Multiple contracts /// ```ignore @@ -95,25 +91,41 @@ pub fn abigen(input: TokenStream) -> TokenStream { .into() } +/// Derives the `Tokenizable` trait for the labeled type. +/// +/// This derive macro automatically adds a type bound `field: Tokenizable` for +/// each field type. +#[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)) +} + /// Derives the `EthEvent` and `Tokenizeable` trait for the labeled type. /// -/// Additional arguments can be specified using the `#[ethevent(...)]` attribute: +/// Additional arguments can be specified using the `#[ethevent(...)]` +/// attribute: /// /// For the struct: /// -/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the +/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default +/// is the /// struct's name. -/// - `signature`, `signature = "..."`: The signature as hex string to override the +/// - `signature`, `signature = "..."`: The signature as hex string to override +/// the /// event's signature. -/// - `abi`, `abi = "..."`: The ABI signature for the event this event's data corresponds to. -/// The `abi` should be solidity event definition or a tuple of the event's types in case the -/// event has non elementary (other `EthAbiType`) types as members +/// - `abi`, `abi = "..."`: The ABI signature for the event this event's data +/// corresponds to. +/// The `abi` should be solidity event definition or a tuple of the event's +/// types in case the event has non elementary (other `EthAbiType`) types as +/// members /// - `anonymous`: A flag to mark this as an anonymous event /// /// For fields: /// /// - `indexed`: flag to mark a field as an indexed event input -/// - `name`: override the name of an indexed event input, default is the rust field name +/// - `name`: override the name of an indexed event input, default is the rust +/// field name /// /// # Example /// ```ignore @@ -137,905 +149,5 @@ pub fn abigen(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); - - // 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_attributes(&input) { - Ok(attributes) => attributes, - Err(errors) => return TokenStream::from(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 TokenStream::from(Error::new(span, err).to_compile_error()) - } - } - } - Err(err) => return TokenStream::from(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 TokenStream::from(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 TokenStream::from(err.to_compile_error()), - }; - - let (abi, hash) = (event.abi_signature(), event.signature()); - - let signature = if let Some((hash, _)) = attributes.signature_hash { - signature(&hash) - } else { - 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 = derive_tokenizeable_impl(&input); - - // parse attributes abi into source - TokenStream::from(quote! { - #tokenize_impl - #ethevent_impl - }) -} - -struct EventField { - topic_name: Option, - index: usize, - param: EventParam, -} - -impl EventField { - fn is_indexed(&self) -> bool { - self.topic_name.is_some() - } -} - -// Converts param types for indexed parameters to bytes32 where appropriate -// 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 -fn topic_param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream { - let core_crate = ethers_core_crate(); - match kind { - ParamType::String - | ParamType::Bytes - | ParamType::Array(_) - | ParamType::FixedArray(_, _) - | ParamType::Tuple(_) => quote! {#core_crate::abi::ParamType::FixedBytes(32)}, - ty => param_type_quote(ty), - } -} - -fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream { - let core_crate = ethers_core_crate(); - match kind { - ParamType::Address => { - quote! {#core_crate::abi::ParamType::Address} - } - ParamType::Bytes => { - quote! {#core_crate::abi::ParamType::Bytes} - } - ParamType::Int(size) => { - let size = Literal::usize_suffixed(*size); - quote! {#core_crate::abi::ParamType::Int(#size)} - } - ParamType::Uint(size) => { - let size = Literal::usize_suffixed(*size); - quote! {#core_crate::abi::ParamType::Uint(#size)} - } - ParamType::Bool => { - quote! {#core_crate::abi::ParamType::Bool} - } - ParamType::String => { - quote! {#core_crate::abi::ParamType::String} - } - ParamType::Array(ty) => { - let ty = param_type_quote(&*ty); - quote! {#core_crate::abi::ParamType::Array(Box::new(#ty))} - } - ParamType::FixedBytes(size) => { - let size = Literal::usize_suffixed(*size); - quote! {#core_crate::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)} - } - ParamType::Tuple(tuple) => { - let elements = tuple.iter().map(param_type_quote); - quote! { - #core_crate::abi::ParamType::Tuple( - ::std::vec![ - #( #elements ),* - ] - ) - } - } - } -} - -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 = if param.indexed { - if topic_name.is_none() { - Some(param.name.clone()) - } else { - topic_name - } - } else { - None - }; - - 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| 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| 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) - }) -} - -fn derive_abi_event_from_fields(input: &DeriveInput) -> Result { - let fields: Vec<_> = 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 => { - 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 inputs = fields - .iter() - .map(|f| { - let name = f - .ident - .as_ref() - .map(|name| name.to_string()) - .unwrap_or_else(|| "".to_string()); - find_parameter_type(&f.ty).map(|ty| (name, ty)) - }) - .collect::, _>>()?; - - let event = Event { - name: "".to_string(), - inputs: inputs - .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 find_parameter_type(ty: &Type) -> Result { - match ty { - Type::Array(ty) => { - let param = find_parameter_type(ty.elem.as_ref())?; - if let Expr::Lit(ref expr) = ty.len { - if let Lit::Int(ref len) = expr.lit { - if let Ok(size) = len.base10_parse::() { - return Ok(ParamType::FixedArray(Box::new(param), size)); - } - } - } - Err(Error::new( - ty.span(), - "Failed to derive proper ABI from array field", - )) - } - Type::Path(ty) => { - if let Some(ident) = ty.path.get_ident() { - return match ident.to_string().to_lowercase().as_str() { - "address" => Ok(ParamType::Address), - "string" => Ok(ParamType::String), - "bool" => Ok(ParamType::Bool), - "int" | "uint" => Ok(ParamType::Uint(256)), - "h160" => Ok(ParamType::FixedBytes(20)), - "h256" | "secret" | "hash" => Ok(ParamType::FixedBytes(32)), - "h512" | "public" => Ok(ParamType::FixedBytes(64)), - s => parse_int_param_type(s).ok_or_else(|| { - Error::new(ty.span(), "Failed to derive proper ABI from fields") - }), - }; - } - // check for `Vec` - if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" { - 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))); - } - } - } - } - - Err(Error::new( - ty.span(), - "Failed to derive proper ABI from fields", - )) - } - Type::Tuple(ty) => { - let params = ty - .elems - .iter() - .map(|t| find_parameter_type(t)) - .collect::, _>>()?; - Ok(ParamType::Tuple(params)) - } - _ => Err(Error::new( - ty.span(), - "Failed to derive proper ABI from fields", - )), - } -} - -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 - } -} - -fn signature(hash: &[u8]) -> proc_macro2::TokenStream { - let core_crate = ethers_core_crate(); - let bytes = hash.iter().copied().map(Literal::u8_unsuffixed); - quote! {#core_crate::types::H256([#( #bytes ),*])} -} - -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)) -} - -/// Derives the `Tokenizable` trait for the labeled type. -/// -/// This derive macro automatically adds a type bound `field: Tokenizable` for each -/// field type. -#[proc_macro_derive(EthAbiType)] -pub fn derive_abi_type(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - TokenStream::from(derive_tokenizeable_impl(&input)) -} - -fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { - let core_crate = 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 (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 } - }); - 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("tokens size is sufficient qed").into_token())? } - }); - let init_struct_impl = quote! { Self { #(#assignments,)* } }; - - let into_token = fields.named.iter().map(|f| { - let name = f.ident.as_ref().expect("Named fields have names"); - quote_spanned! { f.span() => self.#name.into_token() } - }); - let into_token_impl = quote! { #(#into_token,)* }; - - ( - tokenize_predicates, - fields.named.len(), - init_struct_impl, - into_token_impl, - ) - } - 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 } - }); - 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("tokens size is sufficient qed").into_token())? } - }); - let init_struct_impl = quote! { Self(#(#assignments,)* ) }; - - let into_token = fields.unnamed.iter().enumerate().map(|(i, f)| { - let idx = syn::Index::from(i); - quote_spanned! { f.span() => self.#idx.into_token() } - }); - let into_token_impl = quote! { #(#into_token,)* }; - - ( - tokenize_predicates, - fields.unnamed.len(), - init_struct_impl, - into_token_impl, - ) - } - Fields::Unit => { - return Error::new( - input.span(), - "EthAbiType cannot be derived for empty structs and unit", - ) - .to_compile_error(); - } - }, - Data::Enum(_) => { - return Error::new(input.span(), "EthAbiType cannot be derived for enums") - .to_compile_error(); - } - Data::Union(_) => { - return Error::new(input.span(), "EthAbiType cannot be derived for unions") - .to_compile_error(); - } - }; - - // there might be the case that the event has only 1 params, which is not a tuple - let (from_token_impl, into_token_impl) = match params_len { - 0 => ( - quote! { - Ok(#init_struct_impl) - }, - // can't encode an empty struct - // TODO: panic instead? - quote! { - #core_crate::abi::Token::Tuple(Vec::new()) - }, - ), - _ => { - let from_token = quote! { - if let #core_crate::abi::Token::Tuple(tokens) = token { - if tokens.len() != #params_len { - return Err(#core_crate::abi::InvalidOutputType(::std::format!( - "Expected {} tokens, got {}: {:?}", - #params_len, - tokens.len(), - tokens - ))); - } - - let mut iter = tokens.into_iter(); - - Ok(#init_struct_impl) - } else { - Err(#core_crate::abi::InvalidOutputType(::std::format!( - "Expected Tuple, got {:?}", - token - ))) - } - }; - - let into_token = quote! { - #core_crate::abi::Token::Tuple( - ::std::vec![ - #into_token_impl - ] - ) - }; - (from_token, into_token) - } - }; - - quote! { - impl<#generic_params> #core_crate::abi::Tokenizable for #name<#generic_args> - where - #generic_predicates - #tokenize_predicates - { - - fn from_token(token: #core_crate::abi::Token) -> Result where - Self: Sized { - #from_token_impl - } - - fn into_token(self) -> #core_crate::abi::Token { - #into_token_impl - } - } - - impl<#generic_params> #core_crate::abi::TokenizableItem for #name<#generic_args> - where - #generic_predicates - #tokenize_predicates - { } - } -} - -#[derive(Default)] -struct Attributes { - name: Option<(String, Span)>, - abi: Option<(String, Span)>, - signature_hash: Option<(Vec, Span)>, - anonymous: Option<(bool, Span)>, -} - -fn parse_attributes(input: &DeriveInput) -> Result { - let mut result = Attributes::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) + TokenStream::from(event::derive_eth_event_impl(input)) } diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs new file mode 100644 index 000000000..12bbf770b --- /dev/null +++ b/ethers-contract/ethers-contract-derive/src/utils.rs @@ -0,0 +1,159 @@ +use ethers_contract_abigen::ethers_core_crate; +use ethers_core::abi::ParamType; +use proc_macro2::Literal; +use quote::quote; +use syn::spanned::Spanned as _; +use syn::{parse::Error, Expr, GenericArgument, Lit, PathArguments, Type}; + +pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream { + let core_crate = ethers_core_crate(); + let bytes = hash.iter().copied().map(Literal::u8_unsuffixed); + quote! {#core_crate::types::H256([#( #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 + } +} + +// Converts param types for indexed parameters to bytes32 where appropriate +// 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(); + match kind { + ParamType::String + | ParamType::Bytes + | ParamType::Array(_) + | ParamType::FixedArray(_, _) + | ParamType::Tuple(_) => quote! {#core_crate::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(); + match kind { + ParamType::Address => { + quote! {#core_crate::abi::ParamType::Address} + } + ParamType::Bytes => { + quote! {#core_crate::abi::ParamType::Bytes} + } + ParamType::Int(size) => { + let size = Literal::usize_suffixed(*size); + quote! {#core_crate::abi::ParamType::Int(#size)} + } + ParamType::Uint(size) => { + let size = Literal::usize_suffixed(*size); + quote! {#core_crate::abi::ParamType::Uint(#size)} + } + ParamType::Bool => { + quote! {#core_crate::abi::ParamType::Bool} + } + ParamType::String => { + quote! {#core_crate::abi::ParamType::String} + } + ParamType::Array(ty) => { + let ty = param_type_quote(&*ty); + quote! {#core_crate::abi::ParamType::Array(Box::new(#ty))} + } + ParamType::FixedBytes(size) => { + let size = Literal::usize_suffixed(*size); + quote! {#core_crate::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)} + } + ParamType::Tuple(tuple) => { + let elements = tuple.iter().map(param_type_quote); + quote! { + #core_crate::abi::ParamType::Tuple( + ::std::vec![ + #( #elements ),* + ] + ) + } + } + } +} + +/// Tries to find the corresponding `ParamType` for the given type +pub fn find_parameter_type(ty: &Type) -> Result { + match ty { + Type::Array(ty) => { + let param = find_parameter_type(ty.elem.as_ref())?; + if let Expr::Lit(ref expr) = ty.len { + if let Lit::Int(ref len) = expr.lit { + if let Ok(size) = len.base10_parse::() { + return Ok(ParamType::FixedArray(Box::new(param), size)); + } + } + } + Err(Error::new( + ty.span(), + "Failed to derive proper ABI from array field", + )) + } + Type::Path(ty) => { + if let Some(ident) = ty.path.get_ident() { + return match ident.to_string().to_lowercase().as_str() { + "address" => Ok(ParamType::Address), + "string" => Ok(ParamType::String), + "bool" => Ok(ParamType::Bool), + "int" | "uint" => Ok(ParamType::Uint(256)), + "h160" => Ok(ParamType::FixedBytes(20)), + "h256" | "secret" | "hash" => Ok(ParamType::FixedBytes(32)), + "h512" | "public" => Ok(ParamType::FixedBytes(64)), + s => parse_int_param_type(s).ok_or_else(|| { + Error::new(ty.span(), "Failed to derive proper ABI from fields") + }), + }; + } + // check for `Vec` + if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" { + 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))); + } + } + } + } + + 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)) + } + _ => Err(Error::new( + ty.span(), + "Failed to derive proper ABI from fields", + )), + } +} From e4bc79ccdf04e32b037c98276096c7fee081b655 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 16 Oct 2021 11:18:41 +0200 Subject: [PATCH 2/9] feat: add display derive macro --- .../ethers-contract-derive/src/display.rs | 54 +++++++++++++++++++ .../ethers-contract-derive/src/lib.rs | 14 ++++- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 ethers-contract/ethers-contract-derive/src/display.rs diff --git a/ethers-contract/ethers-contract-derive/src/display.rs b/ethers-contract/ethers-contract-derive/src/display.rs new file mode 100644 index 000000000..c422b7c37 --- /dev/null +++ b/ethers-contract/ethers-contract-derive/src/display.rs @@ -0,0 +1,54 @@ +//! Helper functions for deriving `Display` + +use proc_macro2::TokenStream; +use quote::quote; +use syn::spanned::Spanned as _; +use syn::{parse::Error, Data, DeriveInput, Fields}; +use crate::utils; + +/// Derive `fmt::Display` for the given type +pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result { + let fields: Vec<_> = 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![] + } + }, + Data::Enum(_) => { + return Err(Error::new(input.span(), "Enum types are not supported by EthDisplay")) + } + Data::Union(_) => { + return Err(Error::new(input.span(), "Union types are not supported by EthDisplay")) + } + }; + + let mut prints = Vec::with_capacity(fields.len()); + + for field in fields { + let param = utils::find_parameter_type(&field.ty) ?; + + + } + + let name = &input.ident; + + Ok(quote! { + impl ::std::fmt::Display for #name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + + Ok(()) + } + } + }) +} + +struct X; +impl ::std::fmt::Display for X { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + let x: Vec = vec![]; + let x = hex::encode(&x); + write!(f, "0x{:?}", x) + } +} diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs index 37a1010c8..8f94c8f5e 100644 --- a/ethers-contract/ethers-contract-derive/src/lib.rs +++ b/ethers-contract/ethers-contract-derive/src/lib.rs @@ -1,6 +1,6 @@ //! Implementation of procedural macro for generating type-safe bindings to an //! ethereum smart contract. -#![deny(missing_docs, unsafe_code, unused)] +#![deny(missing_docs, unsafe_code)] use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; @@ -9,6 +9,7 @@ use abigen::Contracts; pub(crate) mod abi_ty; mod abigen; +mod display; mod event; mod spanned; pub(crate) mod utils; @@ -101,6 +102,17 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream { TokenStream::from(abi_ty::derive_tokenizeable_impl(&input)) } +/// Derives the `fmt::Display` trait using convenient formatters for the underlying primitive types/tokens. +#[proc_macro_derive(EthDisplay, attributes(ethdisplay))] +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() + } + +} + /// Derives the `EthEvent` and `Tokenizeable` trait for the labeled type. /// /// Additional arguments can be specified using the `#[ethevent(...)]` From b8fb5ecc81b61ab572dd0dac3e1a1d5a30361109 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 16 Oct 2021 14:45:22 +0200 Subject: [PATCH 3/9] chore: reexport hex --- ethers-core/src/utils/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 0d69d8d2d..9197228f3 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -33,6 +33,9 @@ pub use units::Units; /// Re-export RLP pub use rlp; +/// Re-export hex +pub use hex; + use crate::types::{Address, Bytes, U256}; use k256::{ecdsa::SigningKey, EncodedPoint as K256PublicKey}; use std::ops::Neg; From 562a095f06eaa3f92a2d6068119736572c299615 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 16 Oct 2021 14:45:38 +0200 Subject: [PATCH 4/9] feat: add EthDisplay --- .../ethers-contract-derive/src/display.rs | 96 +++++++++++++++---- .../ethers-contract-derive/src/lib.rs | 28 +++++- .../ethers-contract-derive/src/utils.rs | 3 +- ethers-contract/src/lib.rs | 2 +- 4 files changed, 102 insertions(+), 27 deletions(-) diff --git a/ethers-contract/ethers-contract-derive/src/display.rs b/ethers-contract/ethers-contract-derive/src/display.rs index c422b7c37..c6215bb78 100644 --- a/ethers-contract/ethers-contract-derive/src/display.rs +++ b/ethers-contract/ethers-contract-derive/src/display.rs @@ -1,9 +1,13 @@ //! Helper functions for deriving `Display` use proc_macro2::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::spanned::Spanned as _; use syn::{parse::Error, Data, DeriveInput, Fields}; + +use ethers_contract_abigen::ethers_core_crate; +use ethers_core::abi::ParamType; + use crate::utils; /// Derive `fmt::Display` for the given type @@ -17,38 +21,88 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result { - return Err(Error::new(input.span(), "Enum types are not supported by EthDisplay")) + return Err(Error::new( + input.span(), + "Enum types are not supported by EthDisplay", + )) } Data::Union(_) => { - return Err(Error::new(input.span(), "Union types are not supported by EthDisplay")) + 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 mut fmts = TokenStream::new(); + for (idx, field) in fields.iter().enumerate() { + let ident = field + .ident + .clone() + .unwrap_or_else(|| format_ident!("{}", idx)); + let tokens = if let Ok(param) = utils::find_parameter_type(&field.ty) { + match param { + ParamType::Address | ParamType::Uint(_) | ParamType::Int(_) => { + quote! { + write!(f, "{:?}", self.#ident)?; + } + } + ParamType::Bytes => { + quote! { + write!(f, "0x{}", #hex_encode(self.#ident))?; + } + } + ParamType::Bool | ParamType::String | ParamType::Tuple(_) => { + quote! { + self.#ident.fmt(f)?; + } + } - let mut prints = Vec::with_capacity(fields.len()); - - for field in fields { - let param = utils::find_parameter_type(&field.ty) ?; - - + ParamType::Array(ty) | ParamType::FixedArray(ty, _) => { + if *ty == ParamType::Uint(8) { + // `u8` + quote! { + write!(f, "0x{}", #hex_encode(&self.#ident[..]))?; + } + } else { + // format as array with `[arr[0].display, arr[1].display,...]` + quote! { + write!(f, "[")?; + for (idx, val) in self.#ident.iter().enumerate() { + write!(f, "{}", val)?; + if idx < self.#ident.len() - 1 { + write!(f, ", ")?; + } + } + write!(f, "]")?; + } + } + } + ParamType::FixedBytes(_) => { + quote! { + write!(f, "0x{}", #hex_encode(&self.#ident))?; + } + } + } + } else { + // could not detect the parameter type and rely on delegating `fmt` instead + quote! { + self.#ident.fmt(f) + } + }; + fmts.extend(tokens); + if idx < fields.len() - 1 { + fmts.extend(quote! { write!(f, ", ")?;}); + } } - let name = &input.ident; - Ok(quote! { impl ::std::fmt::Display for #name { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - + #fmts Ok(()) } } }) } - -struct X; -impl ::std::fmt::Display for X { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - let x: Vec = vec![]; - let x = hex::encode(&x); - write!(f, "0x{:?}", x) - } -} diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs index 8f94c8f5e..bf36f3928 100644 --- a/ethers-contract/ethers-contract-derive/src/lib.rs +++ b/ethers-contract/ethers-contract-derive/src/lib.rs @@ -102,15 +102,35 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream { TokenStream::from(abi_ty::derive_tokenizeable_impl(&input)) } -/// Derives the `fmt::Display` trait using convenient formatters for the underlying primitive types/tokens. +/// Derives `fmt::Display` trait and generates a convenient format for all the +/// underlying primitive types/tokens. +/// +/// The fields of the structure are formatted comma separated, like `self.0, +/// self.1, self.2,...` +/// +/// # Example +/// +/// ```ignore +/// #[derive(Debug, Clone, EthAbiType, EthDisplay)] +/// struct MyStruct { +/// addr: Address, +/// old_value: String, +/// new_value: String, +/// h: H256, +/// arr_u8: [u8; 32], +/// arr_u16: [u16; 32], +/// v: Vec, +/// } +/// let val = MyStruct {..}; +/// format!("{}", val); +/// ``` #[proc_macro_derive(EthDisplay, attributes(ethdisplay))] 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) => TokenStream::from(tokens), + Err(err) => err.to_compile_error().into(), } - } /// Derives the `EthEvent` and `Tokenizeable` trait for the labeled type. diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs index 12bbf770b..29e4d8496 100644 --- a/ethers-contract/ethers-contract-derive/src/utils.rs +++ b/ethers-contract/ethers-contract-derive/src/utils.rs @@ -94,7 +94,8 @@ pub fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream { } } -/// Tries to find the corresponding `ParamType` for the given type +/// Tries to find the corresponding `ParamType` used for tokenization for the +/// given type pub fn find_parameter_type(ty: &Type) -> Result { match ty { Type::Array(ty) => { diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs index 2a2858166..db3d2a222 100644 --- a/ethers-contract/src/lib.rs +++ b/ethers-contract/src/lib.rs @@ -50,7 +50,7 @@ pub use ethers_contract_abigen::Abigen; #[cfg(any(test, feature = "abigen"))] #[cfg_attr(docsrs, doc(cfg(feature = "abigen")))] -pub use ethers_contract_derive::{abigen, EthAbiType, EthEvent}; +pub use ethers_contract_derive::{abigen, EthAbiType, EthDisplay, EthEvent}; // Hide the Lazy re-export, it's just for convenience #[doc(hidden)] From bbf2eaab3c75c9a161034fd6b5b6521d589dd85e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 16 Oct 2021 14:51:27 +0200 Subject: [PATCH 5/9] test: add display test --- ethers-contract/tests/common/derive.rs | 38 +++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/ethers-contract/tests/common/derive.rs b/ethers-contract/tests/common/derive.rs index be1d87dfc..7c05eacba 100644 --- a/ethers-contract/tests/common/derive.rs +++ b/ethers-contract/tests/common/derive.rs @@ -1,5 +1,5 @@ use ethers_contract::EthLogDecode; -use ethers_contract::{abigen, EthAbiType, EthEvent}; +use ethers_contract::{abigen, EthAbiType, EthDisplay, EthEvent}; use ethers_core::abi::{RawLog, Tokenizable}; use ethers_core::types::Address; use ethers_core::types::{H160, H256, I256, U128, U256}; @@ -335,3 +335,39 @@ fn can_decode_event_with_no_params() { let _ = ::decode_log(&log).unwrap(); } + +#[test] +fn eth_display_works() { + #[derive(Debug, Clone, EthAbiType, EthDisplay)] + struct MyStruct { + addr: Address, + old_value: String, + new_value: String, + h: H256, + i: I256, + arr_u8: [u8; 32], + arr_u16: [u16; 32], + v: Vec, + } + let item = MyStruct { + addr: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse().unwrap(), + old_value: "50".to_string(), + new_value: "100".to_string(), + h: H256::random(), + i: I256::zero(), + arr_u8: [0; 32], + arr_u16: [1; 32], + v: vec![0; 32], + }; + + let val = format!( + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, 50, 100, 0x{}, {}, 0x{}, {:?}, 0x{}", + hex::encode(&item.h), + item.i, + hex::encode(&item.arr_u8), + item.arr_u16, + hex::encode(&item.v), + ); + + assert_eq!(val, format!("{}", item)); +} From 4d7f770dc2a513fa36f32ebada8be4131c68a2ad Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 16 Oct 2021 14:55:40 +0200 Subject: [PATCH 6/9] fix: use ? op --- ethers-contract/ethers-contract-derive/src/display.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-contract/ethers-contract-derive/src/display.rs b/ethers-contract/ethers-contract-derive/src/display.rs index c6215bb78..32a77b3bc 100644 --- a/ethers-contract/ethers-contract-derive/src/display.rs +++ b/ethers-contract/ethers-contract-derive/src/display.rs @@ -88,7 +88,7 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result Date: Sat, 16 Oct 2021 15:13:18 +0200 Subject: [PATCH 7/9] feat: derive EthDisplay in abigen --- .../ethers-contract-abigen/src/contract/events.rs | 6 +++--- .../ethers-contract-derive/src/display.rs | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs index f976928ae..629082084 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs @@ -150,7 +150,6 @@ impl Context { /// we can replace it fn expand_input_type(&self, input: &EventParam) -> Result { let ethers_core = util::ethers_core_crate(); - Ok(match (&input.kind, input.indexed) { (ParamType::Array(ty), true) => { if let ParamType::Tuple(..) = **ty { @@ -184,7 +183,8 @@ impl Context { quote! { #ethers_core::types::H256 } } (ParamType::Tuple(..), true) => { - // represents an struct + eprintln!("TUPLE {:?}", input); + // represents a struct if let Some(ty) = self .abi_parser .structs @@ -269,7 +269,7 @@ impl Context { let ethers_contract = util::ethers_contract_crate(); Ok(quote! { - #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthEvent, #derives)] + #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)] #[ethevent( name = #event_abi_name, abi = #abi_signature )] pub #data_type_definition }) diff --git a/ethers-contract/ethers-contract-derive/src/display.rs b/ethers-contract/ethers-contract-derive/src/display.rs index 32a77b3bc..23ea5516e 100644 --- a/ethers-contract/ethers-contract-derive/src/display.rs +++ b/ethers-contract/ethers-contract-derive/src/display.rs @@ -53,12 +53,16 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result { + ParamType::Bool | ParamType::String => { quote! { self.#ident.fmt(f)?; } } - + ParamType::Tuple(_) => { + quote! { + write!(f, "{:?}", &self.#ident)?; + } + } ParamType::Array(ty) | ParamType::FixedArray(ty, _) => { if *ty == ParamType::Uint(8) { // `u8` @@ -86,9 +90,9 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result Date: Sat, 16 Oct 2021 15:15:16 +0200 Subject: [PATCH 8/9] feat: derive display for event enum --- .../ethers-contract-abigen/src/contract/events.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs index 629082084..06492e749 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs @@ -105,6 +105,16 @@ impl Context { Err(#ethers_core::abi::Error::InvalidData) } } + + impl ::std::fmt::Display for #enum_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match self { + #( + #enum_name::#variants(element) => element.fmt(f) + ),* + } + } + } } } @@ -183,7 +193,6 @@ impl Context { quote! { #ethers_core::types::H256 } } (ParamType::Tuple(..), true) => { - eprintln!("TUPLE {:?}", input); // represents a struct if let Some(ty) = self .abi_parser From 003d47200be59bf97e0d33872612ac2df425152d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 16 Oct 2021 15:18:47 +0200 Subject: [PATCH 9/9] chore: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed9894a6..696e6d381 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Unreleased +- `abigen!` now generates `Display` for all events using the new `EthDisplay` macro [#513](https://github.com/gakonst/ethers-rs/pull/513) - `abigen!` now supports overloaded functions natively [#501](https://github.com/gakonst/ethers-rs/pull/501) - `abigen!` now supports multiple contracts [#498](https://github.com/gakonst/ethers-rs/pull/498) - Use rust types as contract function inputs for human readable abi [#482](https://github.com/gakonst/ethers-rs/pull/482)