ethers-rs/ethers-contract/ethers-contract-derive/src/lib.rs

1022 lines
38 KiB
Rust

//! Implementation of procedural macro for generating type-safe bindings to an
//! ethereum smart contract.
#![deny(missing_docs, unsafe_code)]
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 abigen::{expand, ContractArgs};
use ethers_core::abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType};
use hex::FromHex;
use spanned::Spanned;
mod abigen;
mod spanned;
/// Proc macro to generate type-safe bindings to a contract. This macro accepts
/// an Ethereum contract ABI or a path. Note that this path is rooted in
/// the crate's root `CARGO_MANIFEST_DIR`.
///
/// # Examples
///
/// ```ignore
/// # use ethers_contract_derive::abigen;
/// // ABI Path
/// abigen!(MyContract, "MyContract.json");
///
/// // HTTP(S) source
/// abigen!(MyContract, "https://my.domain.local/path/to/contract.json");
///
/// // Etherscan.io
/// abigen!(MyContract, "etherscan:0x0001020304050607080910111213141516171819");
/// abigen!(MyContract, "https://etherscan.io/address/0x0001020304050607080910111213141516171819");
///
/// // npmjs
/// abigen!(MyContract, "npm:@org/package@1.0.0/path/to/contract.json");
/// ```
///
/// Note that Etherscan rate-limits requests to their API, to avoid this an
/// `ETHERSCAN_API_KEY` environment variable can be set. If it is, it will use
/// that API key when retrieving the contract ABI.
///
/// Currently the proc macro accepts additional parameters to configure some
/// aspects of the code generation. Specifically it accepts:
/// - `methods`: A list of mappings from method signatures to method names
/// allowing methods names to be explicitely set for contract methods. This
/// also provides a workaround for generating code for contracts with multiple
/// methods with the same name.
/// - `event_derives`: A list of additional derives that should be added to
/// contract event structs and enums.
///
/// ```ignore
/// abigen!(
/// MyContract,
/// "path/to/MyContract.json",
/// methods {
/// myMethod(uint256,bool) as my_renamed_method;
/// },
/// event_derives (serde::Deserialize, serde::Serialize),
/// );
/// ```
#[proc_macro]
pub fn abigen(input: TokenStream) -> TokenStream {
let args = parse_macro_input!(input as Spanned<ContractArgs>);
let span = args.span();
expand(args.into_inner())
.unwrap_or_else(|e| Error::new(span, format!("{:?}", e)).to_compile_error())
.into()
}
/// Derives the `EthEvent` and `Tokenizeable` trait for the labeled type.
///
/// Additional arguments can be specified using the `#[ethevent(...)]` attribute:
///
/// For the struct:
///
/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the
/// struct's name.
/// - `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
/// - `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
///
/// # Example
/// ```ignore
/// # use ethers_core::types::Address;
///
/// #[derive(Debug, EthAbiType)]
/// struct Inner {
/// inner: Address,
/// msg: String,
/// }
///
/// #[derive(Debug, EthEvent)]
/// #[ethevent(abi = "ValueChangedEvent((address,string),string)")]
/// struct ValueChangedEvent {
/// #[ethevent(indexed, name = "_target")]
/// target: Address,
/// msg: String,
/// inner: Inner,
/// }
/// ```
#[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::<Source>().and_then(|s| s.get()) {
Ok(abi) => {
// try to derive the signature from the abi from the parsed abi
// TODO(mattsse): this will fail for events that contain other non elementary types in their abi
// because the parser doesn't know how to substitute the types
// this could be mitigated by getting the ABI of each non elementary type at runtime
// and computing the the signature as `static Lazy::...`
match 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<Self, #core_crate::abi::Error> 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<String>,
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<proc_macro2::TokenStream, Error> {
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::<Vec<u8>>();
},
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::<Vec<u8>>();
},
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<Event, Error> {
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::<Result<Vec<_>, _>>()?;
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<String>, 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<ParamType, Error> {
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::<usize>() {
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::<Result<Vec<_>, _>>()?;
Ok(ParamType::Tuple(params))
}
_ => Err(Error::new(
ty.span(),
"Failed to derive proper ABI from fields",
)),
}
}
fn parse_int_param_type(s: &str) -> Option<ParamType> {
let size = s
.chars()
.skip(1)
.collect::<String>()
.parse::<usize>()
.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<Event, String> {
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<Self, #core_crate::abi::InvalidOutputType> 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<u8>, Span)>,
anonymous: Option<(bool, Span)>,
}
fn parse_attributes(input: &DeriveInput) -> Result<Attributes, proc_macro2::TokenStream> {
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)
}