//! Implementation of procedural macro for generating type-safe bindings to an //! ethereum smart contract. #![deny(missing_docs, unsafe_code)] use ethers_contract_abigen::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); 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); 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 ethers_contract::EthEvent for #name { fn name() -> ::std::borrow::Cow<'static, str> { #event_name.into() } fn signature() -> ethers_core::types::H256 { #signature } fn abi_signature() -> ::std::borrow::Cow<'static, str> { #abi.into() } fn decode_log(log: ðers_core::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 { match kind { ParamType::String | ParamType::Bytes | ParamType::Array(_) | ParamType::FixedArray(_, _) | ParamType::Tuple(_) => quote! {ethers_core::abi::ParamType::FixedBytes(32)}, ty => param_type_quote(ty), } } fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream { match kind { ParamType::Address => { quote! {ethers_core::abi::ParamType::Address} } ParamType::Bytes => { quote! {ethers_core::abi::ParamType::Bytes} } ParamType::Int(size) => { let size = Literal::usize_suffixed(*size); quote! {ethers_core::abi::ParamType::Int(#size)} } ParamType::Uint(size) => { let size = Literal::usize_suffixed(*size); quote! {ethers_core::abi::ParamType::Uint(#size)} } ParamType::Bool => { quote! {ethers_core::abi::ParamType::Bool} } ParamType::String => { quote! {ethers_core::abi::ParamType::String} } ParamType::Array(ty) => { let ty = param_type_quote(&*ty); quote! {ethers_core::abi::ParamType::Array(Box::new(#ty))} } ParamType::FixedBytes(size) => { let size = Literal::usize_suffixed(*size); quote! {ethers_core::abi::ParamType::FixedBytes(#size)} } ParamType::FixedArray(ty, size) => { let ty = param_type_quote(&*ty); let size = Literal::usize_suffixed(*size); quote! {ethers_core::abi::ParamType::FixedArray(Box::new(#ty),#size)} } ParamType::Tuple(tuple) => { let elements = tuple.iter().map(param_type_quote); quote! { ethers_core::abi::ParamType::Tuple( ::std::vec![ #( #elements ),* ] ) } } } } fn derive_decode_from_log_impl( input: &DeriveInput, event: &Event, ) -> Result { 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 = ::std::vec![#( #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(ethers_core::abi::Error::InvalidData); } }, ) } else { ( quote! { let event_signature = topics.get(0).ok_or(ethers_core::abi::Error::InvalidData)?; if event_signature != &Self::signature() { return Err(ethers_core::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(ethers_core::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 = ethers_core::abi::decode(&topic_types, &flat_topics)?; #topic_tokens_len_check let data_tokens = ethers_core::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 = ethers_core::abi::decode(&topic_types, &flat_topics)?; #topic_tokens_len_check let mut data_tokens = ethers_core::abi::decode(&data_types, &data)?; let mut tokens = Vec::with_capacity(topics.len() + data_tokens.len()); #( tokens.push(#swap_tokens); )* } }; Ok(quote! { let ethers_core::abi::RawLog {data, topics} = log; #signature_check #topic_types_init #data_types_init #flat_topics_init #tokens_init ethers_core::abi::Detokenize::from_tokens(tokens).map_err(|_|ethers_core::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 bytes = hash.iter().copied().map(Literal::u8_unsuffixed); quote! {ethers_core::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 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: ethers_core::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: ethers_core::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: ethers_core::abi::Tokenize } }); let tokenize_predicates = quote! { #(#tokenize_predicates,)* }; let assignments = fields.unnamed.iter().map(|f| { quote_spanned! { f.span() => ethers_core::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! { ethers_core::abi::Token::Tuple(Vec::new()) }, ), 1 => { // This is a hacky solution in order to keep the same tokenstream as for tuples let from_token = quote! { let mut iter = Some(token).into_iter(); Ok(#init_struct_impl) }; // This is a hack to get rid of the trailing comma introduced in the macro that concatenates all the fields if let Ok(into_token) = into_token_impl .to_string() .as_str() .trim_end_matches(',') .parse() { (from_token, into_token) } else { return Error::new(input.span(), "Failed to derive Tokenizeable implementation") .to_compile_error(); } } _ => { let from_token = quote! { if let ethers_core::abi::Token::Tuple(tokens) = token { if tokens.len() != #params_len { return Err(ethers_core::abi::InvalidOutputType(::std::format!( "Expected {} tokens, got {}: {:?}", #params_len, tokens.len(), tokens ))); } let mut iter = tokens.into_iter(); Ok(#init_struct_impl) } else { Err(ethers_core::abi::InvalidOutputType(::std::format!( "Expected Tuple, got {:?}", token ))) } }; let into_token = quote! { ethers_core::abi::Token::Tuple( ::std::vec![ #into_token_impl ] ) }; (from_token, into_token) } }; quote! { impl<#generic_params> ethers_core::abi::Tokenizable for #name<#generic_args> where #generic_predicates #tokenize_predicates { fn from_token(token: ethers_core::abi::Token) -> Result where Self: Sized { #from_token_impl } fn into_token(self) -> ethers_core::abi::Token { #into_token_impl } } impl<#generic_params> ethers_core::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) }