From b0b4f4e09e971265da51c41e321ef73fe4c6f7cf Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 6 Aug 2021 14:47:17 +0200 Subject: [PATCH] feat: detect ethers crate paths in derive macros (#366) * feat: determine ethers crate name using metadata * use crate detection --- Cargo.lock | 36 ++++++++ .../ethers-contract-abigen/Cargo.toml | 2 + .../ethers-contract-abigen/src/lib.rs | 2 +- .../ethers-contract-abigen/src/util.rs | 50 ++++++++++- .../ethers-contract-derive/src/lib.rs | 89 +++++++++++-------- 5 files changed, 138 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0796f5df..5f818eca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,37 @@ dependencies = [ "serde", ] +[[package]] +name = "camino" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c297bd3135f558552f99a0daa180876984ea2c4ffa7470314540dff8c654109a" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + [[package]] name = "cc" version = "1.0.68" @@ -808,8 +839,10 @@ version = "0.4.0" dependencies = [ "Inflector", "anyhow", + "cargo_metadata", "ethers-core", "hex", + "once_cell", "proc-macro2", "quote", "reqwest", @@ -2307,6 +2340,9 @@ name = "semver" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +dependencies = [ + "serde", +] [[package]] name = "serde" diff --git a/ethers-contract/ethers-contract-abigen/Cargo.toml b/ethers-contract/ethers-contract-abigen/Cargo.toml index 2a24d68a..9da44463 100644 --- a/ethers-contract/ethers-contract-abigen/Cargo.toml +++ b/ethers-contract/ethers-contract-abigen/Cargo.toml @@ -21,6 +21,8 @@ url = "2.1" serde_json = "1.0.61" hex = { version = "0.4.2", default-features = false, features = ["std"] } reqwest = { version = "0.11.3", features = ["blocking"] } +once_cell = { version = "1.8.0", default-features = false } +cargo_metadata = "0.14.0" [package.metadata.docs.rs] all-features = true diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs index e3f69fd4..716ac85b 100644 --- a/ethers-contract/ethers-contract-abigen/src/lib.rs +++ b/ethers-contract/ethers-contract-abigen/src/lib.rs @@ -21,7 +21,7 @@ mod util; pub use ethers_core::types::Address; pub use source::Source; -pub use util::parse_address; +pub use util::{ethers_contract_crate, ethers_core_crate, parse_address}; use anyhow::Result; use proc_macro2::TokenStream; diff --git a/ethers-contract/ethers-contract-abigen/src/util.rs b/ethers-contract/ethers-contract-abigen/src/util.rs index ffc835c8..4952f7cd 100644 --- a/ethers-contract/ethers-contract-abigen/src/util.rs +++ b/ethers-contract/ethers-contract-abigen/src/util.rs @@ -1,11 +1,59 @@ use ethers_core::types::Address; use anyhow::{anyhow, Result}; +use cargo_metadata::{CargoOpt, DependencyKind, Metadata, MetadataCommand}; use inflector::Inflector; +use once_cell::sync::Lazy; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::quote; use reqwest::Client; -use syn::Ident as SynIdent; +use syn::{Ident as SynIdent, Path}; + +/// See `determine_ethers_crates` +/// +/// This ensures that the `MetadataCommand` is only run once +static ETHERS_CRATES: Lazy<(&'static str, &'static str)> = Lazy::new(determine_ethers_crates); + +/// Convenience function to turn the `ethers_core` name in `ETHERS_CRATE` into a `Path` +pub fn ethers_core_crate() -> Path { + syn::parse_str(ETHERS_CRATES.0).expect("valid path; qed") +} +/// Convenience function to turn the `ethers_contract` name in `ETHERS_CRATE` into an `Path` +pub fn ethers_contract_crate() -> Path { + syn::parse_str(ETHERS_CRATES.1).expect("valid path; qed") +} + +/// The crates name to use when deriving macros: (`core`, `contract`) +/// +/// We try to determine which crate ident to use based on the dependencies of +/// the project in which the macro is used. This is useful because the macros, +/// like `EthEvent` are provided by the `ethers-contract` crate which depends on +/// `ethers_core`. Most commonly `ethers` will be used as dependency which +/// reexports all the different crates, essentially `ethers::core` is +/// `ethers_core` So depending on the dependency used `ethers` ors `ethers_core +/// | ethers_contract`, we need to use the fitting crate ident when expand the +/// macros This will attempt to parse the current `Cargo.toml` and check the +/// ethers related dependencies. +pub fn determine_ethers_crates() -> (&'static str, &'static str) { + MetadataCommand::new() + .manifest_path(&format!( + "{}/Cargo.toml", + std::env::var("CARGO_MANIFEST_DIR").expect("No Manifest found") + )) + .exec() + .ok() + .and_then(|metadata| { + metadata.root_package().and_then(|pkg| { + pkg.dependencies + .iter() + .filter(|dep| dep.kind == DependencyKind::Normal) + .find_map(|dep| { + (dep.name == "ethers").then(|| ("ethers::core", "ethers::contract")) + }) + }) + }) + .unwrap_or(("ethers_core", "ethers_contract")) +} /// Expands a identifier string into an token. pub fn ident(name: &str) -> Ident { diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs index 50ccd5c3..ec112741 100644 --- a/ethers-contract/ethers-contract-derive/src/lib.rs +++ b/ethers-contract/ethers-contract-derive/src/lib.rs @@ -2,7 +2,7 @@ //! ethereum smart contract. #![deny(missing_docs, unsafe_code)] -use ethers_contract_abigen::Source; +use ethers_contract_abigen::{ethers_contract_crate, ethers_core_crate, Source}; use proc_macro::TokenStream; use proc_macro2::{Literal, Span}; use quote::{quote, quote_spanned}; @@ -117,6 +117,11 @@ pub fn abigen(input: TokenStream) -> TokenStream { #[proc_macro_derive(EthEvent, attributes(ethevent))] pub fn derive_abi_event(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); + + // the ethers crates to use + let core_crate = ethers_core_crate(); + let contract_crate = ethers_contract_crate(); + let name = &input.ident; let attributes = match parse_attributes(&input) { Ok(attributes) => attributes, @@ -206,13 +211,13 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream { let anon = attributes.anonymous.map(|(b, _)| b).unwrap_or_default(); let ethevent_impl = quote! { - impl ethers_contract::EthEvent for #name { + impl #contract_crate::EthEvent for #name { fn name() -> ::std::borrow::Cow<'static, str> { #event_name.into() } - fn signature() -> ethers_core::types::H256 { + fn signature() -> #core_crate::types::H256 { #signature } @@ -220,7 +225,7 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream { #abi.into() } - fn decode_log(log: ðers_core::abi::RawLog) -> Result where Self: Sized { + fn decode_log(log: &#core_crate::abi::RawLog) -> Result where Self: Sized { #decode_log_impl } @@ -256,55 +261,57 @@ impl EventField { // these indexed param types according to // https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters fn topic_param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream { + let core_crate = ethers_core_crate(); match kind { ParamType::String | ParamType::Bytes | ParamType::Array(_) | ParamType::FixedArray(_, _) - | ParamType::Tuple(_) => quote! {ethers_core::abi::ParamType::FixedBytes(32)}, + | ParamType::Tuple(_) => quote! {#core_crate::abi::ParamType::FixedBytes(32)}, ty => param_type_quote(ty), } } fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream { + let core_crate = ethers_core_crate(); match kind { ParamType::Address => { - quote! {ethers_core::abi::ParamType::Address} + quote! {#core_crate::abi::ParamType::Address} } ParamType::Bytes => { - quote! {ethers_core::abi::ParamType::Bytes} + quote! {#core_crate::abi::ParamType::Bytes} } ParamType::Int(size) => { let size = Literal::usize_suffixed(*size); - quote! {ethers_core::abi::ParamType::Int(#size)} + quote! {#core_crate::abi::ParamType::Int(#size)} } ParamType::Uint(size) => { let size = Literal::usize_suffixed(*size); - quote! {ethers_core::abi::ParamType::Uint(#size)} + quote! {#core_crate::abi::ParamType::Uint(#size)} } ParamType::Bool => { - quote! {ethers_core::abi::ParamType::Bool} + quote! {#core_crate::abi::ParamType::Bool} } ParamType::String => { - quote! {ethers_core::abi::ParamType::String} + quote! {#core_crate::abi::ParamType::String} } ParamType::Array(ty) => { let ty = param_type_quote(&*ty); - quote! {ethers_core::abi::ParamType::Array(Box::new(#ty))} + quote! {#core_crate::abi::ParamType::Array(Box::new(#ty))} } ParamType::FixedBytes(size) => { let size = Literal::usize_suffixed(*size); - quote! {ethers_core::abi::ParamType::FixedBytes(#size)} + quote! {#core_crate::abi::ParamType::FixedBytes(#size)} } ParamType::FixedArray(ty, size) => { let ty = param_type_quote(&*ty); let size = Literal::usize_suffixed(*size); - quote! {ethers_core::abi::ParamType::FixedArray(Box::new(#ty),#size)} + quote! {#core_crate::abi::ParamType::FixedArray(Box::new(#ty),#size)} } ParamType::Tuple(tuple) => { let elements = tuple.iter().map(param_type_quote); quote! { - ethers_core::abi::ParamType::Tuple( + #core_crate::abi::ParamType::Tuple( ::std::vec![ #( #elements ),* ] @@ -318,6 +325,8 @@ fn derive_decode_from_log_impl( input: &DeriveInput, event: &Event, ) -> Result { + let core_crate = ethers_core_crate(); + let fields: Vec<_> = match input.data { Data::Struct(ref data) => match data.fields { Fields::Named(ref fields) => { @@ -416,16 +425,16 @@ fn derive_decode_from_log_impl( }, quote! { if topic_tokens.len() != topics.len() { - return Err(ethers_core::abi::Error::InvalidData); + return Err(#core_crate::abi::Error::InvalidData); } }, ) } else { ( quote! { - let event_signature = topics.get(0).ok_or(ethers_core::abi::Error::InvalidData)?; + let event_signature = topics.get(0).ok_or(#core_crate::abi::Error::InvalidData)?; if event_signature != &Self::signature() { - return Err(ethers_core::abi::Error::InvalidData); + return Err(#core_crate::abi::Error::InvalidData); } }, quote! { @@ -433,7 +442,7 @@ fn derive_decode_from_log_impl( }, quote! { if topic_tokens.len() != topics.len() - 1 { - return Err(ethers_core::abi::Error::InvalidData); + return Err(#core_crate::abi::Error::InvalidData); } }, ) @@ -447,9 +456,9 @@ fn derive_decode_from_log_impl( .all(|(idx, f)| f.index == idx) { quote! { - let topic_tokens = ethers_core::abi::decode(&topic_types, &flat_topics)?; + let topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?; #topic_tokens_len_check - let data_tokens = ethers_core::abi::decode(&data_types, data)?; + let data_tokens = #core_crate::abi::decode(&data_types, data)?; let tokens:Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect(); } } else { @@ -462,9 +471,9 @@ fn derive_decode_from_log_impl( }); quote! { - let mut topic_tokens = ethers_core::abi::decode(&topic_types, &flat_topics)?; + let mut topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?; #topic_tokens_len_check - let mut data_tokens = ethers_core::abi::decode(&data_types, &data)?; + let mut data_tokens = #core_crate::abi::decode(&data_types, &data)?; let mut tokens = Vec::with_capacity(topics.len() + data_tokens.len()); #( tokens.push(#swap_tokens); )* } @@ -472,7 +481,7 @@ fn derive_decode_from_log_impl( Ok(quote! { - let ethers_core::abi::RawLog {data, topics} = log; + let #core_crate::abi::RawLog {data, topics} = log; #signature_check @@ -483,7 +492,7 @@ fn derive_decode_from_log_impl( #tokens_init - ethers_core::abi::Detokenize::from_tokens(tokens).map_err(|_|ethers_core::abi::Error::InvalidData) + #core_crate::abi::Detokenize::from_tokens(tokens).map_err(|_|#core_crate::abi::Error::InvalidData) }) } @@ -669,8 +678,9 @@ fn parse_int_param_type(s: &str) -> Option { } fn signature(hash: &[u8]) -> proc_macro2::TokenStream { + let core_crate = ethers_core_crate(); let bytes = hash.iter().copied().map(Literal::u8_unsuffixed); - quote! {ethers_core::types::H256([#( #bytes ),*])} + quote! {#core_crate::types::H256([#( #bytes ),*])} } fn parse_event(abi: &str) -> Result { @@ -695,6 +705,7 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream { } fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { + let core_crate = ethers_core_crate(); let name = &input.ident; let generic_params = input.generics.params.iter().map(|p| quote! { #p }); let generic_params = quote! { #(#generic_params,)* }; @@ -719,13 +730,13 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { 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 } + quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize } }); let tokenize_predicates = quote! { #(#tokenize_predicates,)* }; let assignments = fields.named.iter().map(|f| { let name = f.ident.as_ref().expect("Named fields have names"); - quote_spanned! { f.span() => #name: ethers_core::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? } + quote_spanned! { f.span() => #name: #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? } }); let init_struct_impl = quote! { Self { #(#assignments,)* } }; @@ -745,12 +756,12 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { 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 } + quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize } }); let tokenize_predicates = quote! { #(#tokenize_predicates,)* }; let assignments = fields.unnamed.iter().map(|f| { - quote_spanned! { f.span() => ethers_core::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? } + quote_spanned! { f.span() => #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? } }); let init_struct_impl = quote! { Self(#(#assignments,)* ) }; @@ -794,7 +805,7 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { // can't encode an empty struct // TODO: panic instead? quote! { - ethers_core::abi::Token::Tuple(Vec::new()) + #core_crate::abi::Token::Tuple(Vec::new()) }, ), 1 => { @@ -819,9 +830,9 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { } _ => { let from_token = quote! { - if let ethers_core::abi::Token::Tuple(tokens) = token { + if let #core_crate::abi::Token::Tuple(tokens) = token { if tokens.len() != #params_len { - return Err(ethers_core::abi::InvalidOutputType(::std::format!( + return Err(#core_crate::abi::InvalidOutputType(::std::format!( "Expected {} tokens, got {}: {:?}", #params_len, tokens.len(), @@ -833,7 +844,7 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { Ok(#init_struct_impl) } else { - Err(ethers_core::abi::InvalidOutputType(::std::format!( + Err(#core_crate::abi::InvalidOutputType(::std::format!( "Expected Tuple, got {:?}", token ))) @@ -841,7 +852,7 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { }; let into_token = quote! { - ethers_core::abi::Token::Tuple( + #core_crate::abi::Token::Tuple( ::std::vec![ #into_token_impl ] @@ -852,23 +863,23 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream { }; quote! { - impl<#generic_params> ethers_core::abi::Tokenizable for #name<#generic_args> + impl<#generic_params> #core_crate::abi::Tokenizable for #name<#generic_args> where #generic_predicates #tokenize_predicates { - fn from_token(token: ethers_core::abi::Token) -> Result where + fn from_token(token: #core_crate::abi::Token) -> Result where Self: Sized { #from_token_impl } - fn into_token(self) -> ethers_core::abi::Token { + fn into_token(self) -> #core_crate::abi::Token { #into_token_impl } } - impl<#generic_params> ethers_core::abi::TokenizableItem for #name<#generic_args> + impl<#generic_params> #core_crate::abi::TokenizableItem for #name<#generic_args> where #generic_predicates #tokenize_predicates