From 4608ddd9fa914af0944ba81e95b8ed189180af08 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 16 Oct 2021 10:19:42 +0200 Subject: [PATCH] chore: move proc macro implementation to separate modules (#510) --- .../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 00000000..69818dad --- /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 00000000..78b8b5a8 --- /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 c0ba36d2..37a1010c 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 00000000..12bbf770 --- /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", + )), + } +}