Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
feat(ethers-contract): Make ethers-providers optional (#2536)
Browse files Browse the repository at this point in the history
* feat(ethers-contract): Make ethers-providers optional

* multicall: Move providers feature implementation in another file

* add providers feature guard in tests

* Enable `providers` in ethers-middleware

* Enable providers by default

---------

Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
  • Loading branch information
oblique and DaniPopes authored Aug 18, 2023
1 parent 5fc33e3 commit 179891d
Show file tree
Hide file tree
Showing 20 changed files with 1,137 additions and 1,012 deletions.
8 changes: 5 additions & 3 deletions ethers-contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ rustdoc-args = ["--cfg", "docsrs"]
all-features = true

[dependencies]
ethers-providers.workspace = true
ethers-providers = { workspace = true, optional = true }
ethers-core.workspace = true
ethers-signers.workspace = true

serde.workspace = true
serde_json.workspace = true
Expand All @@ -41,12 +40,15 @@ ethers-contract-derive = { workspace = true, optional = true }

[dev-dependencies]
ethers-providers = { workspace = true, features = ["ws"] }
ethers-signers.workspace = true

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }

[features]
default = ["abigen"]
default = ["abigen", "providers"]

providers = ["ethers-providers", "ethers-contract-abigen/providers", "ethers-contract-derive/providers"]

abigen = ["ethers-contract-abigen", "ethers-contract-derive"]
abigen-online = ["abigen", "ethers-contract-abigen/online"]
Expand Down
4 changes: 4 additions & 0 deletions ethers-contract/ethers-contract-abigen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ tokio = { workspace = true, optional = true }
url = { workspace = true, optional = true }

[features]
default = ["providers"]

providers = []

online = ["reqwest", "ethers-etherscan", "url", "tokio"]
rustls = ["reqwest?/rustls-tls", "ethers-etherscan?/rustls"]
openssl = ["reqwest?/native-tls", "ethers-etherscan?/openssl"]
Expand Down
111 changes: 67 additions & 44 deletions ethers-contract/ethers-contract-abigen/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ mod types;

use super::{util, Abigen};
use crate::contract::{methods::MethodAlias, structs::InternalStructs};
#[cfg(feature = "providers")]
use ethers_core::macros::ethers_providers_crate;
use ethers_core::{
abi::{Abi, AbiParser, ErrorExt, EventExt, JsonAbi},
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
macros::{ethers_contract_crate, ethers_core_crate},
types::Bytes,
};
use eyre::{eyre, Result};
Expand Down Expand Up @@ -131,37 +133,44 @@ pub struct Context {
impl Context {
/// Generates the tokens.
pub fn expand(&self) -> Result<ExpandedContract> {
let name = &self.contract_ident;
let name_mod = util::ident(&util::safe_module_name(&self.contract_name));
let abi_name = self.inline_abi_ident();

// 1. Declare Contract struct
let struct_decl = self.struct_declaration();

// 2. Declare events structs & impl FromTokens for each event
let events_decl = self.events_declaration()?;

// 3. impl block for the event functions
let contract_events = self.event_methods()?;

// 4. impl block for the contract methods and their corresponding types
// 3. impl block for the contract methods and their corresponding types
let (contract_methods, call_structs) = self.methods_and_call_structs()?;

// 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
// 4. Declare the structs parsed from the human readable abi
let abi_structs_decl = self.abi_structs()?;

// 7. declare all error types
// 5. declare all error types
let errors_decl = self.errors()?;

let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let ethers_providers = ethers_providers_crate();

let contract = quote! {
#struct_decl
#struct_decl
};

#[cfg(feature = "providers")]
let contract = {
let name = &self.contract_ident;
let abi_name = self.inline_abi_ident();

// 6. impl block for the event functions
let contract_events = self.event_methods()?;

// 7. The deploy method, only if the contract has a bytecode object
let deployment_methods = self.deployment_methods();

let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let ethers_providers = ethers_providers_crate();

quote! {
#contract

impl<M: #ethers_providers::Middleware> #name<M> {
/// Creates a new contract instance with the specified `ethers` client at
Expand All @@ -182,8 +191,13 @@ impl Context {
Self::new(contract.address(), contract.client())
}
}
}
};

// Avoid having a warning
#[cfg(not(feature = "providers"))]
let _ = contract_methods;

Ok(ExpandedContract {
module: name_mod,
imports: quote!(),
Expand Down Expand Up @@ -339,8 +353,6 @@ impl Context {

/// 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();

Expand Down Expand Up @@ -390,7 +402,7 @@ impl Context {
}
});

quote! {
let code = quote! {
// The `Lazy` ABI
#abi

Expand All @@ -399,41 +411,52 @@ impl Context {

// The static deployed Bytecode, if present
#deployed_bytecode
};

#[cfg(feature = "providers")]
let code = {
let name = &self.contract_ident;

// Struct declaration
pub struct #name<M>(#ethers_contract::Contract<M>);
quote! {
#code

// Struct declaration
pub struct #name<M>(#ethers_contract::Contract<M>);

// Manual implementation since `M` is stored in `Arc<M>` and does not need to be `Clone`
impl<M> ::core::clone::Clone for #name<M> {
fn clone(&self) -> Self {
Self(::core::clone::Clone::clone(&self.0))
// Manual implementation since `M` is stored in `Arc<M>` and does not need to be `Clone`
impl<M> ::core::clone::Clone for #name<M> {
fn clone(&self) -> Self {
Self(::core::clone::Clone::clone(&self.0))
}
}
}

// Deref to the inner contract to have access to all its methods
impl<M> ::core::ops::Deref for #name<M> {
type Target = #ethers_contract::Contract<M>;
// Deref to the inner contract to have access to all its methods
impl<M> ::core::ops::Deref for #name<M> {
type Target = #ethers_contract::Contract<M>;

fn deref(&self) -> &Self::Target {
&self.0
fn deref(&self) -> &Self::Target {
&self.0
}
}
}

impl<M> ::core::ops::DerefMut for #name<M> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
impl<M> ::core::ops::DerefMut for #name<M> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
}

// `<name>(<address>)`
impl<M> ::core::fmt::Debug for #name<M> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_tuple(::core::stringify!(#name))
.field(&self.address())
.finish()
// `<name>(<address>)`
impl<M> ::core::fmt::Debug for #name<M> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_tuple(::core::stringify!(#name))
.field(&self.address())
.finish()
}
}
}
}
};

code
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ impl Context {
}

/// Returns all deploy (constructor) implementations
#[cfg(feature = "providers")]
pub(crate) fn deployment_methods(&self) -> Option<TokenStream> {
// don't generate deploy if no bytecode
self.contract_bytecode.as_ref()?;
Expand Down
5 changes: 5 additions & 0 deletions ethers-contract/ethers-contract-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ syn.workspace = true
Inflector.workspace = true
hex.workspace = true
serde_json.workspace = true

[features]
default = ["providers"]

providers = ["ethers-contract-abigen/providers"]
18 changes: 13 additions & 5 deletions ethers-contract/src/base.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
use crate::ContractInstance;
pub use ethers_core::abi::AbiError;
use ethers_core::{
abi::{Abi, Detokenize, Error, Event, Function, FunctionExt, RawLog, Token, Tokenize},
types::{Address, Bytes, Selector, H256},
types::{Bytes, Selector, H256},
};
use ethers_providers::Middleware;
use std::{
borrow::Borrow,
collections::{BTreeMap, HashMap},
fmt::Debug,
hash::Hash,
};

if_providers! {
use crate::ContractInstance;
use ethers_providers::Middleware;
use ethers_core::types::Address;
use std::borrow::Borrow;
}

/// A reduced form of `Contract` which just takes the `abi` and produces
/// ABI encoded data for its functions.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -219,6 +223,7 @@ impl BaseContract {
}

/// Upgrades a `BaseContract` into a full fledged contract with an address and middleware.
#[cfg(feature = "providers")]
pub fn into_contract<B, M>(self, address: Address, client: B) -> ContractInstance<B, M>
where
B: Borrow<M>,
Expand Down Expand Up @@ -314,7 +319,10 @@ where
#[cfg(test)]
mod tests {
use super::*;
use ethers_core::{abi::parse_abi, types::U256};
use ethers_core::{
abi::parse_abi,
types::{Address, U256},
};

#[test]
fn can_parse_function_inputs() {
Expand Down
22 changes: 3 additions & 19 deletions ethers-contract/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@ use crate::{error::ContractRevert, EthError};

use super::base::{decode_function_data, AbiError};
use ethers_core::{
abi::{AbiDecode, AbiEncode, Detokenize, Function, InvalidOutputType, Tokenizable},
abi::{Detokenize, Function, InvalidOutputType},
types::{
transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Selector,
TransactionRequest, U256,
transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, TransactionRequest, U256,
},
utils::id,
};
use ethers_providers::{
call_raw::{CallBuilder, RawCall},
JsonRpcError, Middleware, MiddlewareError, PendingTransaction, ProviderError,
};

use std::{
borrow::{Borrow, Cow},
borrow::Borrow,
fmt::Debug,
future::{Future, IntoFuture},
marker::PhantomData,
Expand All @@ -26,20 +24,6 @@ use std::{

use thiserror::Error as ThisError;

/// A helper trait for types that represent all call input parameters of a specific function
pub trait EthCall: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
/// The name of the function
fn function_name() -> Cow<'static, str>;

/// Retrieves the ABI signature for the call
fn abi_signature() -> Cow<'static, str>;

/// The selector of the function
fn selector() -> Selector {
id(Self::abi_signature())
}
}

#[derive(ThisError, Debug)]
/// An Error which is thrown when interacting with a smart contract
pub enum ContractError<M: Middleware> {
Expand Down
20 changes: 20 additions & 0 deletions ethers-contract/src/call_core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use ethers_core::{
abi::{AbiDecode, AbiEncode, Tokenizable},
types::Selector,
utils::id,
};
use std::borrow::Cow;

/// A helper trait for types that represent all call input parameters of a specific function
pub trait EthCall: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
/// The name of the function
fn function_name() -> Cow<'static, str>;

/// Retrieves the ABI signature for the call
fn abi_signature() -> Cow<'static, str>;

/// The selector of the function
fn selector() -> Selector {
id(Self::abi_signature())
}
}
3 changes: 2 additions & 1 deletion ethers-contract/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
base::{encode_function_data, AbiError, BaseContract},
call::FunctionCall,
event::{EthEvent, Event},
event::Event,
event_core::EthEvent,
};
use ethers_core::{
abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize},
Expand Down
2 changes: 2 additions & 0 deletions ethers-contract/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use ethers_core::{
types::Selector,
utils::id,
};
#[cfg(feature = "providers")]
use ethers_providers::JsonRpcError;
use std::borrow::Cow;

Expand Down Expand Up @@ -46,6 +47,7 @@ pub trait EthError: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
/// Attempt to decode from a [`JsonRpcError`] by extracting revert data
///
/// Fails if the error is not a revert, or decoding fails
#[cfg(feature = "providers")]
fn from_rpc_response(response: &JsonRpcError) -> Option<Self> {
Self::decode_with_selector(&response.as_revert_data()?)
}
Expand Down
Loading

0 comments on commit 179891d

Please sign in to comment.