From 6336e96995774cee55cf2ad8925452e3d3b8ff91 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 20 Feb 2023 01:53:29 +0100 Subject: [PATCH] refactor(abigen): derives, struct expansion (#2160) * refactor: abigen derives * refactor: struct expansion * refactor: structs 2 * chore: abigen derives and visibility * refactor: abigen docs * refactor: structs 3 * chore: doc * chore: structs conditional default * refactor: method expansion * refactor: final doc * refactor: expansions, add docs, extra From impl * refactor: final struct expansion * feat(types): implement bytes::Bytes static methods * feat: use static Bytes for bytecode * merge artifact * refactor: event input expansion * refactor: common expand params * refactor: method params expansion * refactor: struct fields expansion * refactor: types array expansion * mergings * cleanup * flatten * selector fmt * chore: clippy * refactor: common, imports --- .../ethers-contract-abigen/src/contract.rs | 147 +++++++++-- .../src/contract/common.rs | 156 ------------ .../src/contract/errors.rs | 122 ++++----- .../src/contract/events.rs | 193 ++++++-------- .../src/contract/methods.rs | 236 ++++++++---------- .../src/contract/structs.rs | 110 +++++--- .../src/contract/types.rs | 8 +- .../ethers-contract-abigen/src/lib.rs | 3 + .../src/source/online.rs | 4 + .../ethers-contract-abigen/src/util.rs | 78 +++--- .../ethers-contract-derive/src/abigen.rs | 10 +- .../ethers-contract-derive/src/event.rs | 8 +- 12 files changed, 495 insertions(+), 580 deletions(-) delete mode 100644 ethers-contract/ethers-contract-abigen/src/contract/common.rs diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 051fdb89..a9ca8bf6 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -1,6 +1,5 @@ //! Contains types to generate Rust bindings for Solidity contracts. -mod common; mod errors; mod events; mod methods; @@ -52,14 +51,19 @@ impl ExpandedContract { abi_structs, errors, } = self; + quote! { - // export all the created data types pub use #module::*; + /// This module was auto-generated with ethers-rs Abigen. + /// More information at: #[allow( + clippy::enum_variant_names, clippy::too_many_arguments, + clippy::upper_case_acronyms, + clippy::type_complexity, + dead_code, non_camel_case_types, - clippy::upper_case_acronyms )] pub mod #module { #imports @@ -103,7 +107,7 @@ pub struct Context { error_aliases: BTreeMap, /// Derives added to event structs and enums. - event_derives: Vec, + extra_derives: Vec, /// Manually specified event aliases. event_aliases: BTreeMap, @@ -122,11 +126,8 @@ impl Context { let name_mod = util::ident(&util::safe_module_name(&self.contract_name)); let abi_name = self.inline_abi_ident(); - // 0. Imports - let imports = common::imports(&name.to_string()); - // 1. Declare Contract struct - let struct_decl = common::struct_declaration(self); + let struct_decl = self.struct_declaration(); // 2. Declare events structs & impl FromTokens for each event let events_decl = self.events_declaration()?; @@ -137,7 +138,7 @@ impl Context { // 4. impl block for the contract methods and their corresponding types let (contract_methods, call_structs) = self.methods_and_call_structs()?; - // 5. generate deploy function if + // 5. The deploy method, only if the contract has a bytecode object let deployment_methods = self.deployment_methods(); // 6. Declare the structs parsed from the human readable abi @@ -154,9 +155,8 @@ impl Context { #struct_decl impl #name { - /// Creates a new contract instance with the specified `ethers` - /// client at the given `Address`. The contract derefs to a `ethers::Contract` - /// object + /// Creates a new contract instance with the specified `ethers` client at + /// `address`. The contract derefs to a `ethers::Contract` object. pub fn new>(address: T, client: ::std::sync::Arc) -> Self { Self(#ethers_contract::Contract::new(address.into(), #abi_name.clone(), client)) } @@ -166,19 +166,18 @@ impl Context { #contract_methods #contract_events - } - impl From<#ethers_contract::Contract> for #name { + impl From<#ethers_contract::Contract> for #name { fn from(contract: #ethers_contract::Contract) -> Self { - Self::new(contract.address(), contract.client()) + Self::new(contract.address(), contract.client()) } } }; Ok(ExpandedContract { module: name_mod, - imports, + imports: quote!(), contract, events: events_decl, errors: errors_decl, @@ -282,12 +281,12 @@ impl Context { ); } - let event_derives = args + let extra_derives = args .derives .iter() .map(|derive| syn::parse_str::(derive)) .collect::, _>>() - .context("failed to parse event derives")?; + .wrap_err("failed to parse event derives")?; Ok(Context { abi, @@ -301,7 +300,7 @@ impl Context { contract_deployed_bytecode, method_aliases, error_aliases: Default::default(), - event_derives, + extra_derives, event_aliases, }) } @@ -335,6 +334,116 @@ impl Context { pub fn internal_structs_mut(&mut self) -> &mut InternalStructs { &mut self.internal_structs } + + /// Expands `self.extra_derives` into a comma separated list to be inserted in a + /// `#[derive(...)]` attribute. + pub(crate) fn expand_extra_derives(&self) -> TokenStream { + let extra_derives = &self.extra_derives; + quote!(#( #extra_derives, )*) + } + + /// Generates the token stream for the contract's ABI, bytecode and struct declarations. + pub(crate) fn struct_declaration(&self) -> TokenStream { + let name = &self.contract_ident; + + let ethers_core = ethers_core_crate(); + let ethers_contract = ethers_contract_crate(); + + let abi = { + let abi_name = self.inline_abi_ident(); + let abi = &self.abi_str; + let (doc_str, parse) = if self.human_readable { + // Human readable: use abi::parse_abi_str + let doc_str = "The parsed human-readable ABI of the contract."; + let parse = quote!(#ethers_core::abi::parse_abi_str(__ABI)); + (doc_str, parse) + } else { + // JSON ABI: use serde_json::from_str + let doc_str = "The parsed JSON ABI of the contract."; + let parse = quote!(#ethers_core::utils::__serde_json::from_str(__ABI)); + (doc_str, parse) + }; + + quote! { + #[rustfmt::skip] + const __ABI: &str = #abi; + + // This never fails as we are parsing the ABI in this macro + #[doc = #doc_str] + pub static #abi_name: #ethers_contract::Lazy<#ethers_core::abi::Abi> = + #ethers_contract::Lazy::new(|| #parse.expect("ABI is always valid")); + } + }; + + let bytecode = self.contract_bytecode.as_ref().map(|bytecode| { + let bytecode = bytecode.iter().copied().map(Literal::u8_unsuffixed); + let bytecode_name = self.inline_bytecode_ident(); + quote! { + #[rustfmt::skip] + const __BYTECODE: &[u8] = &[ #( #bytecode ),* ]; + + #[doc = "The bytecode of the contract."] + pub static #bytecode_name: #ethers_core::types::Bytes = #ethers_core::types::Bytes::from_static(__BYTECODE); + } + }); + + let deployed_bytecode = self.contract_deployed_bytecode.as_ref().map(|bytecode| { + let bytecode = bytecode.iter().copied().map(Literal::u8_unsuffixed); + let bytecode_name = self.inline_deployed_bytecode_ident(); + quote! { + #[rustfmt::skip] + const __DEPLOYED_BYTECODE: &[u8] = &[ #( #bytecode ),* ]; + + #[doc = "The deployed bytecode of the contract."] + pub static #bytecode_name: #ethers_core::types::Bytes = #ethers_core::types::Bytes::from_static(__DEPLOYED_BYTECODE); + } + }); + + quote! { + // The `Lazy` ABI + #abi + + // The static Bytecode, if present + #bytecode + + // The static deployed Bytecode, if present + #deployed_bytecode + + // Struct declaration + pub struct #name(#ethers_contract::Contract); + + // Manual implementation since `M` is stored in `Arc` and does not need to be `Clone` + impl ::core::clone::Clone for #name { + fn clone(&self) -> Self { + Self(::core::clone::Clone::clone(&self.0)) + } + } + + // Deref to the inner contract to have access to all its methods + impl ::core::ops::Deref for #name { + type Target = #ethers_contract::Contract; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl ::core::ops::DerefMut for #name { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + // `(
)` + impl ::core::fmt::Debug for #name { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_tuple(stringify!(#name)) + .field(&self.address()) + .finish() + } + } + } + } } /// Solidity supports overloading as long as the signature of an event, error, function is unique, diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs deleted file mode 100644 index ed777d84..00000000 --- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs +++ /dev/null @@ -1,156 +0,0 @@ -use super::Context; -use ethers_core::macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}; -use proc_macro2::{Ident, Literal, TokenStream}; -use quote::quote; - -pub(crate) fn imports(name: &str) -> TokenStream { - let doc_str = format!("{name} was auto-generated with ethers-rs Abigen. More information at: "); - - let ethers_core = ethers_core_crate(); - let ethers_providers = ethers_providers_crate(); - let ethers_contract = ethers_contract_crate(); - - quote! { - #![allow(clippy::enum_variant_names)] - #![allow(dead_code)] - #![allow(clippy::type_complexity)] - #![allow(unused_imports)] - #![doc = #doc_str] - - use std::sync::Arc; - use #ethers_core::{ - abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable}, - types::*, // import all the types so that we can codegen for everything - }; - use #ethers_contract::{Contract, builders::{ContractCall, Event}, Lazy}; - use #ethers_providers::Middleware; - } -} - -/// Generates the token stream for the contract's ABI, bytecode and struct declarations. -pub(crate) fn struct_declaration(cx: &Context) -> TokenStream { - let name = &cx.contract_ident; - - let ethers_core = ethers_core_crate(); - let ethers_contract = ethers_contract_crate(); - - let abi = { - let abi_name = cx.inline_abi_ident(); - let abi = &cx.abi_str; - let (doc_str, parse) = if cx.human_readable { - // Human readable: use abi::parse_abi_str - let doc_str = "The parsed human-readable ABI of the contract."; - let parse = quote!(#ethers_core::abi::parse_abi_str(__ABI)); - (doc_str, parse) - } else { - // JSON ABI: use serde_json::from_str - let doc_str = "The parsed JSON ABI of the contract."; - let parse = quote!(#ethers_core::utils::__serde_json::from_str(__ABI)); - (doc_str, parse) - }; - - quote! { - #[rustfmt::skip] - const __ABI: &str = #abi; - - // This never fails as we are parsing the ABI in this macro - #[doc = #doc_str] - pub static #abi_name: #ethers_contract::Lazy<#ethers_core::abi::Abi> = - #ethers_contract::Lazy::new(|| #parse.expect("ABI is always valid")); - } - }; - - let bytecode = cx.contract_bytecode.as_ref().map(|bytecode| { - let bytecode = bytecode.iter().copied().map(Literal::u8_unsuffixed); - let bytecode_name = cx.inline_bytecode_ident(); - quote! { - #[rustfmt::skip] - const __BYTECODE: &[u8] = &[ #( #bytecode ),* ]; - - #[doc = "The bytecode of the contract."] - pub static #bytecode_name: #ethers_core::types::Bytes = #ethers_core::types::Bytes::from_static(__BYTECODE); - } - }); - - let deployed_bytecode = cx.contract_deployed_bytecode.as_ref().map(|bytecode| { - let bytecode = bytecode.iter().copied().map(Literal::u8_unsuffixed); - let bytecode_name = cx.inline_deployed_bytecode_ident(); - quote! { - #[rustfmt::skip] - const __DEPLOYED_BYTECODE: &[u8] = &[ #( #bytecode ),* ]; - - #[doc = "The deployed bytecode of the contract."] - pub static #bytecode_name: #ethers_core::types::Bytes = #ethers_core::types::Bytes::from_static(__DEPLOYED_BYTECODE); - } - }); - - quote! { - // The `Lazy` ABI - #abi - - // The static Bytecode, if present - #bytecode - - // The static deployed Bytecode, if present - #deployed_bytecode - - // Struct declaration - pub struct #name(#ethers_contract::Contract); - - impl Clone for #name { - fn clone(&self) -> Self { - #name(self.0.clone()) - } - } - - // Deref to the inner contract in order to access more specific functions functions - impl std::ops::Deref for #name { - type Target = #ethers_contract::Contract; - - fn deref(&self) -> &Self::Target { &self.0 } - } - - impl std::fmt::Debug for #name { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_tuple(stringify!(#name)) - .field(&self.address()) - .finish() - } - } - } -} - -/// Expands to the tuple struct definition -pub(crate) fn expand_data_tuple( - name: &Ident, - params: &[(TokenStream, TokenStream)], -) -> TokenStream { - let fields = params - .iter() - .map(|(_, ty)| { - quote! { - pub #ty } - }) - .collect::>(); - - if fields.is_empty() { - quote! { struct #name; } - } else { - quote! { struct #name( #( #fields ),* ); } - } -} - -/// Expands to a struct definition with named fields -pub(crate) fn expand_data_struct( - name: &Ident, - params: &[(TokenStream, TokenStream)], -) -> TokenStream { - let fields = params - .iter() - .map(|(name, ty)| { - quote! { pub #name: #ty } - }) - .collect::>(); - - quote! { struct #name { #( #fields, )* } } -} diff --git a/ethers-contract/ethers-contract-abigen/src/contract/errors.rs b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs index f8fea826..e458ec51 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/errors.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs @@ -1,9 +1,6 @@ -//! derive error bindings +//! Custom errors expansion -use super::{ - common::{expand_data_struct, expand_data_tuple}, - types, util, Context, -}; +use super::{structs::expand_struct, types, util, Context}; use ethers_core::{ abi::{ethabi::AbiError, ErrorExt}, macros::{ethers_contract_crate, ethers_core_crate}, @@ -25,57 +22,46 @@ impl Context { .collect::>>()?; // only expand an enum when multiple errors are present - let errors_enum_decl = if self.abi.errors.values().flatten().count() > 1 { - self.expand_errors_enum() - } else { - quote! {} - }; + let errors_enum_decl = + if data_types.len() > 1 { Some(self.expand_errors_enum()) } else { None }; Ok(quote! { - // HERE #( #data_types )* #errors_enum_decl - - // HERE end }) } /// Expands an ABI error into a single error data type. This can expand either /// into a structure or a tuple in the case where all error parameters are anonymous. fn expand_error(&self, error: &AbiError) -> Result { - let sig = self.error_aliases.get(&error.abi_signature()).cloned(); + let error_name = &error.name; let abi_signature = error.abi_signature(); - let error_name = error_struct_name(&error.name, sig); + let alias_opt = self.error_aliases.get(&abi_signature).cloned(); + let error_struct_name = error_struct_name(&error.name, alias_opt); let fields = self.expand_error_params(error)?; // expand as a tuple if all fields are anonymous let all_anonymous_fields = error.inputs.iter().all(|input| input.name.is_empty()); - let data_type_definition = if all_anonymous_fields { - // expand to a tuple struct - expand_data_tuple(&error_name, &fields) - } else { - // expand to a struct - expand_data_struct(&error_name, &fields) - }; + let data_type_definition = expand_struct(&error_struct_name, &fields, all_anonymous_fields); let doc_str = format!( - "Custom Error type `{}` with signature `{}` and selector `0x{}`", - error.name, - abi_signature, - hex::encode(&error.selector()[..]) + "Custom Error type `{error_name}` with signature `{abi_signature}` and selector `0x{}`", + hex::encode(error.selector()) ); - let ethers_contract = ethers_contract_crate(); - // use the same derives as for events - let derives = util::expand_derives(&self.event_derives); - let error_name = &error.name; + let mut extra_derives = self.expand_extra_derives(); + if util::can_derive_defaults(&error.inputs) { + extra_derives.extend(quote!(Default)); + } + + let ethers_contract = ethers_contract_crate(); Ok(quote! { #[doc = #doc_str] - #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthError, #ethers_contract::EthDisplay, #derives)] + #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthError, #ethers_contract::EthDisplay, #extra_derives)] #[etherror(name = #error_name, abi = #abi_signature)] pub #data_type_definition }) @@ -95,6 +81,7 @@ impl Context { /// Generate an enum with a variant for each event fn expand_errors_enum(&self) -> TokenStream { + let enum_name = self.expand_error_enum_name(); let variants = self .abi .errors @@ -105,58 +92,57 @@ impl Context { }) .collect::>(); + let extra_derives = self.expand_extra_derives(); + let ethers_core = ethers_core_crate(); let ethers_contract = ethers_contract_crate(); - // use the same derives as for events - let derives = util::expand_derives(&self.event_derives); - let enum_name = self.expand_error_enum_name(); - quote! { - #[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #derives)] + #[doc = "Container type for all of the contract's custom errors"] + #[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #extra_derives)] pub enum #enum_name { - #(#variants(#variants)),* + #( #variants(#variants), )* } - impl #ethers_core::abi::AbiDecode for #enum_name { - fn decode(data: impl AsRef<[u8]>) -> ::std::result::Result { - #( - if let Ok(decoded) = <#variants as #ethers_core::abi::AbiDecode>::decode(data.as_ref()) { - return Ok(#enum_name::#variants(decoded)) + impl #ethers_core::abi::AbiDecode for #enum_name { + fn decode(data: impl AsRef<[u8]>) -> ::core::result::Result { + let data = data.as_ref(); + #( + if let Ok(decoded) = <#variants as #ethers_core::abi::AbiDecode>::decode(data) { + return Ok(Self::#variants(decoded)) + } + )* + Err(#ethers_core::abi::Error::InvalidData.into()) + } + } + + impl #ethers_core::abi::AbiEncode for #enum_name { + fn encode(self) -> ::std::vec::Vec { + match self { + #( + Self::#variants(element) => #ethers_core::abi::AbiEncode::encode(element), + )* } - )* - Err(#ethers_core::abi::Error::InvalidData.into()) - } - } - - impl #ethers_core::abi::AbiEncode for #enum_name { - fn encode(self) -> Vec { - match self { - #( - #enum_name::#variants(element) => element.encode() - ),* } } - } - impl ::std::fmt::Display for #enum_name { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match self { - #( - #enum_name::#variants(element) => element.fmt(f) - ),* + impl ::core::fmt::Display for #enum_name { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + #( + Self::#variants(element) => ::core::fmt::Display::fmt(element, f) + ),* + } } } - } - #( - impl ::std::convert::From<#variants> for #enum_name { - fn from(var: #variants) -> Self { - #enum_name::#variants(var) + #( + impl ::core::convert::From<#variants> for #enum_name { + fn from(value: #variants) -> Self { + Self::#variants(value) + } } - } - )* - + )* } } } diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs index bef73855..92ef88d3 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs @@ -1,7 +1,9 @@ -use super::{types, util, Context}; -use crate::util::can_derive_defaults; +//! Events expansion + +use super::{structs::expand_event_struct, types, Context}; +use crate::util; use ethers_core::{ - abi::{Event, EventExt, Param}, + abi::{Event, EventExt}, macros::{ethers_contract_crate, ethers_core_crate}, }; use eyre::Result; @@ -21,11 +23,8 @@ impl Context { .collect::>>()?; // only expand enums when multiple events are present - let events_enum_decl = if sorted_events.values().flatten().count() > 1 { - self.expand_events_enum() - } else { - quote! {} - }; + let events_enum_decl = + if data_types.len() > 1 { Some(self.expand_events_enum()) } else { None }; Ok(quote! { #( #data_types )* @@ -40,8 +39,7 @@ impl Context { let filter_methods = sorted_events .values() .flat_map(std::ops::Deref::deref) - .map(|event| self.expand_filter(event)) - .collect::>(); + .map(|event| self.expand_filter(event)); let events_method = self.expand_events_method(); @@ -67,22 +65,19 @@ impl Context { let ethers_core = ethers_core_crate(); let ethers_contract = ethers_contract_crate(); - // use the same derives as for events - let derives = util::expand_derives(&self.event_derives); + let extra_derives = self.expand_extra_derives(); let enum_name = self.expand_event_enum_name(); quote! { - #[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #derives)] + #[doc = "Container type for all of the contract's events"] + #[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #extra_derives)] pub enum #enum_name { - #(#variants(#variants)),* + #( #variants(#variants), )* } - impl #ethers_contract::EthLogDecode for #enum_name { - fn decode_log(log: &#ethers_core::abi::RawLog) -> ::std::result::Result - where - Self: Sized, - { - #( + impl #ethers_contract::EthLogDecode for #enum_name { + fn decode_log(log: &#ethers_core::abi::RawLog) -> ::core::result::Result { + #( if let Ok(decoded) = #variants::decode_log(log) { return Ok(#enum_name::#variants(decoded)) } @@ -91,15 +86,23 @@ impl Context { } } - impl ::std::fmt::Display for #enum_name { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + impl ::core::fmt::Display for #enum_name { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { match self { #( - #enum_name::#variants(element) => element.fmt(f) - ),* + Self::#variants(element) => ::core::fmt::Display::fmt(element, f), + )* } } } + + #( + impl ::core::convert::From<#variants> for #enum_name { + fn from(value: #variants) -> Self { + Self::#variants(value) + } + } + )* } } @@ -109,7 +112,7 @@ impl Context { } /// Expands the `events` function that bundles all declared events of this contract - fn expand_events_method(&self) -> TokenStream { + fn expand_events_method(&self) -> Option { let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect(); let mut iter = sorted_events.values().flatten(); @@ -125,28 +128,35 @@ impl Context { ) }; - quote! { - /// Returns an [`Event`](#ethers_contract::builders::Event) builder for all events of this contract - pub fn events(&self) -> #ethers_contract::builders::Event, M, #ty> { - self.0.event_with_filter(Default::default()) + Some(quote! { + /// Returns an `Event` builder for all the events of this contract. + pub fn events(&self) -> #ethers_contract::builders::Event< + ::std::sync::Arc, + M, + #ty, + > { + self.0.event_with_filter(::core::default::Default::default()) } - } + }) } else { - quote! {} + None } } /// Expands into a single method for contracting an event stream. fn expand_filter(&self, event: &Event) -> TokenStream { let name = &event.name; - let alias = self.event_aliases.get(&event.abi_signature()).cloned(); + let sig = event.abi_signature(); + let alias = self.event_aliases.get(&sig).cloned(); - // append `filter` to disambiguate with potentially conflicting - // function names - let function_name = if let Some(id) = alias.clone() { - util::safe_ident(&format!("{}_filter", id.to_string().to_snake_case())) - } else { - util::safe_ident(&format!("{}_filter", event.name.to_snake_case())) + // append `filter` to disambiguate with potentially conflicting function names + let function_name = { + let name = if let Some(ref id) = alias { + id.to_string().to_snake_case() + } else { + name.to_snake_case() + }; + util::safe_ident(&format!("{name}_filter")) }; let struct_name = event_struct_name(name, alias); @@ -156,7 +166,11 @@ impl Context { quote! { #[doc = #doc_str] - pub fn #function_name(&self) -> #ethers_contract::builders::Event, M, #struct_name> { + pub fn #function_name(&self) -> #ethers_contract::builders::Event< + ::std::sync::Arc, + M, + #struct_name + > { self.0.event() } } @@ -166,49 +180,27 @@ impl Context { /// into a structure or a tuple in the case where all event parameters (topics /// and data) are anonymous. fn expand_event(&self, event: &Event) -> Result { - let sig = self.event_aliases.get(&event.abi_signature()).cloned(); + let name = &event.name; let abi_signature = event.abi_signature(); - let event_abi_name = event.name.clone(); + let alias = self.event_aliases.get(&abi_signature).cloned(); - let event_name = event_struct_name(&event.name, sig); + let struct_name = event_struct_name(name, alias); - let params = types::expand_event_inputs(event, &self.internal_structs)?; + let fields = types::expand_event_inputs(event, &self.internal_structs)?; // expand as a tuple if all fields are anonymous let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty()); - let data_type_definition = if all_anonymous_fields { - expand_data_tuple(&event_name, ¶ms) - } else { - expand_data_struct(&event_name, ¶ms) - }; + let data_type_definition = expand_event_struct(&struct_name, &fields, all_anonymous_fields); - let derives = util::expand_derives(&self.event_derives); - - // rust-std only derives default automatically for arrays len <= 32 - // for large array types we skip derive(Default) - let derive_default = if can_derive_defaults( - &event - .inputs - .iter() - .map(|param| Param { - name: param.name.clone(), - kind: param.kind.clone(), - internal_type: None, - }) - .collect::>(), - ) { - quote! { - #[derive(Default)] - } - } else { - quote! {} - }; + let mut extra_derives = self.expand_extra_derives(); + if event.inputs.iter().map(|param| ¶m.kind).all(util::can_derive_default) { + extra_derives.extend(quote!(Default)); + } let ethers_contract = ethers_contract_crate(); Ok(quote! { - #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)] - #derive_default - #[ethevent( name = #event_abi_name, abi = #abi_signature )] + #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #extra_derives)] + #[ethevent(name = #name, abi = #abi_signature)] pub #data_type_definition }) } @@ -231,46 +223,6 @@ pub(crate) fn event_struct_alias(event_name: &str) -> Ident { util::ident(&event_name.to_pascal_case()) } -/// Expands an event data structure from its name-type parameter pairs. Returns -/// a tuple with the type definition (i.e. the struct declaration) and -/// construction (i.e. code for creating an instance of the event data). -fn expand_data_struct(name: &Ident, params: &[(TokenStream, TokenStream, bool)]) -> TokenStream { - let fields = params - .iter() - .map(|(name, ty, indexed)| { - if *indexed { - quote! { - #[ethevent(indexed)] - pub #name: #ty - } - } else { - quote! { pub #name: #ty } - } - }) - .collect::>(); - - quote! { struct #name { #( #fields, )* } } -} - -/// Expands an event data named tuple from its name-type parameter pairs. -/// Returns a tuple with the type definition and construction. -fn expand_data_tuple(name: &Ident, params: &[(TokenStream, TokenStream, bool)]) -> TokenStream { - let fields = params - .iter() - .map(|(_, ty, indexed)| { - if *indexed { - quote! { - #[ethevent(indexed)] pub #ty } - } else { - quote! { - pub #ty } - } - }) - .collect::>(); - - quote! { struct #name( #( #fields ),* ); } -} - #[cfg(test)] mod tests { use super::*; @@ -328,7 +280,11 @@ mod tests { #[doc = "Gets the contract's `Transfer` event"] pub fn transfer_event_filter( &self - ) -> ::ethers_contract::builders::Event, M, TransferEventFilter> { + ) -> ::ethers_contract::builders::Event< + ::std::sync::Arc, + M, + TransferEventFilter, + > { self.0.event() } }); @@ -349,7 +305,8 @@ mod tests { #[doc = "Gets the contract's `Transfer` event"] pub fn transfer_filter( &self, - ) -> ::ethers_contract::builders::Event, M, TransferFilter> { + ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, TransferFilter> + { self.0.event() } }); @@ -369,7 +326,7 @@ mod tests { let cx = test_context(); let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let name = event_struct_name(&event.name, None); - let definition = expand_data_struct(&name, ¶ms); + let definition = expand_event_struct(&name, ¶ms, false); assert_quote!(definition, { struct FooFilter { @@ -394,7 +351,7 @@ mod tests { let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let alias = Some(util::ident("FooAliased")); let name = event_struct_name(&event.name, alias); - let definition = expand_data_struct(&name, ¶ms); + let definition = expand_event_struct(&name, ¶ms, false); assert_quote!(definition, { struct FooAliasedFilter { @@ -418,7 +375,7 @@ mod tests { let cx = test_context(); let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let name = event_struct_name(&event.name, None); - let definition = expand_data_tuple(&name, ¶ms); + let definition = expand_event_struct(&name, ¶ms, true); assert_quote!(definition, { struct FooFilter(pub bool, pub ::ethers_core::types::Address); @@ -440,7 +397,7 @@ mod tests { let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap(); let alias = Some(util::ident("FooAliased")); let name = event_struct_name(&event.name, alias); - let definition = expand_data_tuple(&name, ¶ms); + let definition = expand_event_struct(&name, ¶ms, true); assert_quote!(definition, { struct FooAliasedFilter(pub bool, pub ::ethers_core::types::Address); diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 0df43602..797a20e1 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -1,8 +1,7 @@ -use super::{ - common::{expand_data_struct, expand_data_tuple}, - types, Context, -}; -use crate::util::{self, can_derive_defaults}; +//! Methods expansion + +use super::{structs::expand_struct, types, Context}; +use crate::util; use ethers_core::{ abi::{Function, FunctionExt, Param, ParamType}, macros::{ethers_contract_crate, ethers_core_crate}, @@ -33,7 +32,7 @@ impl Context { .map(|function| { let signature = function.abi_signature(); self.expand_function(function, aliases.get(&signature).cloned()) - .with_context(|| format!("error expanding function '{signature}'")) + .wrap_err_with(|| eyre::eyre!("error expanding function '{signature}'")) }) .collect::>>()?; @@ -50,11 +49,10 @@ impl Context { } /// Returns all deploy (constructor) implementations - pub(crate) fn deployment_methods(&self) -> TokenStream { - if self.contract_bytecode.is_none() { - // don't generate deploy if no bytecode - return quote! {} - } + pub(crate) fn deployment_methods(&self) -> Option { + // don't generate deploy if no bytecode + self.contract_bytecode.as_ref()?; + let ethers_core = ethers_core_crate(); let ethers_contract = ethers_contract_crate(); @@ -68,14 +66,14 @@ impl Context { #bytecode_name.clone().into() }; - let deploy = quote! { + Some(quote! { /// Constructs the general purpose `Deployer` instance based on the provided constructor arguments and sends it. /// Returns a new instance of a deployer that returns an instance of this contract after sending the transaction /// /// Notes: - /// 1. If there are no constructor arguments, you should pass `()` as the argument. - /// 1. The default poll duration is 7 seconds. - /// 1. The default number of confirmations is 1 block. + /// - If there are no constructor arguments, you should pass `()` as the argument. + /// - The default poll duration is 7 seconds. + /// - The default number of confirmations is 1 block. /// /// /// # Example @@ -86,22 +84,22 @@ impl Context { /// /// ```ignore /// # async fn deploy(client: ::std::sync::Arc) { - /// abigen!(Greeter,"../greeter.json"); + /// abigen!(Greeter, "../greeter.json"); /// /// let greeter_contract = Greeter::deploy(client, "Hello world!".to_string()).unwrap().send().await.unwrap(); /// let msg = greeter_contract.greet().call().await.unwrap(); /// # } /// ``` - pub fn deploy(client: ::std::sync::Arc, constructor_args: T) -> ::std::result::Result<#ethers_contract::builders::ContractDeployer, #ethers_contract::ContractError> { - let factory = #ethers_contract::ContractFactory::new(#get_abi, #get_bytecode, client); - let deployer = factory.deploy(constructor_args)?; - let deployer = #ethers_contract::ContractDeployer::new(deployer); - Ok(deployer) + pub fn deploy( + client: ::std::sync::Arc, + constructor_args: T, + ) -> ::core::result::Result<#ethers_contract::builders::ContractDeployer, #ethers_contract::ContractError> { + let factory = #ethers_contract::ContractFactory::new(#get_abi, #get_bytecode, client); + let deployer = factory.deploy(constructor_args)?; + let deployer = #ethers_contract::ContractDeployer::new(deployer); + Ok(deployer) } - - }; - - deploy + }) } /// Expands to the corresponding struct type based on the inputs of the given function @@ -110,42 +108,30 @@ impl Context { function: &Function, alias: Option<&MethodAlias>, ) -> Result { - let call_name = expand_call_struct_name(function, alias); + let struct_name = expand_call_struct_name(function, alias); + let fields = self.expand_input_params(function)?; // expand as a tuple if all fields are anonymous let all_anonymous_fields = function.inputs.iter().all(|input| input.name.is_empty()); - let call_type_definition = if all_anonymous_fields { - // expand to a tuple struct - expand_data_tuple(&call_name, &fields) - } else { - // expand to a struct - expand_data_struct(&call_name, &fields) - }; + let call_type_definition = expand_struct(&struct_name, &fields, all_anonymous_fields); + let function_name = &function.name; let abi_signature = function.abi_signature(); let doc_str = format!( "Container type for all input parameters for the `{function_name}` function with signature `{abi_signature}` and selector `0x{}`", - hex::encode(&function.selector()[..]) + hex::encode(function.selector()) ); - let ethers_contract = ethers_contract_crate(); - // use the same derives as for events - let derives = util::expand_derives(&self.event_derives); + let mut extra_derives = self.expand_extra_derives(); + if util::can_derive_defaults(&function.inputs) { + extra_derives.extend(quote!(Default)); + } - // rust-std only derives default automatically for arrays len <= 32 - // for large array types we skip derive(Default) - let derive_default = if can_derive_defaults(&function.inputs) { - quote! { - #[derive(Default)] - } - } else { - quote! {} - }; + let ethers_contract = ethers_contract_crate(); Ok(quote! { #[doc = #doc_str] - #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #derives)] - #derive_default + #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #extra_derives)] #[ethcall( name = #function_name, abi = #abi_signature )] pub #call_type_definition }) @@ -156,56 +142,46 @@ impl Context { &self, function: &Function, alias: Option<&MethodAlias>, - ) -> Result { - let name = &function.name; - let struct_name = expand_return_struct_name(function, alias); - let fields = self.expand_output_params(function)?; + ) -> Result> { // no point in having structs when there is no data returned if function.outputs.is_empty() { - return Ok(TokenStream::new()) + return Ok(None) } + + let name = &function.name; + + let struct_name = expand_return_struct_name(function, alias); + let fields = self.expand_output_params(function)?; // expand as a tuple if all fields are anonymous let all_anonymous_fields = function.outputs.iter().all(|output| output.name.is_empty()); - let return_type_definition = if all_anonymous_fields { - // expand to a tuple struct - expand_data_tuple(&struct_name, &fields) - } else { - // expand to a struct - expand_data_struct(&struct_name, &fields) - }; + let return_type_definition = expand_struct(&struct_name, &fields, all_anonymous_fields); + let abi_signature = function.abi_signature(); let doc_str = format!( "Container type for all return fields from the `{name}` function with signature `{abi_signature}` and selector `0x{}`", - hex::encode(&function.selector()[..]) + hex::encode(function.selector()) ); + let mut extra_derives = self.expand_extra_derives(); + if util::can_derive_defaults(&function.inputs) { + extra_derives.extend(quote!(Default)); + } + let ethers_contract = ethers_contract_crate(); - // use the same derives as for events - let derives = util::expand_derives(&self.event_derives); - // rust-std only derives default automatically for arrays len <= 32 - // for large array types we skip derive(Default) - let derive_default = if can_derive_defaults(&function.outputs) { - quote! { - #[derive(Default)] - } - } else { - quote! {} - }; - - Ok(quote! { + Ok(Some(quote! { #[doc = #doc_str] - #[derive(Clone, Debug,Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)] - #derive_default + #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)] pub #return_type_definition - }) + })) } /// Expands all call structs fn expand_call_structs(&self, aliases: BTreeMap) -> Result { - let mut struct_defs = Vec::new(); - let mut struct_names = Vec::new(); - let mut variant_names = Vec::new(); + let len = self.abi.functions.len(); + let mut struct_defs = Vec::with_capacity(len); + let mut struct_names = Vec::with_capacity(len); + let mut variant_names = Vec::with_capacity(len); for function in self.abi.functions.values().flatten() { let signature = function.abi_signature(); let alias = aliases.get(&signature); @@ -214,86 +190,86 @@ impl Context { variant_names.push(expand_call_struct_variant_name(function, alias)); } - let struct_def_tokens = quote! { - #(#struct_defs)* - }; + let struct_def_tokens = quote!(#(#struct_defs)*); if struct_defs.len() <= 1 { // no need for an enum return Ok(struct_def_tokens) } + let extra_derives = self.expand_extra_derives(); + + let enum_name = self.expand_calls_enum_name(); + let ethers_core = ethers_core_crate(); let ethers_contract = ethers_contract_crate(); - // use the same derives as for events - let derives = util::expand_derives(&self.event_derives); - let enum_name = self.expand_calls_enum_name(); - - Ok(quote! { + let tokens = quote! { #struct_def_tokens - #[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #derives)] + #[doc = "Container type for all of the contract's call "] + #[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #extra_derives)] pub enum #enum_name { - #(#variant_names(#struct_names)),* + #( #variant_names(#struct_names), )* } - impl #ethers_core::abi::AbiDecode for #enum_name { - fn decode(data: impl AsRef<[u8]>) -> ::std::result::Result { - #( - if let Ok(decoded) = <#struct_names as #ethers_core::abi::AbiDecode>::decode(data.as_ref()) { - return Ok(#enum_name::#variant_names(decoded)) + impl #ethers_core::abi::AbiDecode for #enum_name { + fn decode(data: impl AsRef<[u8]>) -> ::core::result::Result { + let data = data.as_ref(); + #( + if let Ok(decoded) = <#struct_names as #ethers_core::abi::AbiDecode>::decode(data) { + return Ok(Self::#variant_names(decoded)) + } + )* + Err(#ethers_core::abi::Error::InvalidData.into()) + } + } + + impl #ethers_core::abi::AbiEncode for #enum_name { + fn encode(self) -> Vec { + match self { + #( + Self::#variant_names(element) => #ethers_core::abi::AbiEncode::encode(element), + )* } - )* - Err(#ethers_core::abi::Error::InvalidData.into()) - } - } - - impl #ethers_core::abi::AbiEncode for #enum_name { - fn encode(self) -> Vec { - match self { - #( - #enum_name::#variant_names(element) => element.encode() - ),* } } - } - impl ::std::fmt::Display for #enum_name { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match self { - #( - #enum_name::#variant_names(element) => element.fmt(f) - ),* + impl ::core::fmt::Display for #enum_name { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + #( + Self::#variant_names(element) => ::core::fmt::Display::fmt(element, f), + )* + } } } - } - #( - impl ::std::convert::From<#struct_names> for #enum_name { - fn from(var: #struct_names) -> Self { - #enum_name::#variant_names(var) + #( + impl ::core::convert::From<#struct_names> for #enum_name { + fn from(value: #struct_names) -> Self { + Self::#variant_names(value) + } } - } - )* + )* + }; - }) + Ok(tokens) } /// Expands all return structs fn expand_return_structs(&self, aliases: BTreeMap) -> Result { - let mut struct_defs = Vec::new(); + let mut tokens = TokenStream::new(); for function in self.abi.functions.values().flatten() { let signature = function.abi_signature(); let alias = aliases.get(&signature); - struct_defs.push(self.expand_return_struct(function, alias)?); + match self.expand_return_struct(function, alias) { + Ok(Some(def)) => tokens.extend(def), + Ok(None) => {} + Err(e) => return Err(e), + } } - - let struct_def_tokens = quote! { - #(#struct_defs)* - }; - - Ok(struct_def_tokens) + Ok(tokens) } /// The name ident of the calls enum @@ -597,7 +573,7 @@ impl Context { fn expand_selector(selector: Selector) -> TokenStream { let bytes = selector.iter().copied().map(Literal::u8_unsuffixed); - quote! { [#( #bytes ),*] } + quote!([ #( #bytes ),* ]) } /// Represents the aliases to use when generating method related elements diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs index 13dcac4f..12cdee93 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs @@ -1,8 +1,7 @@ -//! Methods for expanding structs -use crate::{ - contract::{types, Context}, - util, -}; +//! Structs expansion + +use super::{types, Context}; +use crate::util; use ethers_core::{ abi::{ struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType}, @@ -15,6 +14,7 @@ use inflector::Inflector; use proc_macro2::TokenStream; use quote::quote; use std::collections::{HashMap, VecDeque}; +use syn::Ident; impl Context { /// Generate corresponding types for structs parsed from a human readable ABI @@ -51,17 +51,17 @@ impl Context { /// Generates the type definition for the name that matches the given identifier fn generate_internal_struct(&self, id: &str) -> Result { let sol_struct = - self.internal_structs.structs.get(id).ok_or_else(|| eyre!("struct not found"))?; + self.internal_structs.structs.get(id).ok_or_else(|| eyre!("Struct not found"))?; let struct_name = self .internal_structs .rust_type_names .get(id) - .ok_or_else(|| eyre!("No types found for {}", id))?; + .ok_or_else(|| eyre!("No types found for {id}"))?; let tuple = self .internal_structs .struct_tuples .get(id) - .ok_or_else(|| eyre!("No types found for {}", id))? + .ok_or_else(|| eyre!("No types found for {id}"))? .clone(); self.expand_internal_struct(struct_name, sol_struct, tuple) } @@ -95,33 +95,22 @@ impl Context { FieldType::Elementary(ty) => types::expand(ty)?, FieldType::Struct(struct_ty) => types::expand_struct_type(struct_ty), FieldType::Mapping(_) => { - eyre::bail!("Mapping types in struct `{}` are not supported {:?}", name, field) + eyre::bail!("Mapping types in struct `{name}` are not supported") } }; - if is_tuple { - fields.push(quote!(pub #ty)); + let field_name = if is_tuple { + TokenStream::new() } else { let field_name = util::safe_ident(&field.name().to_snake_case()); - fields.push(quote! { pub #field_name: #ty }); - } + quote!(#field_name) + }; + fields.push((field_name, ty)); } let name = util::ident(name); - let struct_def = if is_tuple { - quote! { - pub struct #name( - #( #fields ),* - ); - } - } else { - quote! { - pub struct #name { - #( #fields ),* - } - } - }; + let struct_def = expand_struct(&name, &fields, is_tuple); let sig = match tuple { ParamType::Tuple(ref types) if !types.is_empty() => util::abi_signature_types(types), @@ -129,20 +118,20 @@ impl Context { }; let doc_str = format!("`{name}({sig})`"); - // use the same derives as for events - let derives = util::expand_derives(&self.event_derives); + let extra_derives = self.expand_extra_derives(); let ethers_contract = ethers_contract_crate(); + Ok(quote! { #[doc = #doc_str] - #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)] - #struct_def + #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)] + pub #struct_def }) } fn generate_human_readable_struct(&self, name: &str) -> Result { let sol_struct = - self.abi_parser.structs.get(name).ok_or_else(|| eyre!("struct not found"))?; + self.abi_parser.structs.get(name).ok_or_else(|| eyre!("Struct `{name}` not found"))?; let mut fields = Vec::with_capacity(sol_struct.fields().len()); let mut param_types = Vec::with_capacity(sol_struct.fields().len()); for field in sol_struct.fields() { @@ -162,14 +151,14 @@ impl Context { .abi_parser .struct_tuples .get(name) - .ok_or_else(|| eyre!("No types found for {}", name))? + .ok_or_else(|| eyre!("No types found for {name}"))? .clone(); let tuple = ParamType::Tuple(tuple); param_types.push(struct_ty.as_param(tuple)); } FieldType::Mapping(_) => { - eyre::bail!("Mapping types in struct `{}` are not supported {:?}", name, field) + eyre::bail!("Mapping types in struct `{name}` are not supported") } } } @@ -178,15 +167,16 @@ impl Context { let name = util::ident(name); - // use the same derives as for events - let derives = &self.event_derives; - let derives = quote! {#(#derives),*}; + let mut extra_derives = self.expand_extra_derives(); + if param_types.iter().all(util::can_derive_default) { + extra_derives.extend(quote!(Default)) + } let ethers_contract = ethers_contract_crate(); Ok(quote! { #[doc = #abi_signature] - #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)] + #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #extra_derives)] pub struct #name { #( #fields ),* } @@ -580,7 +570,7 @@ fn struct_type_name(name: &str) -> &str { } /// `Pairing.G2Point` -> `Pairing.G2Point` -pub fn struct_type_identifier(name: &str) -> &str { +fn struct_type_identifier(name: &str) -> &str { name.trim_start_matches("struct ").split('[').next().unwrap() } @@ -592,16 +582,56 @@ fn struct_type_projections(name: &str) -> Vec { iter.rev().map(str::to_string).collect() } +pub(crate) fn expand_struct( + name: &Ident, + fields: &[(TokenStream, TokenStream)], + is_tuple: bool, +) -> TokenStream { + _expand_struct(name, fields.iter().map(|(a, b)| (a, b, false)), is_tuple) +} + +pub(crate) fn expand_event_struct( + name: &Ident, + fields: &[(TokenStream, TokenStream, bool)], + is_tuple: bool, +) -> TokenStream { + _expand_struct(name, fields.iter().map(|(a, b, c)| (a, b, *c)), is_tuple) +} + +fn _expand_struct<'a>( + name: &Ident, + fields: impl Iterator, + is_tuple: bool, +) -> TokenStream { + let fields = fields.map(|(field, ty, indexed)| { + (field, ty, if indexed { Some(quote!(#[ethevent(indexed)])) } else { None }) + }); + let fields = if let Some(0) = fields.size_hint().1 { + // unit struct + quote!(;) + } else if is_tuple { + // tuple struct + let fields = fields.map(|(_, ty, indexed)| quote!(#indexed pub #ty)); + quote!(( #( #fields ),* );) + } else { + // struct + let fields = fields.map(|(field, ty, indexed)| quote!(#indexed pub #field: #ty)); + quote!({ #( #fields, )* }) + }; + + quote!(struct #name #fields) +} + #[cfg(test)] mod tests { use super::*; + #[test] fn can_determine_structs() { const VERIFIER_ABI: &str = include_str!("../../../tests/solidity-contracts/verifier_abi.json"); let abi = serde_json::from_str::(VERIFIER_ABI).unwrap(); - let internal = InternalStructs::new(abi); - dbg!(internal.rust_type_names); + let _internal = InternalStructs::new(abi); } } diff --git a/ethers-contract/ethers-contract-abigen/src/contract/types.rs b/ethers-contract/ethers-contract-abigen/src/contract/types.rs index f1918d70..b49120de 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/types.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/types.rs @@ -11,7 +11,7 @@ use proc_macro2::{Literal, TokenStream}; use quote::{quote, ToTokens}; /// Expands a ParamType Solidity type to its Rust equivalent. -pub fn expand(kind: &ParamType) -> Result { +pub(crate) fn expand(kind: &ParamType) -> Result { let ethers_core = ethers_core_crate(); match kind { @@ -58,7 +58,7 @@ pub fn expand(kind: &ParamType) -> Result { } /// Expands the event's inputs. -pub fn expand_event_inputs( +pub(crate) fn expand_event_inputs( event: &Event, internal_structs: &InternalStructs, ) -> Result> { @@ -121,7 +121,7 @@ fn expand_event_input( /// Expands `params` to `(name, type)` tokens pairs, while resolving tuples' types using the given /// function. -pub fn expand_params<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>( +pub(crate) fn expand_params<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>( params: &'a [Param], resolve_tuple: F, ) -> Result> { @@ -160,7 +160,7 @@ fn expand_resolved<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>( } /// Expands to the Rust struct type. -pub fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream { +pub(crate) fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream { match struct_ty { StructFieldType::Type(ty) => { let ty = util::ident(&ty.name().to_pascal_case()); diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs index 2b5706da..984a11cc 100644 --- a/ethers-contract/ethers-contract-abigen/src/lib.rs +++ b/ethers-contract/ethers-contract-abigen/src/lib.rs @@ -8,6 +8,7 @@ //! [abigen]: https://docs.rs/ethers/latest/ethers/contract/macro.abigen.html #![deny(rustdoc::broken_intra_doc_links, missing_docs, unsafe_code)] +#![warn(unreachable_pub)] #[cfg(test)] #[allow(missing_docs)] @@ -25,6 +26,8 @@ pub mod multi; pub use multi::MultiAbigen; mod source; +#[cfg(all(feature = "online", not(target_arch = "wasm32")))] +pub use source::Explorer; pub use source::Source; mod util; diff --git a/ethers-contract/ethers-contract-abigen/src/source/online.rs b/ethers-contract/ethers-contract-abigen/src/source/online.rs index b8c200ca..3a6f04c0 100644 --- a/ethers-contract/ethers-contract-abigen/src/source/online.rs +++ b/ethers-contract/ethers-contract-abigen/src/source/online.rs @@ -9,10 +9,14 @@ use url::Url; /// An [etherscan](https://etherscan.io)-like blockchain explorer. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Explorer { + /// #[default] Etherscan, + /// Bscscan, + /// Polygonscan, + /// Snowtrace, } diff --git a/ethers-contract/ethers-contract-abigen/src/util.rs b/ethers-contract/ethers-contract-abigen/src/util.rs index 48f9c6d5..665cc712 100644 --- a/ethers-contract/ethers-contract-abigen/src/util.rs +++ b/ethers-contract/ethers-contract-abigen/src/util.rs @@ -3,35 +3,38 @@ use eyre::Result; use inflector::Inflector; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use std::path::PathBuf; -use syn::{Ident as SynIdent, Path}; +use std::path::{Path, PathBuf}; -/// Expands a identifier string into a token. -pub fn ident(name: &str) -> Ident { +/// Creates a new Ident with the given string at [`Span::call_site`]. +/// +/// # Panics +/// +/// If the input string is neither a keyword nor a legal variable name. +pub(crate) fn ident(name: &str) -> Ident { Ident::new(name, Span::call_site()) } -/// Expands an identifier string into a token and appending `_` if the -/// identifier is for a reserved keyword. +/// Expands an identifier string into a token and appending `_` if the identifier is for a reserved +/// keyword. /// /// Parsing keywords like `self` can fail, in this case we add an underscore. -pub fn safe_ident(name: &str) -> Ident { - syn::parse_str::(name).unwrap_or_else(|_| ident(&format!("{name}_"))) +pub(crate) fn safe_ident(name: &str) -> Ident { + syn::parse_str::(name).unwrap_or_else(|_| ident(&format!("{name}_"))) } /// Converts a `&str` to `snake_case` `String` while respecting identifier rules -pub fn safe_snake_case(ident: &str) -> String { +pub(crate) fn safe_snake_case(ident: &str) -> String { safe_identifier_name(ident.to_snake_case()) } /// Converts a `&str` to `PascalCase` `String` while respecting identifier rules -pub fn safe_pascal_case(ident: &str) -> String { +pub(crate) fn safe_pascal_case(ident: &str) -> String { safe_identifier_name(ident.to_pascal_case()) } /// respects identifier rules, such as, an identifier must not start with a numeric char -fn safe_identifier_name(name: String) -> String { - if name.starts_with(|c: char| c.is_numeric()) { +pub(crate) fn safe_identifier_name(name: String) -> String { + if name.starts_with(char::is_numeric) { format!("_{name}") } else { name @@ -39,39 +42,46 @@ fn safe_identifier_name(name: String) -> String { } /// converts invalid rust module names to valid ones -pub fn safe_module_name(name: &str) -> String { +pub(crate) fn safe_module_name(name: &str) -> String { // handle reserve words used in contracts (eg Enum is a gnosis contract) safe_ident(&safe_snake_case(name)).to_string() } /// Expands an identifier as snakecase and preserve any leading or trailing underscores -pub fn safe_snake_case_ident(name: &str) -> Ident { +pub(crate) fn safe_snake_case_ident(name: &str) -> Ident { let i = name.to_snake_case(); ident(&preserve_underscore_delim(&i, name)) } /// Expands an identifier as pascal case and preserve any leading or trailing underscores -pub fn safe_pascal_case_ident(name: &str) -> Ident { +pub(crate) fn safe_pascal_case_ident(name: &str) -> Ident { let i = name.to_pascal_case(); ident(&preserve_underscore_delim(&i, name)) } /// Reapplies leading and trailing underscore chars to the ident -/// Example `ident = "pascalCase"; alias = __pascalcase__` -> `__pascalCase__` -pub fn preserve_underscore_delim(ident: &str, alias: &str) -> String { - alias - .chars() - .take_while(|c| *c == '_') - .chain(ident.chars()) - .chain(alias.chars().rev().take_while(|c| *c == '_')) - .collect() +/// +/// # Example +/// +/// ```ignore +/// # use ethers_contract_abigen::util::preserve_underscore_delim; +/// assert_eq!( +/// preserve_underscore_delim("pascalCase", "__pascalcase__"), +/// "__pascalCase__" +/// ); +/// ``` +pub(crate) fn preserve_underscore_delim(ident: &str, original: &str) -> String { + let is_underscore = |c: &char| *c == '_'; + let pre = original.chars().take_while(is_underscore); + let post = original.chars().rev().take_while(is_underscore); + pre.chain(ident.chars()).chain(post).collect() } /// Expands a positional identifier string that may be empty. /// /// Note that this expands the parameter name with `safe_ident`, meaning that /// identifiers that are reserved keywords get `_` appended to them. -pub fn expand_input_name(index: usize, name: &str) -> TokenStream { +pub(crate) fn expand_input_name(index: usize, name: &str) -> TokenStream { let name_str = match name { "" => format!("p{index}"), n => n.to_snake_case(), @@ -81,18 +91,14 @@ pub fn expand_input_name(index: usize, name: &str) -> TokenStream { quote! { #name } } -pub fn expand_derives(derives: &[Path]) -> TokenStream { - quote! {#(#derives),*} -} - /// Perform a blocking HTTP GET request and return the contents of the response as a String. #[cfg(all(feature = "online", not(target_arch = "wasm32")))] -pub fn http_get(url: impl reqwest::IntoUrl) -> Result { +pub(crate) fn http_get(url: impl reqwest::IntoUrl) -> Result { Ok(reqwest::blocking::get(url)?.text()?) } /// Replaces any occurrences of env vars in the `raw` str with their value -pub fn resolve_path(raw: &str) -> Result { +pub(crate) fn resolve_path(raw: &str) -> Result { let mut unprocessed = raw; let mut resolved = String::new(); @@ -107,7 +113,7 @@ pub fn resolve_path(raw: &str) -> Result { unprocessed = rest; } None => { - eyre::bail!("Unable to parse a variable from \"{}\"", tail) + eyre::bail!("Unable to parse a variable from \"{tail}\"") } } } @@ -149,7 +155,7 @@ fn take_while(s: &str, mut predicate: impl FnMut(char) -> bool) -> (&str, &str) } /// Returns a list of absolute paths to all the json files under the root -pub fn json_files(root: impl AsRef) -> Vec { +pub(crate) fn json_files(root: impl AsRef) -> Vec { walkdir::WalkDir::new(root) .into_iter() .filter_map(Result::ok) @@ -162,14 +168,14 @@ pub fn json_files(root: impl AsRef) -> Vec { /// Returns whether all the given parameters can derive [`Default`]. /// /// rust-std derives `Default` automatically only for arrays len <= 32 -pub fn can_derive_defaults<'a>(params: impl IntoIterator) -> bool { +pub(crate) fn can_derive_defaults<'a>(params: impl IntoIterator) -> bool { params.into_iter().map(|param| ¶m.kind).all(can_derive_default) } /// Returns whether the given type can derive [`Default`]. /// /// rust-std derives `Default` automatically only for arrays len <= 32 -pub fn can_derive_default(param: &ParamType) -> bool { +pub(crate) fn can_derive_default(param: &ParamType) -> bool { const MAX_SUPPORTED_LEN: usize = 32; match param { ParamType::FixedBytes(len) => *len <= MAX_SUPPORTED_LEN, @@ -186,7 +192,7 @@ pub fn can_derive_default(param: &ParamType) -> bool { } /// Returns the formatted Solidity ABI signature. -pub fn abi_signature<'a, N, T>(name: N, types: T) -> String +pub(crate) fn abi_signature<'a, N, T>(name: N, types: T) -> String where N: std::fmt::Display, T: IntoIterator, @@ -196,7 +202,7 @@ where } /// Returns the Solidity stringified ABI types joined by a single comma. -pub fn abi_signature_types<'a, T: IntoIterator>(types: T) -> String { +pub(crate) fn abi_signature_types<'a, T: IntoIterator>(types: T) -> String { types.into_iter().map(ToString::to_string).collect::>().join(",") } diff --git a/ethers-contract/ethers-contract-derive/src/abigen.rs b/ethers-contract/ethers-contract-derive/src/abigen.rs index 5fcabf9f..500fa630 100644 --- a/ethers-contract/ethers-contract-derive/src/abigen.rs +++ b/ethers-contract/ethers-contract-derive/src/abigen.rs @@ -21,9 +21,9 @@ use syn::{ }; /// A series of `ContractArgs` separated by `;` -#[cfg_attr(test, derive(Debug))] +#[derive(Clone, Debug)] pub(crate) struct Contracts { - inner: Vec<(Span, ContractArgs)>, + pub(crate) inner: Vec<(Span, ContractArgs)>, } impl Contracts { @@ -57,7 +57,7 @@ impl Parse for Contracts { } /// Contract procedural macro arguments. -#[cfg_attr(test, derive(Debug, Eq, PartialEq))] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct ContractArgs { name: String, abi: String, @@ -128,7 +128,7 @@ impl ParseInner for ContractArgs { } /// A single procedural macro parameter. -#[cfg_attr(test, derive(Debug, Eq, PartialEq))] +#[derive(Clone, Debug, PartialEq, Eq)] enum Parameter { Methods(Vec), Derives(Vec), @@ -189,7 +189,7 @@ impl Parse for Parameter { } /// An explicitely named contract method. -#[cfg_attr(test, derive(Debug, Eq, PartialEq))] +#[derive(Clone, Debug, PartialEq, Eq)] struct Method { signature: String, alias: String, diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs index dd0d467a..844bf6a4 100644 --- a/ethers-contract/ethers-contract-derive/src/event.rs +++ b/ethers-contract/ethers-contract-derive/src/event.rs @@ -205,9 +205,9 @@ fn derive_decode_from_log_impl( // decode let (signature_check, flat_topics_init, topic_tokens_len_check) = if event.anonymous { ( - quote! {}, + None, quote! { - let flat_topics = topics.iter().flat_map(|t| t.as_ref().to_vec()).collect::>(); + let flat_topics = topics.iter().flat_map(|t| t.as_ref().to_vec()).collect::>(); }, quote! { if topic_tokens.len() != topics.len() { @@ -217,12 +217,12 @@ fn derive_decode_from_log_impl( ) } else { ( - quote! { + Some(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::>(); },