Expose New `call_raw` API that permits MultiCalls without Detokenization (#915)
* Add `call_raw` method that forgoes detokenization for MultiCall. Have `call` wrap around `call_raw` permitting user to handle detokenization themselves if they wish * Improve documentation: Add details to the documentation example that informs the user of their responsibility to detokenize results
This commit is contained in:
parent
cd24022515
commit
e3f0621d43
|
@ -187,14 +187,7 @@ impl<M: Middleware> Multicall<M> {
|
|||
}
|
||||
|
||||
/// Appends a `call` to the list of calls for the Multicall instance
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If more than the maximum number of supported calls are added. The maximum
|
||||
/// limits is constrained due to tokenization/detokenization support for tuples
|
||||
pub fn add_call<D: Detokenize>(&mut self, call: ContractCall<M, D>) -> &mut Self {
|
||||
assert!(self.calls.len() < 16, "Cannot support more than {} calls", 16);
|
||||
|
||||
match (call.tx.to(), call.tx.data()) {
|
||||
(Some(NameOrAddress::Address(target)), Some(data)) => {
|
||||
let call = Call { target: *target, data: data.clone(), function: call.function };
|
||||
|
@ -284,23 +277,58 @@ impl<M: Middleware> Multicall<M> {
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If more than the maximum number of supported calls are added. The maximum
|
||||
/// limits is constrained due to tokenization/detokenization support for tuples
|
||||
///
|
||||
/// Note: this method _does not_ send a transaction from your account
|
||||
///
|
||||
/// [`ContractError<M>`]: crate::ContractError<M>
|
||||
pub async fn call<D: Detokenize>(&self) -> Result<D, ContractError<M>> {
|
||||
let contract_call = self.as_contract_call();
|
||||
assert!(self.calls.len() < 16, "Cannot decode more than {} calls", 16);
|
||||
let tokens = self.call_raw().await?;
|
||||
let tokens = vec![Token::Tuple(tokens)];
|
||||
let data = D::from_tokens(tokens)?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Queries the Ethereum blockchain via an `eth_call`, but via the Multicall contract and
|
||||
/// without detokenization.
|
||||
///
|
||||
/// It returns a [`ContractError<M>`] if there is any error in the RPC call.
|
||||
///
|
||||
/// ```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?;
|
||||
/// // The consumer of the API is responsible for detokenizing the results
|
||||
/// // as the results will be a Vec<Token>
|
||||
/// let tokens = multicall.call_raw().await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Note: this method _does not_ send a transaction from your account
|
||||
///
|
||||
/// [`ContractError<M>`]: crate::ContractError<M>
|
||||
|
||||
pub async fn call_raw(&self) -> Result<Vec<Token>, ContractError<M>> {
|
||||
let contract_call = self.as_contract_call();
|
||||
// Fetch response from the Multicall contract
|
||||
let (_block_number, return_data) = contract_call.call().await?;
|
||||
|
||||
// Decode return data into ABI tokens
|
||||
let tokens = self
|
||||
.calls
|
||||
.iter()
|
||||
.zip(&return_data)
|
||||
.map(|(call, bytes)| {
|
||||
let mut tokens: Vec<Token> = call.function.decode_output(bytes.as_ref())?;
|
||||
|
||||
Ok(match tokens.len() {
|
||||
0 => Token::Tuple(vec![]),
|
||||
1 => tokens.remove(0),
|
||||
|
@ -308,14 +336,7 @@ impl<M: Middleware> Multicall<M> {
|
|||
})
|
||||
})
|
||||
.collect::<Result<Vec<Token>, ContractError<M>>>()?;
|
||||
|
||||
// Form tokens that represent tuples
|
||||
let tokens = vec![Token::Tuple(tokens)];
|
||||
|
||||
// Detokenize from the tokens into the provided tuple D
|
||||
let data = D::from_tokens(tokens)?;
|
||||
|
||||
Ok(data)
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
/// Signs and broadcasts a batch of transactions by using the Multicall contract as proxy.
|
||||
|
|
|
@ -9,6 +9,7 @@ mod eth_tests {
|
|||
use super::*;
|
||||
use ethers_contract::{LogMeta, Multicall};
|
||||
use ethers_core::{
|
||||
abi::{Detokenize, Token, Tokenizable},
|
||||
types::{transaction::eip712::Eip712, Address, BlockId, Bytes, I256, U256},
|
||||
utils::{keccak256, Ganache},
|
||||
};
|
||||
|
@ -374,7 +375,8 @@ mod eth_tests {
|
|||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||
|
||||
// launch ganache
|
||||
let ganache = Ganache::new().spawn();
|
||||
// some tests expect 100 ether (-e === --wallet.defaultBalance in ether)
|
||||
let ganache = Ganache::new().arg("-e").arg("100").spawn();
|
||||
|
||||
// Instantiate the clients. We assume that clients consume the provider and the wallet
|
||||
// (which makes sense), so for multi-client tests, you must clone the provider.
|
||||
|
@ -425,6 +427,7 @@ mod eth_tests {
|
|||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
not_so_simple_contract
|
||||
.connect(client3.clone())
|
||||
.method::<_, H256>("setValue", "reset second".to_owned())
|
||||
|
@ -498,10 +501,42 @@ mod eth_tests {
|
|||
.eth_balance_of(addrs[4])
|
||||
.eth_balance_of(addrs[5])
|
||||
.eth_balance_of(addrs[6]);
|
||||
|
||||
let balances: (U256, U256, U256) = multicall.call().await.unwrap();
|
||||
assert_eq!(balances.0, U256::from(100000000000000000000u128));
|
||||
assert_eq!(balances.1, U256::from(100000000000000000000u128));
|
||||
assert_eq!(balances.2, U256::from(100000000000000000000u128));
|
||||
assert_eq!(balances.0, U256::from(100_000_000_000_000_000_000u128));
|
||||
assert_eq!(balances.1, U256::from(100_000_000_000_000_000_000u128));
|
||||
assert_eq!(balances.2, U256::from(100_000_000_000_000_000_000u128));
|
||||
|
||||
// clear multicall so we can test `call_raw` w/ >16 calls
|
||||
multicall.clear_calls();
|
||||
|
||||
// clear the current value
|
||||
simple_contract
|
||||
.connect(client2.clone())
|
||||
.method::<_, H256>("setValue", "many".to_owned())
|
||||
.unwrap()
|
||||
.legacy()
|
||||
.send()
|
||||
.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);
|
||||
}
|
||||
|
||||
// must use `call_raw` as `.calls` > 16
|
||||
let tokens = multicall.call_raw().await.unwrap();
|
||||
// if want to use, must detokenize manually
|
||||
let results: Vec<String> = tokens
|
||||
.iter()
|
||||
.map(|token| {
|
||||
// decode manually using Tokenizable method
|
||||
String::from_token(token.to_owned()).unwrap()
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(results, ["many"; 17]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
Loading…
Reference in New Issue