refactor: make contract abstract over Borrow (#2082)

* refactor: contract abstract over Borrow

* refactor: preserve old connect usage

* nit: remove commented out modules

* chore: changelog

* test: add compile check to tests

* docs: add usage note to doc and make sure contractcall is visible

* fix: test compilation

* refactor: ContractCallInternal -> FunctionCall, ContractInternal -> ContractInstance

* fix: Send IntoFuture

* nit: must-use on connect

* docs: remove deprecation warning in docstrings for type aliases

* Update ethers-contract/src/call.rs

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>

---------

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
James Prestwich 2023-02-06 16:27:01 -05:00 committed by GitHub
parent 3323641311
commit 0236de8d2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 268 additions and 91 deletions

View File

@ -297,6 +297,8 @@
### Unreleased ### Unreleased
- Make `Contract` objects generic over the borrow trait, to allow non-arc mware
[#2082](https://github.com/gakonst/ethers-rs/pull/2082)
- Return pending transaction from `Multicall::send` - Return pending transaction from `Multicall::send`
[#2044](https://github.com/gakonst/ethers-rs/pull/2044) [#2044](https://github.com/gakonst/ethers-rs/pull/2044)
- Add abigen to default features - Add abigen to default features

View File

@ -1,4 +1,4 @@
use crate::Contract; use crate::contract::ContractInstance;
pub use ethers_core::abi::AbiError; pub use ethers_core::abi::AbiError;
use ethers_core::{ use ethers_core::{
@ -8,10 +8,10 @@ use ethers_core::{
use ethers_providers::Middleware; use ethers_providers::Middleware;
use std::{ use std::{
borrow::Borrow,
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
fmt::Debug, fmt::Debug,
hash::Hash, hash::Hash,
sync::Arc,
}; };
/// A reduced form of `Contract` which just takes the `abi` and produces /// A reduced form of `Contract` which just takes the `abi` and produces
@ -195,12 +195,12 @@ impl BaseContract {
} }
/// Upgrades a `BaseContract` into a full fledged contract with an address and middleware. /// Upgrades a `BaseContract` into a full fledged contract with an address and middleware.
pub fn into_contract<M: Middleware>( pub fn into_contract<B, M>(self, address: Address, client: B) -> ContractInstance<B, M>
self, where
address: Address, B: Borrow<M>,
client: impl Into<Arc<M>>, M: Middleware,
) -> Contract<M> { {
Contract::new(address, self, client) ContractInstance::new(address, self, client)
} }
} }

View File

@ -15,12 +15,11 @@ use ethers_providers::{
}; };
use std::{ use std::{
borrow::Cow, borrow::{Borrow, Cow},
fmt::Debug, fmt::Debug,
future::{Future, IntoFuture}, future::{Future, IntoFuture},
marker::PhantomData, marker::PhantomData,
pin::Pin, pin::Pin,
sync::Arc,
}; };
use thiserror::Error as ThisError; use thiserror::Error as ThisError;
@ -73,33 +72,49 @@ pub enum ContractError<M: Middleware> {
ContractNotDeployed, ContractNotDeployed,
} }
/// `ContractCall` is a [`FunctionCall`] object with an [`std::sync::Arc`] middleware.
/// This type alias exists to preserve backwards compatibility with
/// less-abstract Contracts.
///
/// For full usage docs, see [`FunctionCall`].
pub type ContractCall<M, D> = FunctionCall<std::sync::Arc<M>, M, D>;
#[derive(Debug)] #[derive(Debug)]
#[must_use = "contract calls do nothing unless you `send` or `call` them"] #[must_use = "contract calls do nothing unless you `send` or `call` them"]
/// Helper for managing a transaction before submitting it to a node /// Helper for managing a transaction before submitting it to a node
pub struct ContractCall<M, D> { pub struct FunctionCall<B, M, D> {
/// The raw transaction object /// The raw transaction object
pub tx: TypedTransaction, pub tx: TypedTransaction,
/// The ABI of the function being called /// The ABI of the function being called
pub function: Function, pub function: Function,
/// Optional block number to be used when calculating the transaction's gas and nonce /// Optional block number to be used when calculating the transaction's gas and nonce
pub block: Option<BlockId>, pub block: Option<BlockId>,
pub(crate) client: Arc<M>, pub(crate) client: B,
pub(crate) datatype: PhantomData<D>, pub(crate) datatype: PhantomData<D>,
pub(crate) _m: PhantomData<M>,
} }
impl<M, D> Clone for ContractCall<M, D> { impl<B, M, D> Clone for FunctionCall<B, M, D>
where
B: Clone,
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
ContractCall { FunctionCall {
tx: self.tx.clone(), tx: self.tx.clone(),
function: self.function.clone(), function: self.function.clone(),
block: self.block, block: self.block,
client: self.client.clone(), client: self.client.clone(),
datatype: self.datatype, datatype: self.datatype,
_m: self._m,
} }
} }
} }
impl<M, D: Detokenize> ContractCall<M, D> { impl<B, M, D> FunctionCall<B, M, D>
where
B: Borrow<M>,
D: Detokenize,
{
/// Sets the `from` field in the transaction to the provided value /// Sets the `from` field in the transaction to the provided value
pub fn from<T: Into<Address>>(mut self, from: T) -> Self { pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
self.tx.set_from(from.into()); self.tx.set_from(from.into());
@ -145,8 +160,9 @@ impl<M, D: Detokenize> ContractCall<M, D> {
} }
} }
impl<M, D> ContractCall<M, D> impl<B, M, D> FunctionCall<B, M, D>
where where
B: Borrow<M>,
M: Middleware, M: Middleware,
D: Detokenize, D: Detokenize,
{ {
@ -157,7 +173,11 @@ where
/// Returns the estimated gas cost for the underlying transaction to be executed /// Returns the estimated gas cost for the underlying transaction to be executed
pub async fn estimate_gas(&self) -> Result<U256, ContractError<M>> { pub async fn estimate_gas(&self) -> Result<U256, ContractError<M>> {
self.client.estimate_gas(&self.tx, self.block).await.map_err(ContractError::MiddlewareError) self.client
.borrow()
.estimate_gas(&self.tx, self.block)
.await
.map_err(ContractError::MiddlewareError)
} }
/// Queries the blockchain via an `eth_call` for the provided transaction. /// Queries the blockchain via an `eth_call` for the provided transaction.
@ -170,8 +190,12 @@ where
/// ///
/// Note: this function _does not_ send a transaction from your account /// Note: this function _does not_ send a transaction from your account
pub async fn call(&self) -> Result<D, ContractError<M>> { pub async fn call(&self) -> Result<D, ContractError<M>> {
let bytes = let bytes = self
self.client.call(&self.tx, self.block).await.map_err(ContractError::MiddlewareError)?; .client
.borrow()
.call(&self.tx, self.block)
.await
.map_err(ContractError::MiddlewareError)?;
// decode output // decode output
let data = decode_function_data(&self.function, &bytes, false)?; let data = decode_function_data(&self.function, &bytes, false)?;
@ -202,7 +226,7 @@ where
/// ///
/// Note: this function _does not_ send a transaction from your account /// Note: this function _does not_ send a transaction from your account
pub fn call_raw_bytes(&self) -> CallBuilder<'_, M::Provider> { pub fn call_raw_bytes(&self) -> CallBuilder<'_, M::Provider> {
let call = self.client.provider().call_raw(&self.tx); let call = self.client.borrow().provider().call_raw(&self.tx);
if let Some(block) = self.block { if let Some(block) = self.block {
call.block(block) call.block(block)
} else { } else {
@ -213,17 +237,19 @@ where
/// Signs and broadcasts the provided transaction /// Signs and broadcasts the provided transaction
pub async fn send(&self) -> Result<PendingTransaction<'_, M::Provider>, ContractError<M>> { pub async fn send(&self) -> Result<PendingTransaction<'_, M::Provider>, ContractError<M>> {
self.client self.client
.borrow()
.send_transaction(self.tx.clone(), self.block) .send_transaction(self.tx.clone(), self.block)
.await .await
.map_err(ContractError::MiddlewareError) .map_err(ContractError::MiddlewareError)
} }
} }
/// [`ContractCall`] can be turned into [`Future`] automatically with `.await`. /// [`FunctionCall`] can be turned into [`Future`] automatically with `.await`.
/// Defaults to calling [`ContractCall::call`]. /// Defaults to calling [`FunctionCall::call`].
impl<M, D> IntoFuture for ContractCall<M, D> impl<B, M, D> IntoFuture for FunctionCall<B, M, D>
where where
Self: 'static, Self: 'static,
B: Borrow<M> + Send + Sync,
M: Middleware, M: Middleware,
D: Detokenize + Send + Sync, D: Detokenize + Send + Sync,
{ {

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
base::{encode_function_data, AbiError, BaseContract}, base::{encode_function_data, AbiError, BaseContract},
call::ContractCall, call::FunctionCall,
event::{EthEvent, Event}, event::{EthEvent, Event},
EthLogDecode, EthLogDecode,
}; };
@ -9,13 +9,20 @@ use ethers_core::{
types::{Address, Filter, Selector, ValueOrArray}, types::{Address, Filter, Selector, ValueOrArray},
}; };
use ethers_providers::Middleware; use ethers_providers::Middleware;
use std::{marker::PhantomData, sync::Arc}; use std::{borrow::Borrow, fmt::Debug, marker::PhantomData, sync::Arc};
#[cfg(not(feature = "legacy"))] #[cfg(not(feature = "legacy"))]
use ethers_core::types::Eip1559TransactionRequest; use ethers_core::types::Eip1559TransactionRequest;
#[cfg(feature = "legacy")] #[cfg(feature = "legacy")]
use ethers_core::types::TransactionRequest; use ethers_core::types::TransactionRequest;
/// `Contract` is a [`ContractInstance`] object with an `Arc` middleware.
/// This type alias exists to preserve backwards compatibility with
/// less-abstract Contracts.
///
/// For full usage docs, see [`ContractInstance`].
pub type Contract<M> = ContractInstance<std::sync::Arc<M>, M>;
/// A Contract is an abstraction of an executable program on the Ethereum Blockchain. /// A Contract is an abstraction of an executable program on the Ethereum Blockchain.
/// It has code (called byte code) as well as allocated long-term memory /// It has code (called byte code) as well as allocated long-term memory
/// (called storage). Every deployed Contract has an address, which is used to connect /// (called storage). Every deployed Contract has an address, which is used to connect
@ -69,7 +76,7 @@ use ethers_core::types::TransactionRequest;
/// use ethers_contract::Contract; /// use ethers_contract::Contract;
/// use ethers_providers::{Provider, Http}; /// use ethers_providers::{Provider, Http};
/// use ethers_signers::Wallet; /// use ethers_signers::Wallet;
/// use std::convert::TryFrom; /// use std::{convert::TryFrom, sync::Arc};
/// ///
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> { /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// // this is a fake address used just for this example /// // this is a fake address used just for this example
@ -82,7 +89,7 @@ use ethers_core::types::TransactionRequest;
/// let client = Provider::<Http>::try_from("http://localhost:8545").unwrap(); /// let client = Provider::<Http>::try_from("http://localhost:8545").unwrap();
/// ///
/// // create the contract object at the address /// // create the contract object at the address
/// let contract = Contract::new(address, abi, client); /// let contract = Contract::new(address, abi, Arc::new(client));
/// ///
/// // Calling constant methods is done by calling `call()` on the method builder. /// // Calling constant methods is done by calling `call()` on the method builder.
/// // (if the function takes no arguments, then you must use `()` as the argument) /// // (if the function takes no arguments, then you must use `()` as the argument)
@ -116,13 +123,13 @@ use ethers_core::types::TransactionRequest;
/// use ethers_contract::{Contract, EthEvent}; /// use ethers_contract::{Contract, EthEvent};
/// use ethers_providers::{Provider, Http, Middleware}; /// use ethers_providers::{Provider, Http, Middleware};
/// use ethers_signers::Wallet; /// use ethers_signers::Wallet;
/// use std::convert::TryFrom; /// use std::{convert::TryFrom, sync::Arc};
/// use ethers_core::abi::{Detokenize, Token, InvalidOutputType}; /// use ethers_core::abi::{Detokenize, Token, InvalidOutputType};
/// # // this is a fake address used just for this example /// # // this is a fake address used just for this example
/// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?; /// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
/// # let abi: Abi = serde_json::from_str(r#"[]"#)?; /// # let abi: Abi = serde_json::from_str(r#"[]"#)?;
/// # let client = Provider::<Http>::try_from("http://localhost:8545").unwrap(); /// # let client = Provider::<Http>::try_from("http://localhost:8545").unwrap();
/// # let contract = Contract::new(address, abi, client); /// # let contract = Contract::new(address, abi, Arc::new(client));
/// ///
/// #[derive(Clone, Debug, EthEvent)] /// #[derive(Clone, Debug, EthEvent)]
/// struct ValueChanged { /// struct ValueChanged {
@ -145,18 +152,40 @@ use ethers_core::types::TransactionRequest;
/// ///
/// _Disclaimer: these above docs have been adapted from the corresponding [ethers.js page](https://docs.ethers.io/ethers.js/html/api-contract.html)_ /// _Disclaimer: these above docs have been adapted from the corresponding [ethers.js page](https://docs.ethers.io/ethers.js/html/api-contract.html)_
/// ///
/// # Usage Note
///
/// `ContractInternal` accepts any client that implements `B: Borrow<M>` where
/// `M :Middleware`. Previous `Contract` versions used only arcs, and relied
/// heavily on [`Arc`]. Due to constraints on the [`FunctionCall`] type,
/// calling contracts requires a `B: Borrow<M> + Clone`. This is fine for most
/// middlware. However, when `B` is an owned middleware that is not Clone, we
/// cannot issue contract calls. Some notable exceptions:
///
/// - `NonceManagerMiddleware`
/// - `SignerMiddleware` (when using a non-Clone Signer)
///
/// When using non-Clone middlewares, instead of instantiating a contract that
/// OWNS the middlware, pass the contract a REFERENCE to the middleware. This
/// will fix the trait bounds issue (as `&M` is always `Clone`).
///
/// We expect to fix this fully in a future version
///
/// [`abigen`]: macro.abigen.html /// [`abigen`]: macro.abigen.html
/// [`Abigen` builder]: struct.Abigen.html /// [`Abigen` builder]: struct.Abigen.html
/// [`event`]: method@crate::Contract::event /// [`event`]: method@crate::ContractInstance::event
/// [`method`]: method@crate::Contract::method /// [`method`]: method@crate::ContractInstance::method
#[derive(Debug)] #[derive(Debug)]
pub struct Contract<M> { pub struct ContractInstance<B, M> {
address: Address, address: Address,
base_contract: BaseContract, base_contract: BaseContract,
client: Arc<M>, client: B,
_m: PhantomData<M>,
} }
impl<M> std::ops::Deref for Contract<M> { impl<B, M> std::ops::Deref for ContractInstance<B, M>
where
B: Borrow<M>,
{
type Target = BaseContract; type Target = BaseContract;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -164,18 +193,25 @@ impl<M> std::ops::Deref for Contract<M> {
} }
} }
impl<M> Clone for Contract<M> { impl<B, M> Clone for ContractInstance<B, M>
where
B: Clone + Borrow<M>,
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
Contract { ContractInstance {
base_contract: self.base_contract.clone(), base_contract: self.base_contract.clone(),
client: self.client.clone(), client: self.client.clone(),
address: self.address, address: self.address,
_m: self._m,
} }
} }
} }
impl<M> Contract<M> { impl<B, M> ContractInstance<B, M>
/// Returns the contract's address. where
B: Borrow<M>,
{
/// Returns the contract's address
pub fn address(&self) -> Address { pub fn address(&self) -> Address {
self.address self.address
} }
@ -186,17 +222,24 @@ impl<M> Contract<M> {
} }
/// Returns a pointer to the contract's client. /// Returns a pointer to the contract's client.
pub fn client(&self) -> Arc<M> { pub fn client(&self) -> B
Arc::clone(&self.client) where
B: Clone,
{
self.client.clone()
} }
/// Returns a reference to the contract's client. /// Returns a reference to the contract's client.
pub fn client_ref(&self) -> &M { pub fn client_ref(&self) -> &M {
Arc::as_ref(&self.client) self.client.borrow()
} }
} }
impl<M: Middleware> Contract<M> { impl<B, M> ContractInstance<B, M>
where
B: Borrow<M>,
M: Middleware,
{
/// Returns an [`Event`](crate::builders::Event) builder for the provided event. /// Returns an [`Event`](crate::builders::Event) builder for the provided event.
/// This function operates in a static context, then it does not require a `self` /// This function operates in a static context, then it does not require a `self`
/// to reference to instantiate an [`Event`](crate::builders::Event) builder. /// to reference to instantiate an [`Event`](crate::builders::Event) builder.
@ -209,14 +252,14 @@ impl<M: Middleware> Contract<M> {
} }
} }
impl<M: Middleware> Contract<M> { impl<B, M> ContractInstance<B, M>
where
B: Borrow<M>,
M: Middleware,
{
/// Creates a new contract from the provided client, abi and address /// Creates a new contract from the provided client, abi and address
pub fn new( pub fn new(address: impl Into<Address>, abi: impl Into<BaseContract>, client: B) -> Self {
address: impl Into<Address>, Self { base_contract: abi.into(), client, address: address.into(), _m: PhantomData }
abi: impl Into<BaseContract>,
client: impl Into<Arc<M>>,
) -> Self {
Self { base_contract: abi.into(), client: client.into(), address: address.into() }
} }
/// Returns an [`Event`](crate::builders::Event) builder for the provided event. /// Returns an [`Event`](crate::builders::Event) builder for the provided event.
@ -227,7 +270,7 @@ impl<M: Middleware> Contract<M> {
/// Returns an [`Event`](crate::builders::Event) builder with the provided filter. /// Returns an [`Event`](crate::builders::Event) builder with the provided filter.
pub fn event_with_filter<D: EthLogDecode>(&self, filter: Filter) -> Event<M, D> { pub fn event_with_filter<D: EthLogDecode>(&self, filter: Filter) -> Event<M, D> {
Event { Event {
provider: &self.client, provider: self.client.borrow(),
filter: filter.address(ValueOrArray::Value(self.address)), filter: filter.address(ValueOrArray::Value(self.address)),
datatype: PhantomData, datatype: PhantomData,
} }
@ -240,40 +283,49 @@ impl<M: Middleware> Contract<M> {
Ok(self.event_with_filter(Filter::new().event(&event.abi_signature()))) Ok(self.event_with_filter(Filter::new().event(&event.abi_signature())))
} }
/// Returns a transaction builder for the provided function name. If there are /// Returns a new contract instance using the provided client
/// multiple functions with the same name due to overloading, consider using ///
/// the `method_hash` method instead, since this will use the first match. /// Clones `self` internally
pub fn method<T: Tokenize, D: Detokenize>( #[must_use]
&self, pub fn connect<N>(&self, client: Arc<N>) -> ContractInstance<Arc<N>, N>
name: &str, where
args: T, N: Middleware,
) -> Result<ContractCall<M, D>, AbiError> { {
// get the function ContractInstance {
let function = self.base_contract.abi.function(name)?; base_contract: self.base_contract.clone(),
self.method_func(function, args) client,
address: self.address,
_m: PhantomData,
}
} }
/// Returns a transaction builder for the selected function signature. This should be /// Returns a new contract instance using the provided client
/// preferred if there are overloaded functions in your smart contract ///
pub fn method_hash<T: Tokenize, D: Detokenize>( /// Clones `self` internally
&self, #[must_use]
signature: Selector, pub fn connect_with<C, N>(&self, client: C) -> ContractInstance<C, N>
args: T, where
) -> Result<ContractCall<M, D>, AbiError> { C: Borrow<N>,
let function = self {
.base_contract ContractInstance {
.methods base_contract: self.base_contract.clone(),
.get(&signature) client,
.map(|(name, index)| &self.base_contract.abi.functions[name][*index]) address: self.address,
.ok_or_else(|| Error::InvalidName(hex::encode(signature)))?; _m: PhantomData,
self.method_func(function, args) }
}
} }
impl<B, M> ContractInstance<B, M>
where
B: Clone + Borrow<M>,
M: Middleware,
{
fn method_func<T: Tokenize, D: Detokenize>( fn method_func<T: Tokenize, D: Detokenize>(
&self, &self,
function: &Function, function: &Function,
args: T, args: T,
) -> Result<ContractCall<M, D>, AbiError> { ) -> Result<FunctionCall<B, M, D>, AbiError> {
let data = encode_function_data(function, args)?; let data = encode_function_data(function, args)?;
#[cfg(feature = "legacy")] #[cfg(feature = "legacy")]
@ -291,15 +343,45 @@ impl<M: Middleware> Contract<M> {
let tx = tx.into(); let tx = tx.into();
Ok(ContractCall { Ok(FunctionCall {
tx, tx,
client: Arc::clone(&self.client), // cheap clone behind the Arc client: self.client.clone(),
block: None, block: None,
function: function.to_owned(), function: function.to_owned(),
datatype: PhantomData, datatype: PhantomData,
_m: self._m,
}) })
} }
/// Returns a transaction builder for the selected function signature. This should be
/// preferred if there are overloaded functions in your smart contract
pub fn method_hash<T: Tokenize, D: Detokenize>(
&self,
signature: Selector,
args: T,
) -> Result<FunctionCall<B, M, D>, AbiError> {
let function = self
.base_contract
.methods
.get(&signature)
.map(|(name, index)| &self.base_contract.abi.functions[name][*index])
.ok_or_else(|| Error::InvalidName(hex::encode(signature)))?;
self.method_func(function, args)
}
/// Returns a transaction builder for the provided function name. If there are
/// multiple functions with the same name due to overloading, consider using
/// the `method_hash` method instead, since this will use the first match.
pub fn method<T: Tokenize, D: Detokenize>(
&self,
name: &str,
args: T,
) -> Result<FunctionCall<B, M, D>, AbiError> {
// get the function
let function = self.base_contract.abi.function(name)?;
self.method_func(function, args)
}
/// Returns a new contract instance at `address`. /// Returns a new contract instance at `address`.
/// ///
/// Clones `self` internally /// Clones `self` internally
@ -309,12 +391,4 @@ impl<M: Middleware> Contract<M> {
this.address = address.into(); this.address = address.into();
this this
} }
/// Returns a new contract instance using the provided client
///
/// Clones `self` internally
#[must_use]
pub fn connect<N>(&self, client: Arc<N>) -> Contract<N> {
Contract { base_contract: self.base_contract.clone(), client, address: self.address }
}
} }

View File

@ -3,13 +3,13 @@
#![deny(unsafe_code)] #![deny(unsafe_code)]
mod contract; mod contract;
pub use contract::Contract; pub use contract::{Contract, ContractInstance};
mod base; mod base;
pub use base::{decode_function_data, encode_function_data, AbiError, BaseContract}; pub use base::{decode_function_data, encode_function_data, AbiError, BaseContract};
mod call; mod call;
pub use call::{ContractError, EthCall}; pub use call::{ContractCall, ContractError, EthCall, FunctionCall};
mod error; mod error;
pub use error::EthError; pub use error::EthError;

View File

@ -6,10 +6,10 @@ use ethers_core::types::{Filter, ValueOrArray, H256};
#[cfg(not(feature = "celo"))] #[cfg(not(feature = "celo"))]
mod eth_tests { mod eth_tests {
use super::*; use super::*;
use ethers_contract::{EthEvent, LogMeta, Multicall, MulticallVersion}; use ethers_contract::{ContractInstance, EthEvent, LogMeta, Multicall, MulticallVersion};
use ethers_core::{ use ethers_core::{
abi::{encode, Detokenize, Token, Tokenizable}, abi::{encode, Detokenize, Token, Tokenizable},
types::{transaction::eip712::Eip712, Address, BlockId, Bytes, I256, U256}, types::{transaction::eip712::Eip712, Address, BlockId, Bytes, H160, I256, U256},
utils::{keccak256, Anvil}, utils::{keccak256, Anvil},
}; };
use ethers_derive_eip712::*; use ethers_derive_eip712::*;
@ -17,6 +17,81 @@ mod eth_tests {
use ethers_signers::{LocalWallet, Signer}; use ethers_signers::{LocalWallet, Signer};
use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration}; use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
#[derive(Debug)]
pub struct NonClone<M> {
m: M,
}
#[derive(Debug)]
pub struct MwErr<M: Middleware>(M::Error);
impl<M> ethers_providers::FromErr<M::Error> for MwErr<M>
where
M: Middleware,
{
fn from(src: M::Error) -> Self {
Self(src)
}
}
impl<M: Middleware> std::fmt::Display for MwErr<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl<M: Middleware> std::error::Error for MwErr<M> {}
impl<M: Middleware> Middleware for NonClone<M> {
type Error = MwErr<M>;
type Provider = M::Provider;
type Inner = M;
fn inner(&self) -> &Self::Inner {
&self.m
}
}
// this is not a test. It is a compile check. :)
// It exists to ensure that trait bounds on contract internal behave as
// expected. It should not be run
fn it_compiles() {
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
// launch anvil
let anvil = Anvil::new().spawn();
let client = Provider::<Http>::try_from(anvil.endpoint())
.unwrap()
.interval(Duration::from_millis(10u64));
// Works (B == M, M: Clone)
let c: ContractInstance<&Provider<Http>, Provider<Http>> =
ContractInstance::new(H160::default(), abi.clone(), &client);
let _ = c.method::<(), ()>("notARealMethod", ());
// Works (B == &M, M: Clone)
let c: ContractInstance<Provider<Http>, Provider<Http>> =
ContractInstance::new(H160::default(), abi.clone(), client.clone());
let _ = c.method::<(), ()>("notARealMethod", ());
let non_clone_mware = NonClone { m: client };
// Works (B == &M, M: !Clone)
let c: ContractInstance<&NonClone<Provider<Http>>, NonClone<Provider<Http>>> =
ContractInstance::new(H160::default(), abi, &non_clone_mware);
let _ = c.method::<(), ()>("notARealMethod", ());
// // Fails (B == M, M: !Clone)
// let c: ContractInternal<NonClone<Provider<Http>>, NonClone<Provider<Http>>> =
// ContractInternal::new(H160::default(), abi, non_clone_mware);
// let _ = c.method::<(), ()>("notARealMethod", ());
}
#[tokio::test] #[tokio::test]
async fn deploy_and_call_contract() { async fn deploy_and_call_contract() {
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol"); let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
@ -265,7 +340,7 @@ mod eth_tests {
// Also set up a subscription for the same thing // Also set up a subscription for the same thing
let ws = Provider::connect(anvil.ws_endpoint()).await.unwrap(); let ws = Provider::connect(anvil.ws_endpoint()).await.unwrap();
let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws); let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws.into());
let event2 = contract2.event::<ValueChanged>(); let event2 = contract2.event::<ValueChanged>();
let mut subscription = event2.subscribe().await.unwrap(); let mut subscription = event2.subscribe().await.unwrap();