From 279aea6323b824609aab7dc2963c3b6637c7fcc9 Mon Sep 17 00:00:00 2001 From: Andrey Kuznetsov Date: Sun, 18 Dec 2022 15:05:02 +0400 Subject: [PATCH] feat(contract): add_calls and call_array for multicall (#1941) --- CHANGELOG.md | 1 + ethers-contract/src/multicall/mod.rs | 60 ++++++++++++++++++++++++++++ ethers-contract/tests/it/contract.rs | 33 +++++++++++---- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1b26a77..7b157dc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ - [#842](https://github.com/gakonst/ethers-rs/issues/842) Add support for I256 types in `parse_units` and `format_units`. Added `twos_complement` function for I256. - [#1934](https://github.com/gakonst/ethers-rs/pull/1934) Allow 16 calls in multicall. +- [#1941](https://github.com/gakonst/ethers-rs/pull/1941) Add `add_calls` and `call_array` for `Multicall`. ## ethers-contract-abigen diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs index 8dd24542..56dca427 100644 --- a/ethers-contract/src/multicall/mod.rs +++ b/ethers-contract/src/multicall/mod.rs @@ -437,6 +437,27 @@ impl Multicall { } } + /// Appends multiple `call`s to the list of calls of the Multicall instance. + /// + /// Version specific details: + /// - 1: `allow_failure` is ignored. + /// - >=2: `allow_failure` specifies whether or not this call is allowed to revert in the + /// multicall. + /// - 3: Transaction values are used when broadcasting transactions with [`send`], otherwise + /// they are always ignored. + /// + /// [`send`]: #method.send + pub fn add_calls( + &mut self, + allow_failure: bool, + calls: impl IntoIterator>, + ) -> &mut Self { + for call in calls { + self.add_call(call, allow_failure); + } + self + } + /// Appends a `call` to the list of calls of the Multicall instance for querying the block hash /// of a given block number. /// @@ -615,6 +636,45 @@ impl Multicall { Ok(data) } + /// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract, assuming + /// that every call returns same data type. + /// + /// Note: this method _does not_ send a transaction from your account. + /// + /// # Errors + /// + /// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing + /// the tokens back to the expected return type. + /// + /// # Examples + /// + /// The return type must be annotated while calling this method: + /// + /// ```no_run + /// # async fn foo() -> Result<(), Box> { + /// # use ethers_core::types::{U256, Address}; + /// # use ethers_providers::{Provider, Http}; + /// # use ethers_contract::Multicall; + /// # use std::convert::TryFrom; + /// # + /// # let client = Provider::::try_from("http://localhost:8545")?; + /// # + /// # let multicall = Multicall::new(client, None).await?; + /// // If the all Solidity function calls `returns (uint256)`: + /// let result: Vec = multicall.call().await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn call_array(&self) -> Result, M> { + let tokens = self.call_raw().await?; + let res: std::result::Result, ContractError> = tokens + .into_iter() + .map(|token| D::from_tokens(vec![token]).map_err(ContractError::DetokenizationError)) + .collect(); + + Ok(res?) + } + /// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract and /// without detokenization. /// diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs index e15005aa..8fdd0469 100644 --- a/ethers-contract/tests/it/contract.rs +++ b/ethers-contract/tests/it/contract.rs @@ -15,7 +15,7 @@ mod eth_tests { use ethers_derive_eip712::*; use ethers_providers::{Http, Middleware, PendingTransaction, Provider, StreamExt}; use ethers_signers::{LocalWallet, Signer}; - use std::{convert::TryFrom, sync::Arc, time::Duration}; + use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration}; #[tokio::test] async fn deploy_and_call_contract() { @@ -517,10 +517,26 @@ mod eth_tests { .add_get_eth_balance(addrs[5], false) .add_get_eth_balance(addrs[6], false); + let valid_balances = [ + U256::from(10_000_000_000_000_000_000_000u128), + U256::from(10_000_000_000_000_000_000_000u128), + U256::from(10_000_000_000_000_000_000_000u128), + ]; + let balances: (U256, U256, U256) = multicall.call().await.unwrap(); - assert_eq!(balances.0, U256::from(10_000_000_000_000_000_000_000u128)); - assert_eq!(balances.1, U256::from(10_000_000_000_000_000_000_000u128)); - assert_eq!(balances.2, U256::from(10_000_000_000_000_000_000_000u128)); + assert_eq!(balances.0, valid_balances[0]); + assert_eq!(balances.1, valid_balances[1]); + assert_eq!(balances.2, valid_balances[2]); + + // call_array + multicall + .clear_calls() + .add_get_eth_balance(addrs[4], false) + .add_get_eth_balance(addrs[5], false) + .add_get_eth_balance(addrs[6], false); + + let balances: Vec = multicall.call_array().await.unwrap(); + assert_eq!(balances, Vec::from_iter(valid_balances.iter().copied())); // clear multicall so we can test `call_raw` w/ >16 calls multicall.clear_calls(); @@ -536,10 +552,11 @@ mod eth_tests { .unwrap(); // build up a list of calls greater than the 16 max restriction - for i in 0..=16 { - let call = simple_contract.method::<_, String>("getValue", ()).unwrap(); - multicall.add_call(call, false); - } + multicall.add_calls( + false, + std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap()) + .take(17), // .collect(), + ); // must use `call_raw` as `.calls` > 16 let tokens = multicall.call_raw().await.unwrap();