Repository: gakonst/ethers-rs
Pull request #1666 (2022-09-05 00:25:21)

feat(contract): add extra Multicall helper methods
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation

<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

`Multicall` has some extra helper methods such as `getBlockHash` or `getBlockNumber`.

## Solution

Add these methods to `Multicall` in the same style of the already present `eth_balance_of`. Decided against adding the `allow_failure` param on most of these helper calls as they are base opcodes included in all EVM implementations (except `BASEFEE`) so they shouldn't fail if we're using the base Multicall contract.
Also did some small docs changes and refactoring.

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [x] Added Documentation
- [x] Updated the changelog


DaniPopes: > reasonable, lgtm
> 
> but should we prefix these calls with `add_`?

Makes sense, following `add_call`, but changing `eth_balance_of` would be a breaking change

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa26373d..c544f355 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,7 @@
 
 ### Unreleased
 
-- Fix RLP encoding of `TransactionReceipt`
+- Fix RLP encoding of `TransactionReceipt` [#1661](https://github.com/gakonst/ethers-rs/pull/1661)
 - Add `Unit8` helper type [#1639](https://github.com/gakonst/ethers-rs/pull/1639)
 - Add `evm.deployedBytecode.immutableReferences` output selector [#1523](https://github.com/gakonst/ethers-rs/pull/1523)
 - Added `get_erc1155_token_transfer_events` function for etherscan client [#1503](https://github.com/gakonst/ethers-rs/pull/1503)
@@ -263,6 +263,10 @@
 
 ### Unreleased
 
+- Add extra Multicall helper methods
+  [#1666](https://github.com/gakonst/ethers-rs/pull/1666)
+- Update Multicall to Multicall3
+  [#1584](https://github.com/gakonst/ethers-rs/pull/1584)
 - Add `Event::stream_with_meta` and `Event::subscribe_with_meta`
   [#1483](https://github.com/gakonst/ethers-rs/pull/1483)
 - Added tx builder methods to `ContractFactory`
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index 15e77c2c..2754e819 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -96,6 +96,17 @@ pub enum MulticallError<M: Middleware> {
 
 pub type Result<T, M> = std::result::Result<T, MulticallError<M>>;
 
+/// Helper struct for managing calls to be made to the `function` in smart contract `target`
+/// with `data`.
+#[derive(Clone, Debug)]
+pub struct Call {
+    target: Address,
+    data: Bytes,
+    value: U256,
+    allow_failure: bool,
+    function: Function,
+}
+
 /// The version of the [`Multicall`](super::Multicall).
 /// Used to determine which methods of the Multicall smart contract to use:
 /// - [`Multicall`] : `aggregate((address,bytes)[])`
@@ -224,16 +235,16 @@ impl TryFrom<u8> for MulticallVersion {
 /// multicall = multicall.version(MulticallVersion::Multicall);
 /// multicall
 ///     .clear_calls()
-///     .eth_balance_of(address_1, false)
-///     .eth_balance_of(address_2, false);
+///     .add_get_eth_balance(address_1, false)
+///     .add_get_eth_balance(address_2, false);
 /// let _balances: (U256, U256) = multicall.call().await?;
 ///
 /// // or with version 2 and above
 /// multicall = multicall.version(MulticallVersion::Multicall3);
 /// multicall
 ///     .clear_calls()
-///     .eth_balance_of(address_1, false)
-///     .eth_balance_of(address_2, false);
+///     .add_get_eth_balance(address_1, false)
+///     .add_get_eth_balance(address_2, false);
 /// let _balances: ((bool, U256), (bool, U256)) = multicall.call().await?;
 ///
 /// # Ok(())
@@ -248,6 +259,7 @@ impl TryFrom<u8> for MulticallVersion {
 /// [`add_call`]: #method.add_call
 /// [`call`]: #method.call
 /// [`send`]: #method.send
+#[derive(Clone)]
 #[must_use = "Multicall does nothing unless you use `call` or `send`"]
 pub struct Multicall<M> {
     version: MulticallVersion,
@@ -257,18 +269,7 @@ pub struct Multicall<M> {
     contract: MulticallContract<M>,
 }
 
-impl<M> Clone for Multicall<M> {
-    fn clone(&self) -> Self {
-        Multicall {
-            calls: self.calls.clone(),
-            block: self.block,
-            contract: self.contract.clone(),
-            legacy: self.legacy,
-            version: self.version,
-        }
-    }
-}
-
+// Manually implement Debug due to Middleware trait bounds.
 impl<M: Middleware> std::fmt::Debug for Multicall<M> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("Multicall")
@@ -281,17 +282,6 @@ impl<M: Middleware> std::fmt::Debug for Multicall<M> {
     }
 }
 
-/// Helper struct for managing calls to be made to the `function` in smart contract `target`
-/// with `data`.
-#[derive(Clone, Debug)]
-pub struct Call {
-    target: Address,
-    data: Bytes,
-    value: U256,
-    allow_failure: bool,
-    function: Function,
-}
-
 impl<M: Middleware> Multicall<M> {
     /// Creates a new Multicall instance from the provided client. If provided with an `address`,
     /// it instantiates the Multicall contract with that address, otherwise it defaults to
@@ -410,13 +400,13 @@ impl<M: Middleware> Multicall<M> {
         self
     }
 
-    /// Sets the `block` field for the multicall aggregate call.
+    /// Sets the `block` field of the Multicall aggregate call.
     pub fn block(mut self, block: impl Into<BlockNumber>) -> Self {
         self.block = Some(block.into());
         self
     }
 
-    /// Appends a `call` to the list of calls for the Multicall instance.
+    /// Appends a `call` to the list of calls of the Multicall instance.
     ///
     /// Version specific details:
     /// - 1: `allow_failure` is ignored.
@@ -447,18 +437,89 @@ impl<M: Middleware> Multicall<M> {
         }
     }
 
-    /// Appends a `call` to the list of calls for the Multicall instance for querying
-    /// the ETH balance of an address
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the block hash
+    /// of a given block number.
     ///
-    /// # Panics
+    /// Note: this call will return 0 if `block_number` is not one of the most recent 256 blocks.
+    /// ([Reference](https://docs.soliditylang.org/en/latest/units-and-global-variables.html?highlight=blockhash#block-and-transaction-properties))
+    pub fn add_get_block_hash(&mut self, block_number: impl Into<U256>) -> &mut Self {
+        let call = self.contract.get_block_hash(block_number.into());
+        self.add_call(call, false)
+    }
+
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the current
+    /// block number.
+    pub fn add_get_block_number(&mut self) -> &mut Self {
+        let call = self.contract.get_block_number();
+        self.add_call(call, false)
+    }
+
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the current
+    /// block coinbase address.
+    pub fn add_get_current_block_coinbase(&mut self) -> &mut Self {
+        let call = self.contract.get_current_block_coinbase();
+        self.add_call(call, false)
+    }
+
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the current
+    /// block difficulty.
     ///
-    /// If more than the maximum number of supported calls are added (16). The maximum limit is
-    /// constrained due to tokenization/detokenization support for tuples.
-    pub fn eth_balance_of(&mut self, addr: Address, allow_failure: bool) -> &mut Self {
-        let call = self.contract.get_eth_balance(addr);
+    /// Note: in a post-merge environment, the return value of this call will be the output of the
+    /// randomness beacon provided by the beacon chain.
+    /// ([Reference](https://eips.ethereum.org/EIPS/eip-4399#abstract))
+    pub fn add_get_current_block_difficulty(&mut self) -> &mut Self {
+        let call = self.contract.get_current_block_difficulty();
+        self.add_call(call, false)
+    }
+
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the current
+    /// block gas limit.
+    pub fn add_get_current_block_gas_limit(&mut self) -> &mut Self {
+        let call = self.contract.get_current_block_gas_limit();
+        self.add_call(call, false)
+    }
+
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the current
+    /// block timestamp.
+    pub fn add_get_current_block_timestamp(&mut self) -> &mut Self {
+        let call = self.contract.get_current_block_timestamp();
+        self.add_call(call, false)
+    }
+
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the ETH
+    /// balance of an address.
+    pub fn add_get_eth_balance(
+        &mut self,
+        address: impl Into<Address>,
+        allow_failure: bool,
+    ) -> &mut Self {
+        let call = self.contract.get_eth_balance(address.into());
         self.add_call(call, allow_failure)
     }
 
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the last
+    /// block hash.
+    pub fn add_get_last_block_hash(&mut self) -> &mut Self {
+        let call = self.contract.get_last_block_hash();
+        self.add_call(call, false)
+    }
+
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the current
+    /// block base fee.
+    ///
+    /// Note: this call will fail if the chain that it is called on does not implement the
+    /// [BASEFEE opcode](https://eips.ethereum.org/EIPS/eip-3198).
+    pub fn add_get_basefee(&mut self, allow_failure: bool) -> &mut Self {
+        let call = self.contract.get_basefee();
+        self.add_call(call, allow_failure)
+    }
+
+    /// Appends a `call` to the list of calls of the Multicall instance for querying the chain id.
+    pub fn add_get_chain_id(&mut self) -> &mut Self {
+        let call = self.contract.get_chain_id();
+        self.add_call(call, false)
+    }
+
     /// Clears the batch of calls from the Multicall instance.
     /// Re-use the already instantiated Multicall to send a different batch of transactions or do
     /// another aggregate query.
diff --git a/ethers-solc/src/artifact_output/mod.rs b/ethers-solc/src/artifact_output/mod.rs
index 37528dc1..d5e23367 100644
--- a/ethers-solc/src/artifact_output/mod.rs
+++ b/ethers-solc/src/artifact_output/mod.rs
@@ -563,7 +563,7 @@ where
 /// relationship (1-N+).
 pub trait ArtifactOutput {
     /// Represents the artifact that will be stored for a `Contract`
-    type Artifact: Artifact + DeserializeOwned + Serialize + fmt::Debug;
+    type Artifact: Artifact + DeserializeOwned + Serialize + fmt::Debug + Send + Sync;
 
     /// Handle the aggregated set of compiled contracts from the solc [`crate::CompilerOutput`].
     ///
diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs
index 4943b69f..0a335fa9 100644
--- a/ethers-solc/src/cache.rs
+++ b/ethers-solc/src/cache.rs
@@ -301,12 +301,19 @@ impl SolFilesCache {
     /// let artifacts = cache.read_artifacts::<CompactContractBytecode>().unwrap();
     /// # }
     /// ```
-    pub fn read_artifacts<Artifact: DeserializeOwned>(&self) -> Result<Artifacts<Artifact>> {
-        let mut artifacts = ArtifactsMap::new();
-        for (file, entry) in self.files.iter() {
-            let file_name = format!("{}", file.display());
-            artifacts.insert(file_name, entry.read_artifact_files()?);
-        }
+    pub fn read_artifacts<Artifact: DeserializeOwned + Send + Sync>(
+        &self,
+    ) -> Result<Artifacts<Artifact>> {
+        use rayon::prelude::*;
+
+        let artifacts = self
+            .files
+            .par_iter()
+            .map(|(file, entry)| {
+                let file_name = format!("{}", file.display());
+                entry.read_artifact_files().map(|files| (file_name, files))
+            })
+            .collect::<Result<ArtifactsMap<_>>>()?;
         Ok(Artifacts(artifacts))
     }
 


Test patch:
diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs
index 915a17b9..756210b2 100644
--- a/ethers-contract/tests/it/contract.rs
+++ b/ethers-contract/tests/it/contract.rs
@@ -499,9 +499,9 @@ mod eth_tests {
         // so should have 100 ETH
         multicall
             .clear_calls()
-            .eth_balance_of(addrs[4], false)
-            .eth_balance_of(addrs[5], false)
-            .eth_balance_of(addrs[6], false);
+            .add_get_eth_balance(addrs[4], false)
+            .add_get_eth_balance(addrs[5], false)
+            .add_get_eth_balance(addrs[6], false);
 
         let balances: (U256, U256, U256) = multicall.call().await.unwrap();
         assert_eq!(balances.0, U256::from(10_000_000_000_000_000_000_000u128));
@@ -653,8 +653,8 @@ mod eth_tests {
         // ((bool, U256)) == (bool, U256)
         let bal_before: ((bool, U256), (bool, U256)) = multicall
             .clear_calls()
-            .eth_balance_of(rc_addr, false)
-            .eth_balance_of(rc_addr, false)
+            .add_get_eth_balance(rc_addr, false)
+            .add_get_eth_balance(rc_addr, false)
             .call()
             .await
             .unwrap();
@@ -665,8 +665,8 @@ mod eth_tests {
 
         let bal_after: ((bool, U256), (bool, U256)) = multicall
             .clear_calls()
-            .eth_balance_of(rc_addr, false)
-            .eth_balance_of(rc_addr, false)
+            .add_get_eth_balance(rc_addr, false)
+            .add_get_eth_balance(rc_addr, false)
             .call()
             .await
             .unwrap();