Skip to content

Commit

Permalink
feat(contract): add_calls and call_array for multicall
Browse files Browse the repository at this point in the history
  • Loading branch information
onsails committed Dec 15, 2022
1 parent b76e790 commit 6a73cb0
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 2 additions & 26 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 63 additions & 3 deletions ethers-contract/src/multicall/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ impl<M: Middleware> Multicall<M> {
let chain_id =
client.get_chainid().await.map_err(ContractError::MiddlewareError)?;
if !MULTICALL_SUPPORTED_CHAIN_IDS.contains(&chain_id) {
return Err(MulticallError::InvalidChainId(chain_id))
return Err(MulticallError::InvalidChainId(chain_id));
}
MULTICALL_ADDRESS
}
Expand Down Expand Up @@ -354,7 +354,7 @@ impl<M: Middleware> Multicall<M> {
let chain_id =
chain_id.expect("Must provide at least one of: address or chain ID.").into();
if !MULTICALL_SUPPORTED_CHAIN_IDS.contains(&chain_id) {
return Err(MulticallError::InvalidChainId(chain_id))
return Err(MulticallError::InvalidChainId(chain_id));
}
MULTICALL_ADDRESS
}
Expand Down Expand Up @@ -437,6 +437,27 @@ impl<M: Middleware> Multicall<M> {
}
}

/// 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<D: Detokenize>(
&mut self,
calls: Vec<ContractCall<M, D>>,
allow_failure: bool,
) -> &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.
///
Expand Down Expand Up @@ -615,6 +636,45 @@ impl<M: Middleware> Multicall<M> {
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<dyn std::error::Error>> {
/// # use ethers_core::types::{U256, Address};
/// # use ethers_providers::{Provider, Http};
/// # use ethers_contract::Multicall;
/// # use std::convert::TryFrom;
/// #
/// # let client = Provider::<Http>::try_from("http://localhost:8545")?;
/// #
/// # let multicall = Multicall::new(client, None).await?;
/// // If the all Solidity function calls `returns (uint256)`:
/// let result: Vec<U256> = multicall.call().await?;
/// # Ok(())
/// # }
/// ```
pub async fn call_array<D: Detokenize>(&self) -> Result<Vec<D>, M> {
let tokens = self.call_raw().await?;
let res: std::result::Result<Vec<D>, ContractError<M>> = 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.
///
Expand Down Expand Up @@ -705,7 +765,7 @@ impl<M: Middleware> Multicall<M> {
// still do so because of other calls that are in the same multicall
// aggregate.
if !call.allow_failure {
return Err(MulticallError::IllegalRevert)
return Err(MulticallError::IllegalRevert);
}

// Decode with "Error(string)" (0x08c379a0)
Expand Down
36 changes: 28 additions & 8 deletions ethers-contract/tests/it/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod eth_tests {
use ethers_derive_eip712::*;
use ethers_providers::{Http, Middleware, PendingTransaction, Provider, StreamExt};
use ethers_signers::{LocalWallet, Signer};
use std::iter::FromIterator;
use std::{convert::TryFrom, sync::Arc, time::Duration};

#[tokio::test]
Expand Down Expand Up @@ -517,10 +518,28 @@ 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<U256> = multicall.call_array().await.unwrap();
assert_eq!(balances, Vec::from_iter(valid_balances.iter().copied()));
// let balances: Vec<U256> = multicall.call_array::<U256>().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();
Expand All @@ -535,11 +554,12 @@ mod eth_tests {
.await
.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(
std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap())
.take(17)
.collect(),
false,
);

// must use `call_raw` as `.calls` > 16
let tokens = multicall.call_raw().await.unwrap();
Expand Down

0 comments on commit 6a73cb0

Please sign in to comment.