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

feat(ethers-contract): Make ethers-providers optional #2536

Merged
merged 6 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
oblique marked this conversation as resolved.
Show resolved Hide resolved

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 = []
oblique marked this conversation as resolved.
Show resolved Hide resolved

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
Loading