feat(contract): return multicall pending transaction (#2044)

* feat(contract): return multicall pending transaction

* fix: tests

* docs: update CHANGELOG.md
This commit is contained in:
DaniPopes 2023-01-12 04:30:56 +01:00 committed by GitHub
parent b4b153a364
commit 015eeabea8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 50 deletions

View File

@ -291,6 +291,8 @@
### Unreleased ### Unreleased
- Return pending transaction from `Multicall::send`
[#2044](https://github.com/gakonst/ethers-rs/pull/2044)
- Add abigen to default features - Add abigen to default features
[#1684](https://github.com/gakonst/ethers-rs/pull/1684) [#1684](https://github.com/gakonst/ethers-rs/pull/1684)
- Add extra Multicall helper methods - Add extra Multicall helper methods

View File

@ -4,21 +4,18 @@ use crate::{
event::{EthEvent, Event}, event::{EthEvent, Event},
EthLogDecode, EthLogDecode,
}; };
use ethers_core::{ use ethers_core::{
abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize}, abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize},
types::{Address, Filter, Selector, ValueOrArray}, types::{Address, Filter, Selector, ValueOrArray},
}; };
use ethers_providers::Middleware;
use std::{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;
use ethers_providers::Middleware;
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
/// 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
@ -161,6 +158,7 @@ pub struct Contract<M> {
impl<M> std::ops::Deref for Contract<M> { impl<M> std::ops::Deref for Contract<M> {
type Target = BaseContract; type Target = BaseContract;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.base_contract &self.base_contract
} }
@ -177,19 +175,24 @@ impl<M> Clone for Contract<M> {
} }
impl<M> Contract<M> { impl<M> Contract<M> {
/// Returns the contract's address /// Returns the contract's address.
pub fn address(&self) -> Address { pub fn address(&self) -> Address {
self.address self.address
} }
/// Returns a reference to the contract's ABI /// Returns a reference to the contract's ABI.
pub fn abi(&self) -> &Abi { pub fn abi(&self) -> &Abi {
&self.base_contract.abi &self.base_contract.abi
} }
/// 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) -> Arc<M> {
self.client.clone() Arc::clone(&self.client)
}
/// Returns a reference to the contract's client.
pub fn client_ref(&self) -> &M {
Arc::as_ref(&self.client)
} }
} }
@ -301,10 +304,7 @@ impl<M: Middleware> Contract<M> {
/// ///
/// Clones `self` internally /// Clones `self` internally
#[must_use] #[must_use]
pub fn at<T: Into<Address>>(&self, address: T) -> Self pub fn at<T: Into<Address>>(&self, address: T) -> Self {
where
M: Clone,
{
let mut this = self.clone(); let mut this = self.clone();
this.address = address.into(); this.address = address.into();
this this
@ -314,10 +314,7 @@ impl<M: Middleware> Contract<M> {
/// ///
/// Clones `self` internally /// Clones `self` internally
#[must_use] #[must_use]
pub fn connect<N>(&self, client: Arc<N>) -> Contract<N> pub fn connect<N>(&self, client: Arc<N>) -> Contract<N> {
where
N: Clone,
{
Contract { base_contract: self.base_contract.clone(), client, address: self.address } Contract { base_contract: self.base_contract.clone(), client, address: self.address }
} }
} }

View File

@ -4,9 +4,9 @@ use crate::{
}; };
use ethers_core::{ use ethers_core::{
abi::{AbiDecode, Detokenize, Function, Token}, abi::{AbiDecode, Detokenize, Function, Token},
types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, H160, U256}, types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, H160, U256},
}; };
use ethers_providers::Middleware; use ethers_providers::{Middleware, PendingTransaction};
use std::{convert::TryFrom, sync::Arc}; use std::{convert::TryFrom, sync::Arc};
pub mod multicall_contract; pub mod multicall_contract;
@ -223,8 +223,7 @@ impl TryFrom<u8> for MulticallVersion {
/// ///
/// // `await`ing the `send` method waits for the transaction to be broadcast, which also /// // `await`ing the `send` method waits for the transaction to be broadcast, which also
/// // returns the transaction hash /// // returns the transaction hash
/// let tx_hash = multicall.send().await?; /// let _tx_receipt = multicall.send().await?.await.expect("tx dropped");
/// let _tx_receipt = PendingTransaction::new(tx_hash, &client).await?;
/// ///
/// // you can also query ETH balances of multiple addresses /// // you can also query ETH balances of multiple addresses
/// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::<Address>()?; /// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::<Address>()?;
@ -569,7 +568,7 @@ impl<M: Middleware> Multicall<M> {
/// .add_call(broadcast_1, false) /// .add_call(broadcast_1, false)
/// .add_call(broadcast_2, false); /// .add_call(broadcast_2, false);
/// ///
/// let _tx_hash = multicall.send().await?; /// let _tx_receipt = multicall.send().await?.await.expect("tx dropped");
/// ///
/// # let call_1 = contract.method::<_, String>("getValue", ())?; /// # let call_1 = contract.method::<_, String>("getValue", ())?;
/// # let call_2 = contract.method::<_, Address>("lastSender", ())?; /// # let call_2 = contract.method::<_, Address>("lastSender", ())?;
@ -735,7 +734,7 @@ impl<M: Middleware> Multicall<M> {
v @ (MulticallVersion::Multicall2 | MulticallVersion::Multicall3) => { v @ (MulticallVersion::Multicall2 | MulticallVersion::Multicall3) => {
let is_v2 = v == MulticallVersion::Multicall2; let is_v2 = v == MulticallVersion::Multicall2;
let call = if is_v2 { self.as_try_aggregate() } else { self.as_aggregate_3() }; let call = if is_v2 { self.as_try_aggregate() } else { self.as_aggregate_3() };
let return_data = call.call().await?; let return_data = ContractCall::call(&call).await?;
self.calls self.calls
.iter() .iter()
.zip(return_data.into_iter()) .zip(return_data.into_iter())
@ -789,7 +788,7 @@ impl<M: Middleware> Multicall<M> {
} }
/// Signs and broadcasts a batch of transactions by using the Multicall contract as proxy, /// Signs and broadcasts a batch of transactions by using the Multicall contract as proxy,
/// returning the transaction hash once the transaction confirms. /// returning the pending transaction.
/// ///
/// Note: this method will broadcast a transaction from an account, meaning it must have /// Note: this method will broadcast a transaction from an account, meaning it must have
/// sufficient funds for gas and transaction value. /// sufficient funds for gas and transaction value.
@ -811,32 +810,18 @@ impl<M: Middleware> Multicall<M> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub async fn send(&self) -> Result<TxHash, M> { pub async fn send(&self) -> Result<PendingTransaction<'_, M::Provider>, M> {
// Broadcast transaction and return the transaction hash let tx = match self.version {
// TODO: Can we make this return a PendingTransaction directly instead? MulticallVersion::Multicall => self.as_aggregate().tx,
// Seems hard due to `returns a value referencing data owned by the current function` MulticallVersion::Multicall2 => self.as_try_aggregate().tx,
MulticallVersion::Multicall3 => self.as_aggregate_3_value().tx,
// running clippy --fix on this throws E0597
#[allow(clippy::let_and_return)]
let tx_hash = match self.version {
MulticallVersion::Multicall => {
let call = self.as_aggregate();
let hash = *call.send().await?;
hash
}
MulticallVersion::Multicall2 => {
let call = self.as_try_aggregate();
let hash = *call.send().await?;
hash
}
MulticallVersion::Multicall3 => {
let call = self.as_aggregate_3_value();
let hash = *call.send().await?;
hash
}
}; };
Ok(tx_hash) self.contract
.client_ref()
.send_transaction(tx, self.block.map(Into::into))
.await
.map_err(|e| MulticallError::ContractError(ContractError::MiddlewareError(e)))
} }
/// v1 /// v1

View File

@ -491,8 +491,7 @@ mod eth_tests {
multicall_send.clear_calls().add_call(broadcast, false).add_call(broadcast2, false); multicall_send.clear_calls().add_call(broadcast, false).add_call(broadcast2, false);
// broadcast the transaction and wait for it to be mined // broadcast the transaction and wait for it to be mined
let tx_hash = multicall_send.legacy().send().await.unwrap(); let _tx_receipt = multicall_send.legacy().send().await.unwrap().await.unwrap();
let _tx_receipt = PendingTransaction::new(tx_hash, client.provider()).await.unwrap();
// Do another multicall to check the updated return values // Do another multicall to check the updated return values
// The `getValue` calls should return the last value we set in the batched broadcast // The `getValue` calls should return the last value we set in the batched broadcast