Repository: gakonst/ethers-rs
Pull request #2040 (2023-01-10 22:30:46)

chore(deps): bump bzip2 from 0.4.3 to 0.4.4
Bumps [bzip2](https://github.com/alexcrichton/bzip2-rs) from 0.4.3 to 0.4.4.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a href="https://github.com/alexcrichton/bzip2-rs/commits/0.4.4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=bzip2&package-manager=cargo&previous-version=0.4.3&new-version=0.4.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/gakonst/ethers-rs/network/alerts).

</details>


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 608599c7..3208a490 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### Unreleased
 
+- Avoid unnecessary allocations in `utils` [#2046](https://github.com/gakonst/ethers-rs/pull/2046)
 - Add abigen support for hardhat generated bytecode json format [#2012](https://github.com/gakonst/ethers-rs/pull/2012)
 - Fix typo in `RwClient` docs for `write_client` method.
 - Add support for Geth `debug_traceCall` [#1949](https://github.com/gakonst/ethers-rs/pull/1949)
@@ -290,6 +291,8 @@
 
 ### Unreleased
 
+- Return pending transaction from `Multicall::send`
+  [#2044](https://github.com/gakonst/ethers-rs/pull/2044)
 - Add abigen to default features
   [#1684](https://github.com/gakonst/ethers-rs/pull/1684)
 - Add extra Multicall helper methods
diff --git a/Cargo.lock b/Cargo.lock
index b217928d..48959675 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -396,9 +396,9 @@ dependencies = [
 
 [[package]]
 name = "bzip2"
-version = "0.4.3"
+version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
 dependencies = [
  "bzip2-sys",
  "libc",
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index 5f417611..09a2d713 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -4,21 +4,18 @@ use crate::{
     event::{EthEvent, Event},
     EthLogDecode,
 };
-
 use ethers_core::{
     abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize},
     types::{Address, Filter, Selector, ValueOrArray},
 };
+use ethers_providers::Middleware;
+use std::{marker::PhantomData, sync::Arc};
 
 #[cfg(not(feature = "legacy"))]
 use ethers_core::types::Eip1559TransactionRequest;
 #[cfg(feature = "legacy")]
 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.
 /// 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
@@ -161,6 +158,7 @@ pub struct Contract<M> {
 
 impl<M> std::ops::Deref for Contract<M> {
     type Target = BaseContract;
+
     fn deref(&self) -> &Self::Target {
         &self.base_contract
     }
@@ -177,19 +175,24 @@ impl<M> Clone for Contract<M> {
 }
 
 impl<M> Contract<M> {
-    /// Returns the contract's address
+    /// Returns the contract's address.
     pub fn 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 {
         &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> {
-        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
     #[must_use]
-    pub fn at<T: Into<Address>>(&self, address: T) -> Self
-    where
-        M: Clone,
-    {
+    pub fn at<T: Into<Address>>(&self, address: T) -> Self {
         let mut this = self.clone();
         this.address = address.into();
         this
@@ -314,10 +314,7 @@ impl<M: Middleware> Contract<M> {
     ///
     /// Clones `self` internally
     #[must_use]
-    pub fn connect<N>(&self, client: Arc<N>) -> Contract<N>
-    where
-        N: Clone,
-    {
+    pub fn connect<N>(&self, client: Arc<N>) -> Contract<N> {
         Contract { base_contract: self.base_contract.clone(), client, address: self.address }
     }
 }
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index 0b5bc56f..73c67a39 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -4,9 +4,9 @@ use crate::{
 };
 use ethers_core::{
     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};
 
 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
 /// // returns the transaction hash
-/// let tx_hash = multicall.send().await?;
-/// let _tx_receipt = PendingTransaction::new(tx_hash, &client).await?;
+/// let _tx_receipt = multicall.send().await?.await.expect("tx dropped");
 ///
 /// // you can also query ETH balances of multiple addresses
 /// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::<Address>()?;
@@ -569,7 +568,7 @@ impl<M: Middleware> Multicall<M> {
     ///     .add_call(broadcast_1, 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_2 = contract.method::<_, Address>("lastSender", ())?;
@@ -735,7 +734,7 @@ impl<M: Middleware> Multicall<M> {
             v @ (MulticallVersion::Multicall2 | MulticallVersion::Multicall3) => {
                 let is_v2 = v == MulticallVersion::Multicall2;
                 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
                     .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,
-    /// 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
     /// sufficient funds for gas and transaction value.
@@ -811,32 +810,18 @@ impl<M: Middleware> Multicall<M> {
     /// # Ok(())
     /// # }
     /// ```
-    pub async fn send(&self) -> Result<TxHash, M> {
-        // Broadcast transaction and return the transaction hash
-        // TODO: Can we make this return a PendingTransaction directly instead?
-        // Seems hard due to `returns a value referencing data owned by the current function`
-
-        // 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
-            }
+    pub async fn send(&self) -> Result<PendingTransaction<'_, M::Provider>, M> {
+        let tx = match self.version {
+            MulticallVersion::Multicall => self.as_aggregate().tx,
+            MulticallVersion::Multicall2 => self.as_try_aggregate().tx,
+            MulticallVersion::Multicall3 => self.as_aggregate_3_value().tx,
         };
 
-        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
diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs
index f5f2f760..e423b1a6 100644
--- a/ethers-core/src/types/chain.rs
+++ b/ethers-core/src/types/chain.rs
@@ -5,7 +5,7 @@ use std::{
     fmt,
     time::Duration,
 };
-use strum::{AsRefStr, EnumString, EnumVariantNames};
+use strum::{AsRefStr, EnumCount, EnumIter, EnumString, EnumVariantNames};
 
 // compatibility re-export
 #[doc(hidden)]
@@ -33,6 +33,8 @@ pub type ParseChainError = TryFromPrimitiveError<Chain>;
     AsRefStr,         // also for fmt::Display and serde::Serialize
     EnumVariantNames, // Self::VARIANTS
     EnumString,       // FromStr, TryFrom<&str>
+    EnumIter,
+    EnumCount,
     TryFromPrimitive, // TryFrom<u64>
     Deserialize,
 )]
@@ -372,9 +374,15 @@ impl Chain {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use strum::IntoEnumIterator;
 
     #[test]
     fn test_default_chain() {
         assert_eq!(serde_json::to_string(&Chain::default()).unwrap(), "\"mainnet\"");
     }
+
+    #[test]
+    fn test_enum_iter() {
+        assert_eq!(Chain::COUNT, Chain::iter().size_hint().0);
+    }
 }
diff --git a/ethers-core/src/utils/hash.rs b/ethers-core/src/utils/hash.rs
index 2ddb4c43..18552590 100644
--- a/ethers-core/src/utils/hash.rs
+++ b/ethers-core/src/utils/hash.rs
@@ -1,36 +1,42 @@
-//! Various utilities for manipulating Ethereum related dat
+//! Various utilities for manipulating Ethereum related data.
+
 use ethabi::ethereum_types::H256;
 use tiny_keccak::{Hasher, Keccak};
 
-const PREFIX: &str = "\x19Ethereum Signed Message:\n";
-
-/// Hash a message according to EIP-191.
+/// Hash a message according to [EIP-191] (version `0x01`).
+///
+/// The final message is a UTF-8 string, encoded as follows:
+/// `"\x19Ethereum Signed Message:\n" + message.length + message`
+///
+/// This message is then hashed using [Keccak-256](keccak256).
 ///
-/// The data is a UTF-8 encoded string and will enveloped as follows:
-/// `"\x19Ethereum Signed Message:\n" + message.length + message` and hashed
-/// using keccak256.
-pub fn hash_message<S>(message: S) -> H256
-where
-    S: AsRef<[u8]>,
-{
+/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
+pub fn hash_message<T: AsRef<[u8]>>(message: T) -> H256 {
+    const PREFIX: &str = "\x19Ethereum Signed Message:\n";
+
     let message = message.as_ref();
+    let len = message.len();
+    let len_string = len.to_string();
 
-    let mut eth_message = format!("{PREFIX}{}", message.len()).into_bytes();
+    let mut eth_message = Vec::with_capacity(PREFIX.len() + len_string.len() + len);
+    eth_message.extend_from_slice(PREFIX.as_bytes());
+    eth_message.extend_from_slice(len_string.as_bytes());
     eth_message.extend_from_slice(message);
 
-    keccak256(&eth_message).into()
+    H256(keccak256(&eth_message))
 }
 
 /// Compute the Keccak-256 hash of input bytes.
+///
+/// Note that strings are interpreted as UTF-8 bytes,
 // TODO: Add Solidity Keccak256 packing support
-pub fn keccak256<S>(bytes: S) -> [u8; 32]
-where
-    S: AsRef<[u8]>,
-{
+pub fn keccak256<T: AsRef<[u8]>>(bytes: T) -> [u8; 32] {
     let mut output = [0u8; 32];
+
     let mut hasher = Keccak::v256();
     hasher.update(bytes.as_ref());
     hasher.finalize(&mut output);
+
     output
 }
 
@@ -53,7 +59,7 @@ pub fn id<S: AsRef<str>>(signature: S) -> [u8; 4] {
 ///
 /// If the type returns an error during serialization.
 pub fn serialize<T: serde::Serialize>(t: &T) -> serde_json::Value {
-    serde_json::to_value(t).expect("Types never fail to serialize.")
+    serde_json::to_value(t).expect("Failed to serialize value")
 }
 
 #[cfg(test)]
diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs
index ff5ca22a..9528fa01 100644
--- a/ethers-core/src/utils/mod.rs
+++ b/ethers-core/src/utils/mod.rs
@@ -36,7 +36,7 @@ pub use rlp;
 /// Re-export hex
 pub use hex;
 
-use crate::types::{Address, Bytes, ParseI256Error, I256, U256};
+use crate::types::{Address, ParseI256Error, I256, U256};
 use elliptic_curve::sec1::ToEncodedPoint;
 use ethabi::ethereum_types::FromDecStrErr;
 use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey};
@@ -212,10 +212,7 @@ where
 /// assert_eq!(eth, parse_ether(1usize).unwrap());
 /// assert_eq!(eth, parse_ether("1").unwrap());
 /// ```
-pub fn parse_ether<S>(eth: S) -> Result<U256, ConversionError>
-where
-    S: ToString,
-{
+pub fn parse_ether<S: ToString>(eth: S) -> Result<U256, ConversionError> {
     Ok(parse_units(eth, "ether")?.into())
 }
 
@@ -308,10 +305,11 @@ pub fn get_contract_address(sender: impl Into<Address>, nonce: impl Into<U256>)
 /// keccak256( 0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12..]
 pub fn get_create2_address(
     from: impl Into<Address>,
-    salt: impl Into<Bytes>,
-    init_code: impl Into<Bytes>,
+    salt: impl AsRef<[u8]>,
+    init_code: impl AsRef<[u8]>,
 ) -> Address {
-    get_create2_address_from_hash(from, salt, keccak256(init_code.into().as_ref()).to_vec())
+    let init_code_hash = keccak256(init_code.as_ref());
+    get_create2_address_from_hash(from, salt, init_code_hash)
 }
 
 /// Returns the CREATE2 address of a smart contract as specified in
@@ -332,9 +330,7 @@ pub fn get_create2_address(
 ///     utils::{get_create2_address_from_hash, keccak256},
 /// };
 ///
-/// let UNISWAP_V3_POOL_INIT_CODE_HASH = Bytes::from(
-///     hex::decode("e34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54").unwrap(),
-/// );
+/// let init_code_hash = hex::decode("e34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54").unwrap();
 /// let factory: Address = "0x1F98431c8aD98523631AE4a59f267346ea31F984"
 ///     .parse()
 ///     .unwrap();
@@ -344,19 +340,18 @@ pub fn get_create2_address(
 /// let token1: Address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
 ///     .parse()
 ///     .unwrap();
-/// let fee = 500;
+/// let fee = U256::from(500_u64);
 ///
 /// // abi.encode(token0 as address, token1 as address, fee as uint256)
-/// let input = abi::encode(&vec![
+/// let input = abi::encode(&[
 ///     Token::Address(token0),
 ///     Token::Address(token1),
-///     Token::Uint(U256::from(fee)),
+///     Token::Uint(fee),
 /// ]);
 ///
 /// // keccak256(abi.encode(token0, token1, fee))
 /// let salt = keccak256(&input);
-/// let pool_address =
-///     get_create2_address_from_hash(factory, salt.to_vec(), UNISWAP_V3_POOL_INIT_CODE_HASH);
+/// let pool_address = get_create2_address_from_hash(factory, salt, init_code_hash);
 ///
 /// assert_eq!(
 ///     pool_address,
@@ -367,12 +362,18 @@ pub fn get_create2_address(
 /// ```
 pub fn get_create2_address_from_hash(
     from: impl Into<Address>,
-    salt: impl Into<Bytes>,
-    init_code_hash: impl Into<Bytes>,
+    salt: impl AsRef<[u8]>,
+    init_code_hash: impl AsRef<[u8]>,
 ) -> Address {
-    let bytes =
-        [&[0xff], from.into().as_bytes(), salt.into().as_ref(), init_code_hash.into().as_ref()]
-            .concat();
+    let from = from.into();
+    let salt = salt.as_ref();
+    let init_code_hash = init_code_hash.as_ref();
+
+    let mut bytes = Vec::with_capacity(1 + 20 + salt.len() + init_code_hash.len());
+    bytes.push(0xff);
+    bytes.extend_from_slice(from.as_bytes());
+    bytes.extend_from_slice(salt);
+    bytes.extend_from_slice(init_code_hash);
 
     let hash = keccak256(bytes);
 
@@ -388,11 +389,20 @@ pub fn secret_key_to_address(secret_key: &SigningKey) -> Address {
     let public_key = public_key.as_bytes();
     debug_assert_eq!(public_key[0], 0x04);
     let hash = keccak256(&public_key[1..]);
-    Address::from_slice(&hash[12..])
+
+    let mut bytes = [0u8; 20];
+    bytes.copy_from_slice(&hash[12..]);
+    Address::from(bytes)
 }
 
-/// Converts an Ethereum address to the checksum encoding
-/// Ref: <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md>
+/// Encodes an Ethereum address to its [EIP-55] checksum.
+///
+/// You can optionally specify an [EIP-155 chain ID] to encode the address using the [EIP-1191]
+/// extension.
+///
+/// [EIP-55]: https://eips.ethereum.org/EIPS/eip-55
+/// [EIP-155 chain ID]: https://eips.ethereum.org/EIPS/eip-155
+/// [EIP-1191]: https://eips.ethereum.org/EIPS/eip-1191
 pub fn to_checksum(addr: &Address, chain_id: Option<u8>) -> String {
     let prefixed_addr = match chain_id {
         Some(chain_id) => format!("{chain_id}0x{addr:x}"),
diff --git a/ethers-providers/src/transports/common.rs b/ethers-providers/src/transports/common.rs
index 747e1405..a23b68d5 100644
--- a/ethers-providers/src/transports/common.rs
+++ b/ethers-providers/src/transports/common.rs
@@ -1,5 +1,6 @@
 // Code adapted from: https://github.com/althea-net/guac_rs/tree/master/web3/src/jsonrpc
 
+use base64::{engine::general_purpose, Engine};
 use ethers_core::types::U256;
 use serde::{
     de::{self, MapAccess, Unexpected, Visitor},
@@ -190,8 +191,10 @@ pub enum Authorization {
 }
 
 impl Authorization {
-    pub fn basic(username: impl Into<String>, password: impl Into<String>) -> Self {
-        let auth_secret = base64::encode(username.into() + ":" + &password.into());
+    pub fn basic(username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
+        let username = username.as_ref();
+        let password = password.as_ref();
+        let auth_secret = general_purpose::STANDARD.encode(format!("{username}:{password}"));
         Self::Basic(auth_secret)
     }
 
diff --git a/ethers-solc/src/sourcemap.rs b/ethers-solc/src/sourcemap.rs
index 5e722368..f9b75844 100644
--- a/ethers-solc/src/sourcemap.rs
+++ b/ethers-solc/src/sourcemap.rs
@@ -512,7 +512,7 @@ mod tests {
         let source_maps = include_str!("../test-data/out-source-maps.txt");
 
         for (line, s) in source_maps.lines().enumerate() {
-            parse(s).unwrap_or_else(|_| panic!("Failed to parse line {}", line));
+            parse(s).unwrap_or_else(|_| panic!("Failed to parse line {line}"));
         }
     }
 
diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs
index bb4d0a3b..aba0a883 100644
--- a/ethers-solc/src/utils.rs
+++ b/ethers-solc/src/utils.rs
@@ -506,7 +506,7 @@ mod tests {
         let existing = path.join("Test.sol");
         let non_existing = path.join("test.sol");
         std::fs::write(
-            &existing,
+            existing,
             r#"
 pragma solidity ^0.8.10;
 contract A {}
diff --git a/examples/providers/examples/retry.rs b/examples/providers/examples/retry.rs
index 9146bd84..8f78b689 100644
--- a/examples/providers/examples/retry.rs
+++ b/examples/providers/examples/retry.rs
@@ -19,7 +19,7 @@ async fn main() -> eyre::Result<()> {
         .rate_limit_retries(10)
         .timeout_retries(3)
         .initial_backoff(Duration::from_millis(500))
-        .build(provider, Box::new(HttpRateLimitRetryPolicy::default()));
+        .build(provider, Box::<ethers::providers::HttpRateLimitRetryPolicy>::default());
 
     // Send a JSON-RPC request for the latest block
     let block_num = "latest".to_string();


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