From 85f6710471a11cff43178850f2ed96e2a69b7ae8 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 18 Mar 2023 19:44:43 +0100 Subject: [PATCH] chore: update ethers-contract-derive to use syn 2 --- .../ethers-contract-derive/src/abigen.rs | 9 +- .../ethers-contract-derive/src/call.rs | 8 +- .../ethers-contract-derive/src/calllike.rs | 111 +++------ .../ethers-contract-derive/src/error.rs | 7 +- .../ethers-contract-derive/src/event.rs | 220 +++--------------- .../ethers-contract-derive/src/utils.rs | 37 ++- 6 files changed, 113 insertions(+), 279 deletions(-) diff --git a/ethers-contract/ethers-contract-derive/src/abigen.rs b/ethers-contract/ethers-contract-derive/src/abigen.rs index 18cb471c..a0106321 100644 --- a/ethers-contract/ethers-contract-derive/src/abigen.rs +++ b/ethers-contract/ethers-contract-derive/src/abigen.rs @@ -40,8 +40,7 @@ impl Contracts { impl Parse for Contracts { fn parse(input: ParseStream) -> Result { - let inner = - input.parse_terminated::<_, Token![;]>(ContractArgs::parse)?.into_iter().collect(); + let inner = input.parse_terminated(ContractArgs::parse, Token![;])?.into_iter().collect(); Ok(Self { inner }) } } @@ -122,7 +121,7 @@ impl Parse for Parameter { "methods" => { let content; braced!(content in input); - let parsed = content.parse_terminated::<_, Token![;]>(Spanned::::parse)?; + let parsed = content.parse_terminated(Spanned::::parse, Token![;])?; let mut methods = Vec::with_capacity(parsed.len()); let mut signatures = HashSet::new(); @@ -141,7 +140,7 @@ impl Parse for Parameter { "derives" | "event_derives" => { let content; parenthesized!(content in input); - let derives = content.parse_terminated::<_, Token![,]>(Path::parse)?; + let derives = content.parse_terminated(Path::parse, Token![,])?; Ok(Parameter::Derives(derives)) } _ => Err(Error::new(name.span(), "unexpected named parameter")), @@ -168,7 +167,7 @@ impl Parse for Method { // function params let content; parenthesized!(content in input); - let params = content.parse_terminated::<_, Token![,]>(Ident::parse)?; + let params = content.parse_terminated(Ident::parse, Token![,])?; let last_i = params.len().saturating_sub(1); signature.push('('); diff --git a/ethers-contract/ethers-contract-derive/src/call.rs b/ethers-contract/ethers-contract-derive/src/call.rs index f0ff15a0..fb941c59 100644 --- a/ethers-contract/ethers-contract-derive/src/call.rs +++ b/ethers-contract/ethers-contract-derive/src/call.rs @@ -11,12 +11,10 @@ use syn::{parse::Error, DeriveInput}; /// Generates the `ethcall` trait support pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> Result { - let attributes = parse_calllike_attributes(&input, "ethcall")?; + let attributes = parse_calllike_attributes!(input, "ethcall"); - let function_call_name = - attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string()); - - let mut function = if let Some((abi, span)) = attributes.abi { + let function_call_name = attributes.name(&input.ident); + let mut function = if let Some((abi, span)) = attributes.abi() { let sig = abi.trim_start_matches("function ").trim_start(); // try to parse as solidity function match HumanReadableParser::parse_function(&abi) { diff --git a/ethers-contract/ethers-contract-derive/src/calllike.rs b/ethers-contract/ethers-contract-derive/src/calllike.rs index 6cac69e3..ef567cc7 100644 --- a/ethers-contract/ethers-contract-derive/src/calllike.rs +++ b/ethers-contract/ethers-contract-derive/src/calllike.rs @@ -7,97 +7,50 @@ use ethers_core::{ }; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{parse::Error, spanned::Spanned, AttrStyle, DeriveInput, Lit, Meta, NestedMeta}; +use syn::{DeriveInput, LitStr, Result}; /// All the attributes the `EthCall`/`EthError` macro supports #[derive(Default)] pub struct EthCalllikeAttributes { - pub name: Option<(String, Span)>, - pub abi: Option<(String, Span)>, + pub name: Option, + pub abi: Option, } -/// extracts the attributes from the struct annotated with the given attribute -pub fn parse_calllike_attributes( - input: &DeriveInput, - attr_name: &str, -) -> Result { - let mut result = EthCalllikeAttributes::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(attr_name) { - for n in meta.nested.iter() { - if let NestedMeta::Meta(meta) = n { - match meta { - Meta::Path(path) => { - return Err(Error::new( - path.span(), - format!("unrecognized {attr_name} parameter"), - )) - } - Meta::List(meta) => { - return Err(Error::new( - meta.path.span(), - format!("unrecognized {attr_name} parameter"), - )) - } - Meta::NameValue(meta) => { - 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", - )) - } - } else { - return Err(Error::new( - meta.span(), - "name must be a string", - )) - } - } 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", - )) - } - } else { - return Err(Error::new( - meta.span(), - "abi must be a string", - )) - } - } else { - return Err(Error::new( - meta.span(), - format!("unrecognized {attr_name} parameter"), - )) - } - } - } - } - } - } - } - } +impl EthCalllikeAttributes { + pub fn name(&self, fallback: &Ident) -> String { + self.name.as_ref().map(|s| s.value()).unwrap_or_else(|| fallback.to_string()) + } + + pub fn abi(&self) -> Option<(String, Span)> { + self.abi.as_ref().map(|s| (s.value(), s.span())) } - Ok(result) } +macro_rules! parse_calllike_attributes { + ($input:ident, $attr_ident:literal) => {{ + let mut result = EthCalllikeAttributes::default(); + $crate::utils::parse_attributes!($input.attrs.iter(), $attr_ident, meta, + "name", result.name => { + meta.input.parse::<::syn::Token![=]>()?; + let litstr: ::syn::LitStr = meta.input.parse()?; + result.name = Some(litstr); + } + "abi", result.abi => { + meta.input.parse::<::syn::Token![=]>()?; + let litstr: ::syn::LitStr = meta.input.parse()?; + result.abi = Some(litstr); + } + ); + result + }}; +} +pub(crate) use parse_calllike_attributes; + /// Generates the decode implementation based on the type's runtime `AbiType` impl pub fn derive_decode_impl_with_abi_type( input: &DeriveInput, trait_ident: Ident, -) -> Result { +) -> Result { let datatypes_array = utils::abi_parameters_array(input, &trait_ident.to_string())?; Ok(derive_decode_impl(datatypes_array, trait_ident)) } @@ -130,7 +83,7 @@ pub fn derive_codec_impls( input: &DeriveInput, decode_impl: TokenStream, trait_ident: Ident, -) -> Result { +) -> Result { // the ethers crates to use let ethers_core = ethers_core_crate(); let ethers_contract = ethers_contract_crate(); diff --git a/ethers-contract/ethers-contract-derive/src/error.rs b/ethers-contract/ethers-contract-derive/src/error.rs index b6f988ff..832b64e4 100644 --- a/ethers-contract/ethers-contract-derive/src/error.rs +++ b/ethers-contract/ethers-contract-derive/src/error.rs @@ -11,11 +11,10 @@ use syn::{parse::Error, DeriveInput}; /// Generates the `EthError` trait support pub(crate) fn derive_eth_error_impl(input: DeriveInput) -> Result { - let attributes = parse_calllike_attributes(&input, "etherror")?; + let attributes = parse_calllike_attributes!(input, "etherror"); - let error_name = attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string()); - - let mut error = if let Some((src, span)) = attributes.abi { + let error_name = attributes.name(&input.ident); + let mut error = if let Some((src, span)) = attributes.abi() { let raw_function_sig = src.trim_start_matches("error ").trim_start(); // try to parse as solidity error match HumanReadableParser::parse_error(&src) { diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs index f7876e65..40728a5c 100644 --- a/ethers-contract/ethers-contract-derive/src/event.rs +++ b/ethers-contract/ethers-contract-derive/src/event.rs @@ -6,16 +6,12 @@ use ethers_core::{ abi::{Event, EventExt, EventParam, HumanReadableParser}, macros::{ethers_contract_crate, ethers_core_crate}, }; -use hex::FromHex; use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{ - parse::Error, spanned::Spanned, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta, - NestedMeta, -}; +use syn::{spanned::Spanned, Data, DeriveInput, Error, Field, Fields, LitStr, Result, Token}; /// Generates the `EthEvent` trait support -pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result { +pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result { let name = &input.ident; let attributes = parse_event_attributes(&input)?; @@ -64,7 +60,7 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result Result { +fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result { let ethers_core = ethers_core_crate(); let fields: Vec<_> = match input.data { @@ -291,7 +287,7 @@ fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result Result { +fn derive_abi_event_from_fields(input: &DeriveInput) -> Result { let event = Event { name: input.ident.to_string(), inputs: utils::derive_abi_inputs_from_fields(input, "EthEvent")? @@ -303,53 +299,18 @@ fn derive_abi_event_from_fields(input: &DeriveInput) -> Result { 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", - )) - } - } - } - } - } - } - } - } +fn parse_field_attributes(field: &Field) -> Result<(Option, bool)> { + let mut indexed = None::; + let mut topic_name = None::; + utils::parse_attributes!(field.attrs.iter(), "ethevent", meta, + "indexed", indexed => { indexed = Some(true) } + "name", topic_name => { + meta.input.parse::()?; + let litstr: LitStr = meta.input.parse()?; + topic_name = Some(litstr.value()); } - } - - Ok((topic_name, indexed)) + ); + Ok((topic_name, indexed.unwrap_or_default())) } /// All the attributes the `EthEvent` macro supports @@ -357,139 +318,32 @@ fn parse_field_attributes(field: &Field) -> Result<(Option, bool), Error struct EthEventAttributes { name: Option<(String, Span)>, abi: Option<(String, Span)>, - signature_hash: Option<(Vec, Span)>, + signature: Option<(Vec, Span)>, anonymous: Option<(bool, Span)>, } /// extracts the attributes from the struct annotated with `EthEvent` -fn parse_event_attributes(input: &DeriveInput) -> Result { +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", - )) - } - } - } - 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("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", - )) - } - } else { - return Err(Error::new( - meta.span(), - "name must be a string", - )) - } - } 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", - )) - } - } else { - return Err(Error::new( - meta.span(), - "name must be a string", - )) - } - } 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", - )) - } - } else { - return Err(Error::new( - meta.span(), - "abi must be a string", - )) - } - } 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:?}" - ), - )) - } - } - } else { - return Err(Error::new( - meta.span(), - "signature already specified", - )) - } - } else { - return Err(Error::new( - meta.span(), - "signature must be a hex string", - )) - } - } else { - return Err(Error::new( - meta.span(), - "unrecognized ethevent parameter", - )) - } - } - } - } - } - } - } + utils::parse_attributes!(input.attrs.iter(), "ethevent", meta, + "name", result.name => { + meta.input.parse::()?; + let litstr: LitStr = meta.input.parse()?; + result.name = Some((litstr.value(), litstr.span())); } - } + "abi", result.abi => { + meta.input.parse::()?; + let litstr: LitStr = meta.input.parse()?; + result.abi = Some((litstr.value(), litstr.span())); + } + "signature", result.signature => { + meta.input.parse::()?; + let litstr: LitStr = meta.input.parse()?; + let s = litstr.value(); + let b = hex::decode(s.strip_prefix("0x").unwrap_or(&s)).map_err(|e| meta.error(e))?; + result.signature = Some((b, litstr.span())); + } + "anonymous", result.anonymous => { result.anonymous = Some((true, meta.path.span())); } + ); Ok(result) } diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs index 33bb2a40..309a6d4b 100644 --- a/ethers-contract/ethers-contract-derive/src/utils.rs +++ b/ethers-contract/ethers-contract-derive/src/utils.rs @@ -6,6 +6,35 @@ use syn::{ PathArguments, Type, }; +/// Parses the specified attributes from a `syn::Attribute` iterator. +macro_rules! parse_attributes { + ($attrs:expr, $attr_ident:literal, $meta:ident, $($field:pat, $opt:expr => $block:block)*) => { + const ERROR: &str = concat!("unrecognized ", $attr_ident, " attribute"); + const ALREADY_SPECIFIED: &str = concat!($attr_ident, " attribute already specified"); + + for attr in $attrs { + if !attr.path().is_ident($attr_ident) { + continue; + } + + attr.parse_nested_meta(|$meta| { + let ident = $meta.path.get_ident().ok_or_else(|| $meta.error(ERROR))?.to_string(); + match ident.as_str() { + $( + $field if $opt.is_none() => $block, + $field => return Err($meta.error(ALREADY_SPECIFIED)), + )* + + _ => return Err($meta.error(ERROR)), + } + + Ok(()) + })?; + } + }; +} +pub(crate) use parse_attributes; + pub fn ident(name: &str) -> Ident { Ident::new(name, Span::call_site()) } @@ -104,6 +133,8 @@ pub fn param_type_quote(kind: &ParamType) -> TokenStream { /// Tries to find the corresponding `ParamType` used for tokenization for the /// given type pub fn find_parameter_type(ty: &Type) -> Result { + const ERROR: &str = "Failed to derive proper ABI from array field"; + match ty { Type::Array(arr) => { let ty = find_parameter_type(&arr.elem)?; @@ -114,7 +145,7 @@ pub fn find_parameter_type(ty: &Type) -> Result { } } } - Err(Error::new(arr.span(), "Failed to derive proper ABI from array field")) + Err(Error::new(arr.span(), ERROR)) } Type::Path(ty) => { @@ -151,7 +182,7 @@ pub fn find_parameter_type(ty: &Type) -> Result { s => parse_param_type(s), } }) - .ok_or_else(|| Error::new(ty.span(), "Failed to derive proper ABI from fields")) + .ok_or_else(|| Error::new(ty.span(), ERROR)) } Type::Tuple(ty) => ty @@ -161,7 +192,7 @@ pub fn find_parameter_type(ty: &Type) -> Result { .collect::, _>>() .map(ParamType::Tuple), - _ => Err(Error::new(ty.span(), "Failed to derive proper ABI from fields")), + _ => Err(Error::new(ty.span(), ERROR)), } }