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:
parent
3323641311
commit
0236de8d2a
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue