From e72636210c35cc4690914a6e98ffad4b10b3880b Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 5 Nov 2021 14:00:01 +0100 Subject: [PATCH] Refactor crate determination in new ethers-macro crate (#555) * fix: compute ethers-core path in derive eip712 Co-authored-by: Ryan * refactor: move crate determination to ethers-macro * docs: update fmt command * fix: change cargo_metadata dep to optional Co-authored-by: Ryan --- CONTRIBUTING.md | 2 +- Cargo.lock | 2 +- .../ethers-contract-abigen/Cargo.toml | 3 +- .../ethers-contract-abigen/src/contract.rs | 11 ++- .../src/contract/common.rs | 2 +- .../src/contract/events.rs | 19 +++-- .../src/contract/methods.rs | 9 ++- .../src/contract/structs.rs | 15 ++-- .../src/contract/types.rs | 6 +- .../ethers-contract-abigen/src/lib.rs | 2 +- .../ethers-contract-abigen/src/util.rs | 74 ------------------ .../ethers-contract-derive/src/abi_ty.rs | 2 +- .../ethers-contract-derive/src/call.rs | 6 +- .../ethers-contract-derive/src/display.rs | 3 +- .../ethers-contract-derive/src/event.rs | 7 +- .../ethers-contract-derive/src/utils.rs | 3 +- ethers-core/Cargo.toml | 4 + ethers-core/ethers-derive-eip712/Cargo.toml | 4 +- ethers-core/ethers-derive-eip712/src/lib.rs | 27 ++++--- ethers-core/src/lib.rs | 2 + ethers-core/src/macros/ethers_crate.rs | 75 +++++++++++++++++++ ethers-core/src/macros/mod.rs | 4 + 22 files changed, 153 insertions(+), 129 deletions(-) create mode 100644 ethers-core/src/macros/ethers_crate.rs create mode 100644 ethers-core/src/macros/mod.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 22a18ef9..42525537 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -118,7 +118,7 @@ This section lists some commonly needed commands. ``` cargo check --all-features -cargo fmt --all +cargo +nightly fmt --all cargo build --all-features cargo test --all-features ``` diff --git a/Cargo.lock b/Cargo.lock index c0f52c45..58cff6f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,7 +950,6 @@ version = "0.5.3" dependencies = [ "Inflector", "anyhow", - "cargo_metadata", "cfg-if 1.0.0", "ethers-core", "getrandom 0.2.3", @@ -985,6 +984,7 @@ dependencies = [ "arrayvec 0.7.2", "bincode", "bytes", + "cargo_metadata", "convert_case", "ecdsa", "elliptic-curve", diff --git a/ethers-contract/ethers-contract-abigen/Cargo.toml b/ethers-contract/ethers-contract-abigen/Cargo.toml index 8492d37d..34eddb66 100644 --- a/ethers-contract/ethers-contract-abigen/Cargo.toml +++ b/ethers-contract/ethers-contract-abigen/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/gakonst/ethers-rs" keywords = ["ethereum", "web3", "celo", "ethers"] [dependencies] -ethers-core = { version = "^0.5.0", path = "../../ethers-core" } +ethers-core = { version = "^0.5.0", path = "../../ethers-core", features = ["macros"] } anyhow = "1.0.37" Inflector = "0.11" @@ -23,7 +23,6 @@ serde = { version = "1.0.124", features = ["derive"] } hex = { version = "0.4.2", default-features = false, features = ["std"] } reqwest = { version = "0.11.3", features = ["blocking"] } once_cell = "1.8.0" -cargo_metadata = "0.14.0" cfg-if = "1.0.0" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 95a5850c..2d51cc5c 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -8,7 +8,10 @@ mod types; use super::{util, Abigen}; use crate::{contract::structs::InternalStructs, rawabi::RawAbi}; use anyhow::{anyhow, Context as _, Result}; -use ethers_core::abi::{Abi, AbiParser}; +use ethers_core::{ + abi::{Abi, AbiParser}, + macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}, +}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::quote; @@ -111,9 +114,9 @@ impl Context { // 5. Declare the structs parsed from the human readable abi let abi_structs_decl = self.abi_structs()?; - let ethers_core = util::ethers_core_crate(); - let ethers_contract = util::ethers_contract_crate(); - let ethers_providers = util::ethers_providers_crate(); + let ethers_core = ethers_core_crate(); + let ethers_contract = ethers_contract_crate(); + let ethers_providers = ethers_providers_crate(); let contract = quote! { #struct_decl diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs index 11ef39f8..05d787ff 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs @@ -3,7 +3,7 @@ use super::{util, Context}; use proc_macro2::TokenStream; use quote::quote; -use super::util::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}; +use ethers_core::macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}; pub(crate) fn imports(name: &str) -> TokenStream { let doc = util::expand_doc(&format!("{} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs", name)); diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs index fe765a71..f48eabc4 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs @@ -1,6 +1,9 @@ use super::{types, util, Context}; use anyhow::Result; -use ethers_core::abi::{Event, EventExt, EventParam, ParamType, SolStruct}; +use ethers_core::{ + abi::{Event, EventExt, EventParam, ParamType, SolStruct}, + macros::{ethers_contract_crate, ethers_core_crate}, +}; use inflector::Inflector; use proc_macro2::{Ident, Literal, TokenStream}; use quote::quote; @@ -61,8 +64,8 @@ impl Context { let enum_name = self.expand_event_enum_name(); - let ethers_core = util::ethers_core_crate(); - let ethers_contract = util::ethers_contract_crate(); + let ethers_core = ethers_core_crate(); + let ethers_contract = ethers_contract_crate(); quote! { #[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType)] @@ -106,7 +109,7 @@ impl Context { let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect(); let mut iter = sorted_events.values().flatten(); - let ethers_contract = util::ethers_contract_crate(); + let ethers_contract = ethers_contract_crate(); if let Some(event) = iter.next() { let ty = if iter.next().is_some() { @@ -134,7 +137,7 @@ impl Context { /// If a complex types matches with a struct previously parsed by the AbiParser, /// we can replace it fn expand_input_type(&self, input: &EventParam) -> Result { - let ethers_core = util::ethers_core_crate(); + let ethers_core = ethers_core_crate(); Ok(match (&input.kind, input.indexed) { (ParamType::Array(ty), true) => { if let ParamType::Tuple(..) = **ty { @@ -202,7 +205,7 @@ impl Context { /// Expands into a single method for contracting an event stream. fn expand_filter(&self, event: &Event) -> TokenStream { - let ethers_contract = util::ethers_contract_crate(); + let ethers_contract = ethers_contract_crate(); let alias = self.event_aliases.get(&event.abi_signature()).cloned(); let name = if let Some(id) = alias.clone() { @@ -246,7 +249,7 @@ impl Context { let derives = util::expand_derives(&self.event_derives); - let ethers_contract = util::ethers_contract_crate(); + let ethers_contract = ethers_contract_crate(); Ok(quote! { #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)] @@ -318,7 +321,7 @@ mod tests { /// quasi-quoting for code generation. We do this to avoid allocating at runtime fn expand_hash(hash: Hash) -> TokenStream { let bytes = hash.as_bytes().iter().copied().map(Literal::u8_unsuffixed); - let ethers_core = util::ethers_core_crate(); + let ethers_core = ethers_core_crate(); quote! { #ethers_core::types::H256([#( #bytes ),*]) diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 9fdca12f..705aab4a 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -8,6 +8,7 @@ use syn::Ident; use ethers_core::{ abi::{Function, FunctionExt, Param, ParamType}, + macros::{ethers_contract_crate, ethers_core_crate}, types::Selector, }; @@ -63,7 +64,7 @@ impl Context { function.selector() ); let abi_signature_doc = util::expand_doc(&doc); - let ethers_contract = util::ethers_contract_crate(); + let ethers_contract = ethers_contract_crate(); // use the same derives as for events let derives = util::expand_derives(&self.event_derives); @@ -98,8 +99,8 @@ impl Context { return Ok(struct_def_tokens) } - let ethers_core = util::ethers_core_crate(); - let ethers_contract = util::ethers_contract_crate(); + let ethers_core = ethers_core_crate(); + let ethers_contract = ethers_contract_crate(); let enum_name = self.expand_calls_enum_name(); Ok(quote! { @@ -238,7 +239,7 @@ impl Context { // TODO use structs let outputs = expand_fn_outputs(&function.outputs)?; - let ethers_contract = util::ethers_contract_crate(); + let ethers_contract = ethers_contract_crate(); let result = quote! { #ethers_contract::builders::ContractCall }; diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs index 7135f1b4..526a8dcf 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs @@ -6,10 +6,13 @@ use inflector::Inflector; use proc_macro2::TokenStream; use quote::quote; -use ethers_core::abi::{ - param_type::Reader, - struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType}, - ParamType, SolStruct, +use ethers_core::{ + abi::{ + param_type::Reader, + struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType}, + ParamType, SolStruct, + }, + macros::ethers_contract_crate, }; use crate::{ @@ -123,7 +126,7 @@ impl Context { // use the same derives as for events let derives = util::expand_derives(&self.event_derives); - let ethers_contract = util::ethers_contract_crate(); + let ethers_contract = ethers_contract_crate(); Ok(quote! { #abi_signature_doc #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #derives)] @@ -184,7 +187,7 @@ impl Context { let derives = &self.event_derives; let derives = quote! {#(#derives),*}; - let ethers_contract = util::ethers_contract_crate(); + let ethers_contract = ethers_contract_crate(); Ok(quote! { #abi_signature_doc diff --git a/ethers-contract/ethers-contract-abigen/src/contract/types.rs b/ethers-contract/ethers-contract-abigen/src/contract/types.rs index 27ba9565..e44def58 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/types.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/types.rs @@ -1,12 +1,10 @@ use anyhow::{anyhow, Result}; -use ethers_core::abi::ParamType; +use ethers_core::{abi::ParamType, macros::ethers_core_crate}; use proc_macro2::{Literal, TokenStream}; use quote::quote; -use super::util; - pub(crate) fn expand(kind: &ParamType) -> Result { - let ethers_core = util::ethers_core_crate(); + let ethers_core = ethers_core_crate(); match kind { ParamType::Address => Ok(quote! { #ethers_core::types::Address }), diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs index 6ef9ac24..238d78d1 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::{ethers_contract_crate, ethers_core_crate, parse_address}; +pub use util::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 7fd662c5..2706352a 100644 --- a/ethers-contract/ethers-contract-abigen/src/util.rs +++ b/ethers-contract/ethers-contract-abigen/src/util.rs @@ -1,86 +1,12 @@ use ethers_core::types::Address; use anyhow::{anyhow, Result}; -use cargo_metadata::{DependencyKind, MetadataCommand}; use inflector::Inflector; -use once_cell::sync::Lazy; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::quote; 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, &'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") -} -pub fn ethers_providers_crate() -> Path { - syn::parse_str(ETHERS_CRATES.2).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. -/// -/// This process is a bit hacky, we run `cargo metadata` internally which -/// resolves the current package but creates a new `Cargo.lock` file in the -/// process. This is not a problem for regular workspaces but becomes an issue -/// during publishing with `cargo publish` if the project does not ignore -/// `Cargo.lock` in `.gitignore`, because then cargo can't proceed with -/// publishing the crate because the created `Cargo.lock` leads to a modified -/// workspace, not the `CARGO_MANIFEST_DIR` but the workspace `cargo publish` -/// created in `./target/package/..`. Therefore we check prior to executing -/// `cargo metadata` if a `Cargo.lock` file exists and delete it afterwards if -/// it was created by `cargo metadata`. -pub fn determine_ethers_crates() -> (&'static str, &'static str, &'static str) { - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("No Manifest found"); - - // check if the lock file exists, if it's missing we need to clean up afterward - let lock_file = format!("{}/Cargo.lock", manifest_dir); - let needs_lock_file_cleanup = !std::path::Path::new(&lock_file).exists(); - - let res = MetadataCommand::new() - .manifest_path(&format!("{}/Cargo.toml", manifest_dir)) - .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", "ethers::providers")) - }, - ) - }) - }) - .unwrap_or(("ethers_core", "ethers_contract", "ethers_providers")); - - if needs_lock_file_cleanup { - // delete the `Cargo.lock` file that was created by `cargo metadata` - // if the package is not part of a workspace - let _ = std::fs::remove_file(lock_file); - } - - res -} - /// Expands a identifier string into an token. pub fn ident(name: &str) -> Ident { Ident::new(name, Span::call_site()) diff --git a/ethers-contract/ethers-contract-derive/src/abi_ty.rs b/ethers-contract/ethers-contract-derive/src/abi_ty.rs index 8ed4265c..6e153543 100644 --- a/ethers-contract/ethers-contract-derive/src/abi_ty.rs +++ b/ethers-contract/ethers-contract-derive/src/abi_ty.rs @@ -1,6 +1,6 @@ //! Helper functions for deriving `EthAbiType` -use ethers_contract_abigen::ethers_core_crate; +use ethers_core::macros::ethers_core_crate; use proc_macro2::{Ident, Literal, TokenStream}; use quote::{quote, quote_spanned}; use syn::{parse::Error, spanned::Spanned as _, Data, DeriveInput, Fields, Variant}; diff --git a/ethers-contract/ethers-contract-derive/src/call.rs b/ethers-contract/ethers-contract-derive/src/call.rs index 4a9d2aed..f64ddaa5 100644 --- a/ethers-contract/ethers-contract-derive/src/call.rs +++ b/ethers-contract/ethers-contract-derive/src/call.rs @@ -1,11 +1,13 @@ //! Helper functions for deriving `EthCall` -use ethers_contract_abigen::{ethers_contract_crate, ethers_core_crate}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{parse::Error, spanned::Spanned as _, AttrStyle, DeriveInput, Lit, Meta, NestedMeta}; -use ethers_core::abi::{param_type::Reader, AbiParser, Function, FunctionExt, Param, ParamType}; +use ethers_core::{ + abi::{param_type::Reader, AbiParser, Function, FunctionExt, Param, ParamType}, + macros::{ethers_contract_crate, ethers_core_crate}, +}; use crate::{abi_ty, utils}; diff --git a/ethers-contract/ethers-contract-derive/src/display.rs b/ethers-contract/ethers-contract-derive/src/display.rs index 0bacf3f6..45918207 100644 --- a/ethers-contract/ethers-contract-derive/src/display.rs +++ b/ethers-contract/ethers-contract-derive/src/display.rs @@ -4,8 +4,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{parse::Error, spanned::Spanned as _, Data, DeriveInput, Fields, Index}; -use ethers_contract_abigen::ethers_core_crate; -use ethers_core::abi::ParamType; +use ethers_core::{abi::ParamType, macros::ethers_core_crate}; use crate::utils; diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs index 17e3ed2e..e391078c 100644 --- a/ethers-contract/ethers-contract-derive/src/event.rs +++ b/ethers-contract/ethers-contract-derive/src/event.rs @@ -1,6 +1,6 @@ //! Helper functions for deriving `EthEvent` -use ethers_contract_abigen::{ethers_contract_crate, ethers_core_crate, Source}; +use ethers_contract_abigen::Source; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ @@ -8,7 +8,10 @@ use syn::{ NestedMeta, }; -use ethers_core::abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType}; +use ethers_core::{ + abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType}, + macros::{ethers_contract_crate, ethers_core_crate}, +}; use hex::FromHex; use crate::{abi_ty, utils}; diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs index 335e0e90..c755bda5 100644 --- a/ethers-contract/ethers-contract-derive/src/utils.rs +++ b/ethers-contract/ethers-contract-derive/src/utils.rs @@ -1,5 +1,4 @@ -use ethers_contract_abigen::ethers_core_crate; -use ethers_core::{abi::ParamType, types::Selector}; +use ethers_core::{abi::ParamType, macros::ethers_core_crate, types::Selector}; use proc_macro2::Literal; use quote::quote; use syn::{ diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index 5a094d0c..fd365ae9 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -31,6 +31,9 @@ bytes = { version = "1.1.0", features = ["serde"] } hex = { version = "0.4.3", default-features = false, features = ["std"] } once_cell = "1.8.0" +# macros feature enabled dependencies +cargo_metadata = { version = "0.14.0", optional = true } + # eip712 feature enabled dependencies convert_case = { version = "0.4.0", optional = true } syn = { version = "1.0.81", optional = true } @@ -55,6 +58,7 @@ celo = ["legacy"] # celo support extends the transaction format with extra field setup = ["tokio", "futures-util"] # async support for concurrent setup legacy = [] eip712 = ["convert_case", "syn", "quote", "proc-macro2"] +macros = ["syn", "cargo_metadata"] [package.metadata.docs.rs] all-features = true diff --git a/ethers-core/ethers-derive-eip712/Cargo.toml b/ethers-core/ethers-derive-eip712/Cargo.toml index d8a2efb0..d02de967 100644 --- a/ethers-core/ethers-derive-eip712/Cargo.toml +++ b/ethers-core/ethers-derive-eip712/Cargo.toml @@ -10,7 +10,7 @@ proc-macro = true [dependencies] quote = "1.0.9" syn = "1.0.77" -ethers-core = { version = "^0.5.0", path = "../", default-features = false, features = ["eip712"] } +ethers-core = { version = "^0.5.0", path = "../", default-features = false, features = ["eip712", "macros"] } hex = "0.4.3" serde = "1.0.130" serde_json = "1.0.68" @@ -19,4 +19,4 @@ proc-macro2 = "1.0.29" [dev-dependencies] ethers-contract = { version = "^0.5.0", path = "../../ethers-contract"} ethers-contract-derive = { version = "^0.5.0", path = "../../ethers-contract/ethers-contract-derive" } -ethers-signers = { version = "^0.5.0", path = "../../ethers-signers" } \ No newline at end of file +ethers-signers = { version = "^0.5.0", path = "../../ethers-signers" } diff --git a/ethers-core/ethers-derive-eip712/src/lib.rs b/ethers-core/ethers-derive-eip712/src/lib.rs index f2260cbf..5fe9594e 100644 --- a/ethers-core/ethers-derive-eip712/src/lib.rs +++ b/ethers-core/ethers-derive-eip712/src/lib.rs @@ -61,7 +61,7 @@ //! determine if there is a nested eip712 struct. However, this work is not yet complete. use std::convert::TryFrom; -use ethers_core::types::transaction::eip712; +use ethers_core::{macros::ethers_core_crate, types::transaction::eip712}; use proc_macro::TokenStream; use quote::quote; @@ -104,13 +104,16 @@ fn impl_eip_712_macro(ast: &syn::DeriveInput) -> TokenStream { Err(e) => return TokenStream::from(e), }; - // Compute the type hash for the derived struct using the parsed fields from above; + // Compute the type hash for the derived struct using the parsed fields from above. let type_hash = hex::encode(eip712::make_type_hash(primary_type.clone().to_string(), &parsed_fields)); + // Use reference to ethers_core instead of directly using the crate itself. + let ethers_core = ethers_core_crate(); + let implementation = quote! { impl Eip712 for #primary_type { - type Error = ethers_core::types::transaction::eip712::Eip712Error; + type Error = #ethers_core::types::transaction::eip712::Eip712Error; fn type_hash() -> Result<[u8; 32], Self::Error> { use std::convert::TryFrom; @@ -127,34 +130,34 @@ fn impl_eip_712_macro(ast: &syn::DeriveInput) -> TokenStream { Ok(byte_array) } - fn domain(&self) -> Result { - let domain: ethers_core::types::transaction::eip712::EIP712Domain = serde_json::from_str(#domain_str)?; + fn domain(&self) -> Result<#ethers_core::types::transaction::eip712::EIP712Domain, Self::Error> { + let domain: #ethers_core::types::transaction::eip712::EIP712Domain = serde_json::from_str(#domain_str)?; Ok(domain) } fn struct_hash(&self) -> Result<[u8; 32], Self::Error> { - use ethers_core::abi::Tokenizable; - let mut items = vec![ethers_core::abi::Token::Uint( - ethers_core::types::U256::from(&Self::type_hash()?[..]), + use #ethers_core::abi::Tokenizable; + let mut items = vec![#ethers_core::abi::Token::Uint( + #ethers_core::types::U256::from(&Self::type_hash()?[..]), )]; - if let ethers_core::abi::Token::Tuple(tokens) = self.clone().into_token() { + if let #ethers_core::abi::Token::Tuple(tokens) = self.clone().into_token() { for token in tokens { match &token { - ethers_core::abi::Token::Tuple(t) => { + #ethers_core::abi::Token::Tuple(t) => { // TODO: check for nested Eip712 Type; // Challenge is determining the type hash return Err(Self::Error::NestedEip712StructNotImplemented); }, _ => { - items.push(ethers_core::types::transaction::eip712::encode_eip712_type(token)); + items.push(#ethers_core::types::transaction::eip712::encode_eip712_type(token)); } } } } - let struct_hash = ethers_core::utils::keccak256(ethers_core::abi::encode( + let struct_hash = #ethers_core::utils::keccak256(#ethers_core::abi::encode( &items, )); diff --git a/ethers-core/src/lib.rs b/ethers-core/src/lib.rs index 71dd77cf..4f1f8612 100644 --- a/ethers-core/src/lib.rs +++ b/ethers-core/src/lib.rs @@ -51,6 +51,8 @@ pub mod abi; /// Various utilities pub mod utils; +pub mod macros; + // re-export rand to avoid potential confusion when there's rand version mismatches pub use rand; diff --git a/ethers-core/src/macros/ethers_crate.rs b/ethers-core/src/macros/ethers_crate.rs new file mode 100644 index 00000000..19ee9c14 --- /dev/null +++ b/ethers-core/src/macros/ethers_crate.rs @@ -0,0 +1,75 @@ +use cargo_metadata::{DependencyKind, MetadataCommand}; +use once_cell::sync::Lazy; +use syn::Path; + +/// See `determine_ethers_crates` +/// +/// This ensures that the `MetadataCommand` is only run once +static ETHERS_CRATES: Lazy<(&'static str, &'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") +} +pub fn ethers_providers_crate() -> Path { + syn::parse_str(ETHERS_CRATES.2).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. +/// +/// This process is a bit hacky, we run `cargo metadata` internally which +/// resolves the current package but creates a new `Cargo.lock` file in the +/// process. This is not a problem for regular workspaces but becomes an issue +/// during publishing with `cargo publish` if the project does not ignore +/// `Cargo.lock` in `.gitignore`, because then cargo can't proceed with +/// publishing the crate because the created `Cargo.lock` leads to a modified +/// workspace, not the `CARGO_MANIFEST_DIR` but the workspace `cargo publish` +/// created in `./target/package/..`. Therefore we check prior to executing +/// `cargo metadata` if a `Cargo.lock` file exists and delete it afterwards if +/// it was created by `cargo metadata`. +pub fn determine_ethers_crates() -> (&'static str, &'static str, &'static str) { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("No Manifest found"); + + // check if the lock file exists, if it's missing we need to clean up afterward + let lock_file = format!("{}/Cargo.lock", manifest_dir); + let needs_lock_file_cleanup = !std::path::Path::new(&lock_file).exists(); + + let res = MetadataCommand::new() + .manifest_path(&format!("{}/Cargo.toml", manifest_dir)) + .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", "ethers::providers")) + }, + ) + }) + }) + .unwrap_or(("ethers_core", "ethers_contract", "ethers_providers")); + + if needs_lock_file_cleanup { + // delete the `Cargo.lock` file that was created by `cargo metadata` + // if the package is not part of a workspace + let _ = std::fs::remove_file(lock_file); + } + + res +} diff --git a/ethers-core/src/macros/mod.rs b/ethers-core/src/macros/mod.rs new file mode 100644 index 00000000..dae67eba --- /dev/null +++ b/ethers-core/src/macros/mod.rs @@ -0,0 +1,4 @@ +mod ethers_crate; + +#[cfg(feature = "macros")] +pub use ethers_crate::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate};