fix(abigen): generate correct imports depending on ethers crate usage (#413)

This commit is contained in:
Georgios Konstantopoulos 2021-08-28 21:53:01 +03:00 committed by GitHub
parent 526617fcbd
commit 6cc20688d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 88 additions and 50 deletions

View File

@ -77,6 +77,10 @@ impl Context {
// 5. Declare the structs parsed from the human readable abi // 5. Declare the structs parsed from the human readable abi
let abi_structs_decl = cx.abi_structs()?; let abi_structs_decl = cx.abi_structs()?;
let ethers_core = util::ethers_core_crate();
let ethers_contract = util::ethers_contract_crate();
let ethers_providers = util::ethers_providers_crate();
Ok(quote! { Ok(quote! {
// export all the created data types // export all the created data types
pub use #name_mod::*; pub use #name_mod::*;
@ -86,12 +90,12 @@ impl Context {
#imports #imports
#struct_decl #struct_decl
impl<'a, M: ethers_providers::Middleware> #name<M> { impl<'a, M: #ethers_providers::Middleware> #name<M> {
/// Creates a new contract instance with the specified `ethers` /// Creates a new contract instance with the specified `ethers`
/// client at the given `Address`. The contract derefs to a `ethers::Contract` /// client at the given `Address`. The contract derefs to a `ethers::Contract`
/// object /// object
pub fn new<T: Into<ethers_core::types::Address>>(address: T, client: ::std::sync::Arc<M>) -> Self { pub fn new<T: Into<#ethers_core::types::Address>>(address: T, client: ::std::sync::Arc<M>) -> Self {
let contract = ethers_contract::Contract::new(address.into(), #abi_name.clone(), client); let contract = #ethers_contract::Contract::new(address.into(), #abi_name.clone(), client);
Self(contract) Self(contract)
} }

View File

@ -4,9 +4,15 @@ use ethers_core::types::Address;
use proc_macro2::{Literal, TokenStream}; use proc_macro2::{Literal, TokenStream};
use quote::quote; use quote::quote;
use super::util::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate};
pub(crate) fn imports(name: &str) -> TokenStream { 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)); let doc = util::expand_doc(&format!("{} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs", name));
let ethers_core = ethers_core_crate();
let ethers_providers = ethers_providers_crate();
let ethers_contract = ethers_contract_crate();
quote! { quote! {
#![allow(clippy::enum_variant_names)] #![allow(clippy::enum_variant_names)]
#![allow(dead_code)] #![allow(dead_code)]
@ -14,15 +20,12 @@ pub(crate) fn imports(name: &str) -> TokenStream {
#doc #doc
use std::sync::Arc; use std::sync::Arc;
use ethers::{ use #ethers_core::{
core::{
self as ethers_core,
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable}, abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
types::*, // import all the types so that we can codegen for everything types::*, // import all the types so that we can codegen for everything
},
contract::{self as ethers_contract, Contract, builders::{ContractCall, Event}, Lazy},
providers::{self as ethers_providers,Middleware},
}; };
use #ethers_contract::{Contract, builders::{ContractCall, Event}, Lazy};
use #ethers_providers::Middleware;
} }
} }
@ -31,14 +34,18 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) ->
let name = &cx.contract_name; let name = &cx.contract_name;
let abi = &cx.abi_str; let abi = &cx.abi_str;
let ethers_core = ethers_core_crate();
let ethers_providers = ethers_providers_crate();
let ethers_contract = ethers_contract_crate();
let abi_parse = if !cx.human_readable { let abi_parse = if !cx.human_readable {
quote! { quote! {
pub static #abi_name: ethers_contract::Lazy<ethers_core::abi::Abi> = ethers_contract::Lazy::new(|| serde_json::from_str(#abi) pub static #abi_name: #ethers_contract::Lazy<#ethers_core::abi::Abi> = #ethers_contract::Lazy::new(|| serde_json::from_str(#abi)
.expect("invalid abi")); .expect("invalid abi"));
} }
} else { } else {
quote! { quote! {
pub static #abi_name: ethers_contract::Lazy<ethers_core::abi::Abi> = ethers_contract::Lazy::new(|| ethers::core::abi::parse_abi_str(#abi) pub static #abi_name: #ethers_contract::Lazy<#ethers_core::abi::Abi> = #ethers_contract::Lazy::new(|| #ethers_core::abi::parse_abi_str(#abi)
.expect("invalid abi")); .expect("invalid abi"));
} }
}; };
@ -49,17 +56,17 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) ->
// Struct declaration // Struct declaration
#[derive(Clone)] #[derive(Clone)]
pub struct #name<M>(ethers_contract::Contract<M>); pub struct #name<M>(#ethers_contract::Contract<M>);
// Deref to the inner contract in order to access more specific functions functions // Deref to the inner contract in order to access more specific functions functions
impl<M> std::ops::Deref for #name<M> { impl<M> std::ops::Deref for #name<M> {
type Target = ethers_contract::Contract<M>; type Target = #ethers_contract::Contract<M>;
fn deref(&self) -> &Self::Target { &self.0 } fn deref(&self) -> &Self::Target { &self.0 }
} }
impl<M: ethers_providers::Middleware> std::fmt::Debug for #name<M> { impl<M: #ethers_providers::Middleware> std::fmt::Debug for #name<M> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_tuple(stringify!(#name)) f.debug_tuple(stringify!(#name))
.field(&self.address()) .field(&self.address())

View File

@ -61,25 +61,28 @@ impl Context {
let enum_name = self.expand_event_enum_name(); let enum_name = self.expand_event_enum_name();
let ethers_core = util::ethers_core_crate();
let ethers_contract = util::ethers_contract_crate();
quote! { quote! {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum #enum_name { pub enum #enum_name {
#(#variants(#variants)),* #(#variants(#variants)),*
} }
impl ethers_core::abi::Tokenizable for #enum_name { impl #ethers_core::abi::Tokenizable for #enum_name {
fn from_token(token: ethers_core::abi::Token) -> Result<Self, ethers_core::abi::InvalidOutputType> where fn from_token(token: #ethers_core::abi::Token) -> Result<Self, #ethers_core::abi::InvalidOutputType> where
Self: Sized { Self: Sized {
#( #(
if let Ok(decoded) = #variants::from_token(token.clone()) { if let Ok(decoded) = #variants::from_token(token.clone()) {
return Ok(#enum_name::#variants(decoded)) return Ok(#enum_name::#variants(decoded))
} }
)* )*
Err(ethers_core::abi::InvalidOutputType("Failed to decode all event variants".to_string())) Err(#ethers_core::abi::InvalidOutputType("Failed to decode all event variants".to_string()))
} }
fn into_token(self) -> ethers_core::abi::Token { fn into_token(self) -> #ethers_core::abi::Token {
match self { match self {
#( #(
#enum_name::#variants(element) => element.into_token() #enum_name::#variants(element) => element.into_token()
@ -87,10 +90,10 @@ impl Context {
} }
} }
} }
impl ethers_core::abi::TokenizableItem for #enum_name { } impl #ethers_core::abi::TokenizableItem for #enum_name { }
impl ethers_contract::EthLogDecode for #enum_name { impl #ethers_contract::EthLogDecode for #enum_name {
fn decode_log(log: &ethers_core::abi::RawLog) -> Result<Self, ethers_core::abi::Error> fn decode_log(log: &#ethers_core::abi::RawLog) -> Result<Self, #ethers_core::abi::Error>
where where
Self: Sized, Self: Sized,
{ {
@ -99,7 +102,7 @@ impl Context {
return Ok(#enum_name::#variants(decoded)) return Ok(#enum_name::#variants(decoded))
} }
)* )*
Err(ethers_core::abi::Error::InvalidData) Err(#ethers_core::abi::Error::InvalidData)
} }
} }
} }
@ -115,6 +118,7 @@ impl Context {
let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect(); let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect();
let mut iter = sorted_events.values().flatten(); let mut iter = sorted_events.values().flatten();
let ethers_contract = util::ethers_contract_crate();
if let Some(event) = iter.next() { if let Some(event) = iter.next() {
let ty = if iter.next().is_some() { let ty = if iter.next().is_some() {
@ -124,8 +128,8 @@ impl Context {
}; };
quote! { quote! {
/// Returns an [`Event`](ethers_contract::builders::Event) builder for all events of this contract /// Returns an [`Event`](#ethers_contract::builders::Event) builder for all events of this contract
pub fn events(&self) -> ethers_contract::builders::Event<M, #ty> { pub fn events(&self) -> #ethers_contract::builders::Event<M, #ty> {
self.0.event_with_filter(Default::default()) self.0.event_with_filter(Default::default())
} }
} }
@ -142,6 +146,8 @@ impl Context {
/// If a complex types matches with a struct previously parsed by the AbiParser, /// If a complex types matches with a struct previously parsed by the AbiParser,
/// we can replace it /// we can replace it
fn expand_input_type(&self, input: &EventParam) -> Result<TokenStream> { fn expand_input_type(&self, input: &EventParam) -> Result<TokenStream> {
let ethers_core = util::ethers_core_crate();
Ok(match (&input.kind, input.indexed) { Ok(match (&input.kind, input.indexed) {
(ParamType::Array(ty), true) => { (ParamType::Array(ty), true) => {
if let ParamType::Tuple(..) = **ty { if let ParamType::Tuple(..) = **ty {
@ -156,7 +162,7 @@ impl Context {
return Ok(quote! {::std::vec::Vec<#ty>}); return Ok(quote! {::std::vec::Vec<#ty>});
} }
} }
quote! { ethers_core::types::H256 } quote! { #ethers_core::types::H256 }
} }
(ParamType::FixedArray(ty, size), true) => { (ParamType::FixedArray(ty, size), true) => {
if let ParamType::Tuple(..) = **ty { if let ParamType::Tuple(..) = **ty {
@ -172,7 +178,7 @@ impl Context {
return Ok(quote! {[#ty; #size]}); return Ok(quote! {[#ty; #size]});
} }
} }
quote! { ethers_core::types::H256 } quote! { #ethers_core::types::H256 }
} }
(ParamType::Tuple(..), true) => { (ParamType::Tuple(..), true) => {
// represents an struct // represents an struct
@ -185,11 +191,11 @@ impl Context {
{ {
quote! {#ty} quote! {#ty}
} else { } else {
quote! { ethers_core::types::H256 } quote! { #ethers_core::types::H256 }
} }
} }
(ParamType::Bytes, true) | (ParamType::String, true) => { (ParamType::Bytes, true) | (ParamType::String, true) => {
quote! { ethers_core::types::H256 } quote! { #ethers_core::types::H256 }
} }
(kind, _) => types::expand(kind)?, (kind, _) => types::expand(kind)?,
}) })
@ -213,6 +219,8 @@ impl Context {
/// Expands into a single method for contracting an event stream. /// Expands into a single method for contracting an event stream.
fn expand_filter(&self, event: &Event) -> TokenStream { fn expand_filter(&self, event: &Event) -> TokenStream {
let ethers_contract = util::ethers_contract_crate();
// append `filter` to disambiguate with potentially conflicting // append `filter` to disambiguate with potentially conflicting
// function names // function names
let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case())); let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case()));
@ -222,7 +230,7 @@ impl Context {
let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name)); let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
quote! { quote! {
#doc #doc
pub fn #name(&self) -> ethers_contract::builders::Event<M, #result> { pub fn #name(&self) -> #ethers_contract::builders::Event<M, #result> {
self.0.event() self.0.event()
} }
} }
@ -247,8 +255,10 @@ impl Context {
let abi_signature = event.abi_signature(); let abi_signature = event.abi_signature();
let event_abi_name = &event.name; let event_abi_name = &event.name;
let ethers_contract = util::ethers_contract_crate();
Ok(quote! { Ok(quote! {
#[derive(Clone, Debug, Default, Eq, PartialEq, ethers_contract::EthEvent, #derives)] #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthEvent, #derives)]
#[ethevent( name = #event_abi_name, abi = #abi_signature )] #[ethevent( name = #event_abi_name, abi = #abi_signature )]
pub #data_type_definition pub #data_type_definition
}) })
@ -351,9 +361,10 @@ fn expand_derives(derives: &[Path]) -> TokenStream {
/// quasi-quoting for code generation. We do this to avoid allocating at runtime /// quasi-quoting for code generation. We do this to avoid allocating at runtime
fn expand_hash(hash: Hash) -> TokenStream { fn expand_hash(hash: Hash) -> TokenStream {
let bytes = hash.as_bytes().iter().copied().map(Literal::u8_unsuffixed); let bytes = hash.as_bytes().iter().copied().map(Literal::u8_unsuffixed);
let ethers_core = util::ethers_core_crate();
quote! { quote! {
ethers_core::types::H256([#( #bytes ),*]) #ethers_core::types::H256([#( #bytes ),*])
} }
} }

View File

@ -105,7 +105,11 @@ impl Context {
// TODO use structs // TODO use structs
let outputs = expand_fn_outputs(&function.outputs)?; let outputs = expand_fn_outputs(&function.outputs)?;
let result = quote! { ethers_contract::builders::ContractCall<M, #outputs> }; let ethers_core = util::ethers_core_crate();
let ethers_providers = util::ethers_providers_crate();
let ethers_contract = util::ethers_contract_crate();
let result = quote! { #ethers_contract::builders::ContractCall<M, #outputs> };
let (input, arg) = self.expand_inputs_call_arg_with_structs(function)?; let (input, arg) = self.expand_inputs_call_arg_with_structs(function)?;

View File

@ -104,9 +104,10 @@ impl Context {
let derives = &self.event_derives; let derives = &self.event_derives;
let derives = quote! {#(#derives),*}; let derives = quote! {#(#derives),*};
let ethers_contract = util::ethers_contract_crate();
Ok(quote! { Ok(quote! {
#abi_signature_doc #abi_signature_doc
#[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthAbiType, #derives)] #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #derives)]
pub struct #name { pub struct #name {
#( #fields ),* #( #fields ),*
} }
@ -173,9 +174,11 @@ impl Context {
let derives = &self.event_derives; let derives = &self.event_derives;
let derives = quote! {#(#derives),*}; let derives = quote! {#(#derives),*};
let ethers_contract = util::ethers_contract_crate();
structs.extend(quote! { structs.extend(quote! {
#abi_signature_doc #abi_signature_doc
#[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthAbiType, #derives)] #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #derives)]
pub struct #name { pub struct #name {
#( #fields ),* #( #fields ),*
} }

View File

@ -3,9 +3,13 @@ use ethers_core::abi::ParamType;
use proc_macro2::{Literal, TokenStream}; use proc_macro2::{Literal, TokenStream};
use quote::quote; use quote::quote;
use super::util;
pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> { pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
let ethers_core = util::ethers_core_crate();
match kind { match kind {
ParamType::Address => Ok(quote! { ethers_core::types::Address }), ParamType::Address => Ok(quote! { #ethers_core::types::Address }),
ParamType::Bytes => Ok(quote! { Vec<u8> }), ParamType::Bytes => Ok(quote! { Vec<u8> }),
ParamType::Int(n) => match n / 8 { ParamType::Int(n) => match n / 8 {
1 => Ok(quote! { i8 }), 1 => Ok(quote! { i8 }),
@ -22,7 +26,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
3..=4 => Ok(quote! { u32 }), 3..=4 => Ok(quote! { u32 }),
5..=8 => Ok(quote! { u64 }), 5..=8 => Ok(quote! { u64 }),
9..=16 => Ok(quote! { u128 }), 9..=16 => Ok(quote! { u128 }),
17..=32 => Ok(quote! { ethers_core::types::U256 }), 17..=32 => Ok(quote! { #ethers_core::types::U256 }),
_ => Err(anyhow!("unsupported solidity type uint{}", n)), _ => Err(anyhow!("unsupported solidity type uint{}", n)),
}, },
ParamType::Bool => Ok(quote! { bool }), ParamType::Bool => Ok(quote! { bool }),

View File

@ -12,7 +12,8 @@ use syn::{Ident as SynIdent, Path};
/// See `determine_ethers_crates` /// See `determine_ethers_crates`
/// ///
/// This ensures that the `MetadataCommand` is only run once /// This ensures that the `MetadataCommand` is only run once
static ETHERS_CRATES: Lazy<(&'static str, &'static str)> = Lazy::new(determine_ethers_crates); 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` /// Convenience function to turn the `ethers_core` name in `ETHERS_CRATE` into a `Path`
pub fn ethers_core_crate() -> Path { pub fn ethers_core_crate() -> Path {
@ -22,6 +23,9 @@ pub fn ethers_core_crate() -> Path {
pub fn ethers_contract_crate() -> Path { pub fn ethers_contract_crate() -> Path {
syn::parse_str(ETHERS_CRATES.1).expect("valid path; qed") 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`) /// The crates name to use when deriving macros: (`core`, `contract`)
/// ///
@ -34,8 +38,8 @@ pub fn ethers_contract_crate() -> Path {
/// | ethers_contract`, we need to use the fitting crate ident when expand the /// | 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 /// macros This will attempt to parse the current `Cargo.toml` and check the
/// ethers related dependencies. /// ethers related dependencies.
pub fn determine_ethers_crates() -> (&'static str, &'static str) { pub fn determine_ethers_crates() -> (&'static str, &'static str, &'static str) {
MetadataCommand::new() let res = MetadataCommand::new()
.manifest_path(&format!( .manifest_path(&format!(
"{}/Cargo.toml", "{}/Cargo.toml",
std::env::var("CARGO_MANIFEST_DIR").expect("No Manifest found") std::env::var("CARGO_MANIFEST_DIR").expect("No Manifest found")
@ -44,16 +48,17 @@ pub fn determine_ethers_crates() -> (&'static str, &'static str) {
.exec() .exec()
.ok() .ok()
.and_then(|metadata| { .and_then(|metadata| {
metadata.root_package().and_then(|pkg| { metadata.packages[0]
pkg.dependencies .dependencies
.iter() .iter()
.filter(|dep| dep.kind == DependencyKind::Normal) .filter(|dep| dep.kind == DependencyKind::Normal)
.find_map(|dep| { .find_map(|dep| {
(dep.name == "ethers").then(|| ("ethers::core", "ethers::contract")) (dep.name == "ethers")
.then(|| ("ethers::core", "ethers::contract", "ethers::providers"))
}) })
}) })
}) .unwrap_or(("ethers_core", "ethers_contract", "ethers_providers"));
.unwrap_or(("ethers_core", "ethers_contract")) res
} }
/// Expands a identifier string into an token. /// Expands a identifier string into an token.