feat(contract): improve Multicall result handling (#2164)
* nits * clippy * ordering * move * chore: edition 2021 * chore: detokenize nit * feat(contract): improve Multicall result handling * docs: update CHANGELOG.md * feat: make fields public * chore: clippy
This commit is contained in:
parent
3732de844c
commit
e55b8116f3
|
@ -302,6 +302,8 @@
|
||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
|
|
||||||
|
- (Breaking) Improve Multicall result handling
|
||||||
|
[#2164](https://github.com/gakonst/ethers-rs/pull/2105)
|
||||||
- (Breaking) Make `Event` objects generic over borrow & remove lifetime
|
- (Breaking) Make `Event` objects generic over borrow & remove lifetime
|
||||||
[#2105](https://github.com/gakonst/ethers-rs/pull/2105)
|
[#2105](https://github.com/gakonst/ethers-rs/pull/2105)
|
||||||
- Make `Factory` objects generic over the borrow trait, to allow non-arc mware
|
- Make `Factory` objects generic over the borrow trait, to allow non-arc mware
|
||||||
|
|
|
@ -190,12 +190,9 @@ where
|
||||||
///
|
///
|
||||||
/// Note: this function _does not_ send a transaction from your account
|
/// Note: this function _does not_ send a transaction from your account
|
||||||
pub async fn call(&self) -> Result<D, ContractError<M>> {
|
pub async fn call(&self) -> Result<D, ContractError<M>> {
|
||||||
let bytes = self
|
let client: &M = self.client.borrow();
|
||||||
.client
|
let bytes =
|
||||||
.borrow()
|
client.call(&self.tx, self.block).await.map_err(ContractError::MiddlewareError)?;
|
||||||
.call(&self.tx, self.block)
|
|
||||||
.await
|
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
|
||||||
|
|
||||||
// decode output
|
// decode output
|
||||||
let data = decode_function_data(&self.function, &bytes, false)?;
|
let data = decode_function_data(&self.function, &bytes, false)?;
|
||||||
|
|
|
@ -31,8 +31,8 @@ mod multicall;
|
||||||
#[cfg(any(test, feature = "abigen"))]
|
#[cfg(any(test, feature = "abigen"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
||||||
pub use multicall::{
|
pub use multicall::{
|
||||||
multicall_contract, Call, Multicall, MulticallContract, MulticallError, MulticallVersion,
|
contract as multicall_contract, Call, Multicall, MulticallContract, MulticallError,
|
||||||
MULTICALL_ADDRESS, MULTICALL_SUPPORTED_CHAIN_IDS,
|
MulticallVersion, MULTICALL_ADDRESS, MULTICALL_SUPPORTED_CHAIN_IDS,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This module exposes low lever builder structures which are only consumed by the
|
/// This module exposes low lever builder structures which are only consumed by the
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
use crate::call::{ContractCall, ContractError};
|
use crate::call::{ContractCall, ContractError};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{AbiDecode, Detokenize, Function, Token},
|
abi::{AbiDecode, Detokenize, Function, InvalidOutputType, Token, Tokenizable},
|
||||||
types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, H160, U256},
|
types::{
|
||||||
|
transaction::eip2718::TypedTransaction, Address, BlockNumber, Bytes, Chain, NameOrAddress,
|
||||||
|
H160, U256,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ethers_providers::{Middleware, PendingTransaction};
|
use ethers_providers::{Middleware, PendingTransaction};
|
||||||
use std::{convert::TryFrom, fmt, sync::Arc};
|
use std::{convert::TryFrom, fmt, result::Result as StdResult, sync::Arc};
|
||||||
|
|
||||||
pub mod multicall_contract;
|
/// The Multicall contract bindings. Auto-generated with `abigen`.
|
||||||
use multicall_contract::multicall_3::{
|
pub mod contract {
|
||||||
|
ethers_contract_derive::abigen!(Multicall3, "src/multicall/multicall_abi.json");
|
||||||
|
}
|
||||||
|
pub use contract::Multicall3 as MulticallContract;
|
||||||
|
use contract::{
|
||||||
Call as Multicall1Call, Call3 as Multicall3Call, Call3Value as Multicall3CallValue,
|
Call as Multicall1Call, Call3 as Multicall3Call, Call3Value as Multicall3CallValue,
|
||||||
Result as MulticallResult,
|
Result as MulticallResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Export the contract interface
|
|
||||||
pub use multicall_contract::multicall_3::Multicall3 as MulticallContract;
|
|
||||||
|
|
||||||
/// The Multicall3 contract address that is deployed in [`MULTICALL_SUPPORTED_CHAIN_IDS`]:
|
/// The Multicall3 contract address that is deployed in [`MULTICALL_SUPPORTED_CHAIN_IDS`]:
|
||||||
/// [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11)
|
/// [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11)
|
||||||
pub const MULTICALL_ADDRESS: Address = H160([
|
pub const MULTICALL_ADDRESS: Address = H160([
|
||||||
|
@ -89,6 +92,9 @@ pub const MULTICALL_SUPPORTED_CHAIN_IDS: &[u64] = {
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Type alias for `Result<T, MulticallError<M>>`
|
||||||
|
pub type Result<T, M> = StdResult<T, MulticallError<M>>;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum MulticallError<M: Middleware> {
|
pub enum MulticallError<M: Middleware> {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
@ -99,9 +105,47 @@ pub enum MulticallError<M: Middleware> {
|
||||||
|
|
||||||
#[error("Illegal revert: Multicall2 call reverted when it wasn't allowed to.")]
|
#[error("Illegal revert: Multicall2 call reverted when it wasn't allowed to.")]
|
||||||
IllegalRevert,
|
IllegalRevert,
|
||||||
|
|
||||||
|
#[error("Call reverted with data: \"{}\"", decode_error(_0))]
|
||||||
|
CallReverted(Bytes),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T, M> = std::result::Result<T, MulticallError<M>>;
|
impl<M: Middleware> From<ethers_core::abi::Error> for MulticallError<M> {
|
||||||
|
fn from(value: ethers_core::abi::Error) -> Self {
|
||||||
|
Self::ContractError(ContractError::DecodingError(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Middleware> From<InvalidOutputType> for MulticallError<M> {
|
||||||
|
fn from(value: InvalidOutputType) -> Self {
|
||||||
|
Self::ContractError(ContractError::DetokenizationError(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Middleware> MulticallError<M> {
|
||||||
|
pub fn into_bytes(self) -> Result<Bytes, M> {
|
||||||
|
match self {
|
||||||
|
Self::CallReverted(bytes) => Ok(bytes),
|
||||||
|
e => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bytes that the call reverted with.
|
||||||
|
pub fn get_bytes(&self) -> Option<&Bytes> {
|
||||||
|
match self {
|
||||||
|
Self::CallReverted(bytes) => Some(bytes),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats the bytes that the call reverted with.
|
||||||
|
pub fn format_bytes(&self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Self::CallReverted(bytes) => Some(decode_error(bytes)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper struct for managing calls to be made to the `function` in smart contract `target`
|
/// Helper struct for managing calls to be made to the `function` in smart contract `target`
|
||||||
/// with `data`.
|
/// with `data`.
|
||||||
|
@ -141,7 +185,7 @@ impl From<MulticallVersion> for u8 {
|
||||||
|
|
||||||
impl TryFrom<u8> for MulticallVersion {
|
impl TryFrom<u8> for MulticallVersion {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
fn try_from(v: u8) -> std::result::Result<Self, Self::Error> {
|
fn try_from(v: u8) -> StdResult<Self, Self::Error> {
|
||||||
match v {
|
match v {
|
||||||
1 => Ok(MulticallVersion::Multicall),
|
1 => Ok(MulticallVersion::Multicall),
|
||||||
2 => Ok(MulticallVersion::Multicall2),
|
2 => Ok(MulticallVersion::Multicall2),
|
||||||
|
@ -151,6 +195,23 @@ impl TryFrom<u8> for MulticallVersion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MulticallVersion {
|
||||||
|
#[inline]
|
||||||
|
pub fn is_v1(&self) -> bool {
|
||||||
|
matches!(self, Self::Multicall)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_v2(&self) -> bool {
|
||||||
|
matches!(self, Self::Multicall2)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_v3(&self) -> bool {
|
||||||
|
matches!(self, Self::Multicall3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A Multicall is an abstraction for sending batched calls/transactions to the Ethereum blockchain.
|
/// A Multicall is an abstraction for sending batched calls/transactions to the Ethereum blockchain.
|
||||||
/// It stores an instance of the [`Multicall` smart contract](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11#code)
|
/// It stores an instance of the [`Multicall` smart contract](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11#code)
|
||||||
/// and the user provided list of transactions to be called or executed on chain.
|
/// and the user provided list of transactions to be called or executed on chain.
|
||||||
|
@ -190,7 +251,7 @@ impl TryFrom<u8> for MulticallVersion {
|
||||||
/// let abi: Abi = serde_json::from_str(r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#)?;
|
/// let abi: Abi = serde_json::from_str(r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#)?;
|
||||||
///
|
///
|
||||||
/// // connect to the network
|
/// // connect to the network
|
||||||
/// let client = Provider::<Http>::try_from("https://kovan.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27")?;
|
/// let client = Provider::<Http>::try_from("https://goerli.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27")?;
|
||||||
///
|
///
|
||||||
/// // create the contract object. This will be used to construct the calls for multicall
|
/// // create the contract object. This will be used to construct the calls for multicall
|
||||||
/// let client = Arc::new(client);
|
/// let client = Arc::new(client);
|
||||||
|
@ -201,24 +262,18 @@ impl TryFrom<u8> for MulticallVersion {
|
||||||
/// let first_call = contract.method::<_, String>("getValue", ())?;
|
/// let first_call = contract.method::<_, String>("getValue", ())?;
|
||||||
/// let second_call = contract.method::<_, Address>("lastSender", ())?;
|
/// let second_call = contract.method::<_, Address>("lastSender", ())?;
|
||||||
///
|
///
|
||||||
/// // Since this example connects to the Kovan testnet, we need not provide an address for
|
/// // Since this example connects to a known chain, we need not provide an address for
|
||||||
/// // the Multicall contract and we set that to `None`. If you wish to provide the address
|
/// // the Multicall contract and we set that to `None`. If you wish to provide the address
|
||||||
/// // for the Multicall contract, you can pass the `Some(multicall_addr)` argument.
|
/// // for the Multicall contract, you can pass the `Some(multicall_addr)` argument.
|
||||||
/// // Construction of the `Multicall` instance follows the builder pattern:
|
/// // Construction of the `Multicall` instance follows the builder pattern:
|
||||||
/// let mut multicall = Multicall::new(client.clone(), None).await?.version(MulticallVersion::Multicall);
|
/// let mut multicall = Multicall::new(client.clone(), None).await?;
|
||||||
/// multicall
|
/// multicall
|
||||||
/// .add_call(first_call, false)
|
/// .add_call(first_call, false)
|
||||||
/// .add_call(second_call, false);
|
/// .add_call(second_call, false);
|
||||||
///
|
///
|
||||||
/// // `await`ing on the `call` method lets us fetch the return values of both the above calls
|
/// // `await`ing on the `call` method lets us fetch the return values of both the above calls
|
||||||
/// // in one single RPC call
|
/// // in one single RPC call
|
||||||
/// let _return_data: (String, Address) = multicall.call().await?;
|
/// let return_data: (String, Address) = multicall.call().await?;
|
||||||
///
|
|
||||||
/// // using Multicall2 (version 2) or Multicall3 (version 3) differs when parsing `.call()` results
|
|
||||||
/// multicall = multicall.version(MulticallVersion::Multicall3);
|
|
||||||
///
|
|
||||||
/// // each call returns the results in a tuple, with the success status as the first element
|
|
||||||
/// let _return_data: ((bool, String), (bool, Address)) = multicall.call().await?;
|
|
||||||
///
|
///
|
||||||
/// // the same `Multicall` instance can be re-used to do a different batch of transactions.
|
/// // the same `Multicall` instance can be re-used to do a different batch of transactions.
|
||||||
/// // Say we wish to broadcast (send) a couple of transactions via the Multicall contract.
|
/// // Say we wish to broadcast (send) a couple of transactions via the Multicall contract.
|
||||||
|
@ -231,27 +286,17 @@ impl TryFrom<u8> for MulticallVersion {
|
||||||
///
|
///
|
||||||
/// // `await`ing the `send` method waits for the transaction to be broadcast, which also
|
/// // `await`ing the `send` method waits for the transaction to be broadcast, which also
|
||||||
/// // returns the transaction hash
|
/// // returns the transaction hash
|
||||||
/// let _tx_receipt = multicall.send().await?.await.expect("tx dropped");
|
/// let tx_receipt = multicall.send().await?.await.expect("tx dropped");
|
||||||
///
|
///
|
||||||
/// // you can also query ETH balances of multiple addresses
|
/// // you can also query ETH balances of multiple addresses
|
||||||
/// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::<Address>()?;
|
/// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::<Address>()?;
|
||||||
/// let address_2 = "ffffffffffffffffffffffffffffffffffffffff".parse::<Address>()?;
|
/// let address_2 = "ffffffffffffffffffffffffffffffffffffffff".parse::<Address>()?;
|
||||||
///
|
///
|
||||||
/// // using version 1
|
|
||||||
/// multicall = multicall.version(MulticallVersion::Multicall);
|
|
||||||
/// multicall
|
/// multicall
|
||||||
/// .clear_calls()
|
/// .clear_calls()
|
||||||
/// .add_get_eth_balance(address_1, false)
|
/// .add_get_eth_balance(address_1, false)
|
||||||
/// .add_get_eth_balance(address_2, false);
|
/// .add_get_eth_balance(address_2, false);
|
||||||
/// let _balances: (U256, U256) = multicall.call().await?;
|
/// let balances: (U256, U256) = multicall.call().await?;
|
||||||
///
|
|
||||||
/// // or with version 2 and above
|
|
||||||
/// multicall = multicall.version(MulticallVersion::Multicall3);
|
|
||||||
/// multicall
|
|
||||||
/// .clear_calls()
|
|
||||||
/// .add_get_eth_balance(address_1, false)
|
|
||||||
/// .add_get_eth_balance(address_2, false);
|
|
||||||
/// let _balances: ((bool, U256), (bool, U256)) = multicall.call().await?;
|
|
||||||
///
|
///
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
|
@ -269,9 +314,17 @@ impl TryFrom<u8> for MulticallVersion {
|
||||||
pub struct Multicall<M> {
|
pub struct Multicall<M> {
|
||||||
/// The Multicall contract interface.
|
/// The Multicall contract interface.
|
||||||
pub contract: MulticallContract<M>,
|
pub contract: MulticallContract<M>,
|
||||||
version: MulticallVersion,
|
|
||||||
legacy: bool,
|
/// The version of which methods to use when making the contract call.
|
||||||
block: Option<BlockNumber>,
|
pub version: MulticallVersion,
|
||||||
|
|
||||||
|
/// Whether to use a legacy or a EIP-1559 transaction.
|
||||||
|
pub legacy: bool,
|
||||||
|
|
||||||
|
/// The `block` field of the Multicall aggregate call.
|
||||||
|
pub block: Option<BlockNumber>,
|
||||||
|
|
||||||
|
/// The internal call vector.
|
||||||
calls: Vec<Call>,
|
calls: Vec<Call>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,10 +482,10 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// Appends a `call` to the list of calls of the Multicall instance.
|
/// Appends a `call` to the list of calls of the Multicall instance.
|
||||||
///
|
///
|
||||||
/// Version specific details:
|
/// Version specific details:
|
||||||
/// - 1: `allow_failure` is ignored.
|
/// - `1`: `allow_failure` is ignored.
|
||||||
/// - >=2: `allow_failure` specifies whether or not this call is allowed to revert in the
|
/// - `>=2`: `allow_failure` specifies whether or not this call is allowed to revert in the
|
||||||
/// multicall.
|
/// multicall.
|
||||||
/// - 3: Transaction values are used when broadcasting transactions with [`send`], otherwise
|
/// - `3`: Transaction values are used when broadcasting transactions with [`send`], otherwise
|
||||||
/// they are always ignored.
|
/// they are always ignored.
|
||||||
///
|
///
|
||||||
/// [`send`]: #method.send
|
/// [`send`]: #method.send
|
||||||
|
@ -441,32 +494,32 @@ impl<M: Middleware> Multicall<M> {
|
||||||
call: ContractCall<M, D>,
|
call: ContractCall<M, D>,
|
||||||
allow_failure: bool,
|
allow_failure: bool,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
match (call.tx.to(), call.tx.data()) {
|
let (to, data, value) = match call.tx {
|
||||||
(Some(NameOrAddress::Address(target)), Some(data)) => {
|
TypedTransaction::Legacy(tx) => (tx.to, tx.data, tx.value),
|
||||||
|
TypedTransaction::Eip2930(tx) => (tx.tx.to, tx.tx.data, tx.tx.value),
|
||||||
|
TypedTransaction::Eip1559(tx) => (tx.to, tx.data, tx.value),
|
||||||
|
};
|
||||||
|
if data.is_none() && !call.function.outputs.is_empty() {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
if let Some(NameOrAddress::Address(target)) = to {
|
||||||
let call = Call {
|
let call = Call {
|
||||||
target: *target,
|
target,
|
||||||
data: data.clone(),
|
data: data.unwrap_or_default(),
|
||||||
value: call.tx.value().cloned().unwrap_or_default(),
|
value: value.unwrap_or_default(),
|
||||||
allow_failure,
|
allow_failure,
|
||||||
function: call.function,
|
function: call.function,
|
||||||
};
|
};
|
||||||
self.calls.push(call);
|
self.calls.push(call);
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
_ => self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Appends multiple `call`s to the list of calls of the Multicall instance.
|
/// Appends multiple `call`s to the list of calls of the Multicall instance.
|
||||||
///
|
///
|
||||||
/// Version specific details:
|
/// See [`add_call`] for more 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
|
/// [`add_call`]: #method.add_call
|
||||||
pub fn add_calls<D: Detokenize>(
|
pub fn add_calls<D: Detokenize>(
|
||||||
&mut self,
|
&mut self,
|
||||||
allow_failure: bool,
|
allow_failure: bool,
|
||||||
|
@ -611,21 +664,24 @@ impl<M: Middleware> Multicall<M> {
|
||||||
|
|
||||||
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract.
|
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract.
|
||||||
///
|
///
|
||||||
/// Note: this method _does not_ send a transaction from your account.
|
/// For handling calls that have the same result type, see [`call_array`].
|
||||||
|
///
|
||||||
|
/// For handling each call's result individually, see [`call_raw`].
|
||||||
|
///
|
||||||
|
/// [`call_raw`]: #method.call_raw
|
||||||
|
/// [`call_array`]: #method.call_array
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing
|
/// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing
|
||||||
/// the tokens back to the expected return type.
|
/// the tokens back to the expected return type.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// Returns an error if any call failed, even if `allow_failure` was set, or if the return data
|
||||||
///
|
/// was empty.
|
||||||
/// If more than the maximum number of supported calls are added (16). The maximum limit is
|
|
||||||
/// constrained due to tokenization/detokenization support for tuples.
|
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// The return type must be annotated while calling this method:
|
/// The return type must be annotated as a tuple when calling this method:
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
@ -641,31 +697,32 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// // 1. `returns (uint256)`
|
/// // 1. `returns (uint256)`
|
||||||
/// // 2. `returns (string, address)`
|
/// // 2. `returns (string, address)`
|
||||||
/// // 3. `returns (bool)`
|
/// // 3. `returns (bool)`
|
||||||
/// // Version 1:
|
|
||||||
/// let result: (U256, (String, Address), bool) = multicall.call().await?;
|
/// let result: (U256, (String, Address), bool) = multicall.call().await?;
|
||||||
/// // Version 2 and above (each call returns also the success status as the first element):
|
/// // or using the turbofish syntax:
|
||||||
/// let result: ((bool, U256), (bool, (String, Address)), (bool, bool)) = multicall.call().await?;
|
/// let result = multicall.call::<(U256, (String, Address), bool)>().await?;
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn call<D: Detokenize>(&self) -> Result<D, M> {
|
pub async fn call<T: Tokenizable>(&self) -> Result<T, M> {
|
||||||
assert!(self.calls.len() <= 16, "Cannot decode more than 16 calls");
|
let results = self.call_raw().await?;
|
||||||
let tokens = self.call_raw().await?;
|
let tokens = results
|
||||||
let tokens = vec![Token::Tuple(tokens)];
|
.into_iter()
|
||||||
let data = D::from_tokens(tokens).map_err(ContractError::DetokenizationError)?;
|
.map(|res| res.map_err(MulticallError::CallReverted))
|
||||||
Ok(data)
|
.collect::<Result<_, _>>()?;
|
||||||
|
T::from_token(Token::Tuple(tokens)).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract, assuming
|
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract, assuming
|
||||||
/// that every call returns same data type.
|
/// that every call returns same type.
|
||||||
///
|
|
||||||
/// Note: this method _does not_ send a transaction from your account.
|
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing
|
/// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing
|
||||||
/// the tokens back to the expected return type.
|
/// the tokens back to the expected return type.
|
||||||
///
|
///
|
||||||
|
/// Returns an error if any call failed, even if `allow_failure` was set, or if the return data
|
||||||
|
/// was empty.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// The return type must be annotated while calling this method:
|
/// The return type must be annotated while calling this method:
|
||||||
|
@ -685,18 +742,24 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn call_array<D: Detokenize>(&self) -> Result<Vec<D>, M> {
|
pub async fn call_array<T: Tokenizable>(&self) -> Result<Vec<T>, M> {
|
||||||
let tokens = self.call_raw().await?;
|
self.call_raw()
|
||||||
let res: std::result::Result<Vec<D>, ContractError<M>> = tokens
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|token| D::from_tokens(vec![token]).map_err(ContractError::DetokenizationError))
|
.map(|res| {
|
||||||
.collect();
|
res.map_err(MulticallError::CallReverted)
|
||||||
|
.and_then(|token| T::from_token(token).map_err(Into::into))
|
||||||
Ok(res?)
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract and
|
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract.
|
||||||
/// without detokenization.
|
///
|
||||||
|
/// Returns a vector of `Result<Token, Bytes>` for each call added to the Multicall:
|
||||||
|
/// `Err(Bytes)` if the individual call failed while allowed or the return data was empty,
|
||||||
|
/// `Ok(Token)` otherwise.
|
||||||
|
///
|
||||||
|
/// If the Multicall version is 1, this will always be a vector of `Ok`.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
|
@ -715,97 +778,67 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// #
|
/// #
|
||||||
/// # let multicall = Multicall::new(client, None).await?;
|
/// # let multicall = Multicall::new(client, None).await?;
|
||||||
/// // The consumer of the API is responsible for detokenizing the results
|
/// // 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?;
|
/// let tokens = multicall.call_raw().await?;
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
pub async fn call_raw(&self) -> Result<Vec<StdResult<Token, Bytes>>, M> {
|
||||||
/// 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>, M> {
|
|
||||||
// Different call result types based on version
|
// Different call result types based on version
|
||||||
let tokens: Vec<Token> = match self.version {
|
match self.version {
|
||||||
|
// Wrap the return data with `success: true` since version 1 reverts if any call failed
|
||||||
MulticallVersion::Multicall => {
|
MulticallVersion::Multicall => {
|
||||||
let call = self.as_aggregate();
|
let call = self.as_aggregate();
|
||||||
let (_, return_data) = call.call().await?;
|
let (_, bytes) = ContractCall::call(&call).await?;
|
||||||
self.calls
|
self.parse_call_result(
|
||||||
.iter()
|
bytes
|
||||||
.zip(&return_data)
|
.into_iter()
|
||||||
.map(|(call, bytes)| {
|
.map(|return_data| MulticallResult { success: true, return_data }),
|
||||||
// Always return an empty Bytes token for calls that return no data
|
)
|
||||||
if bytes.is_empty() {
|
|
||||||
Ok(Token::Bytes(Default::default()))
|
|
||||||
} else {
|
|
||||||
let mut tokens = call
|
|
||||||
.function
|
|
||||||
.decode_output(bytes)
|
|
||||||
.map_err(ContractError::DecodingError)?;
|
|
||||||
Ok(match tokens.len() {
|
|
||||||
0 => Token::Tuple(vec![]),
|
|
||||||
1 => tokens.remove(0),
|
|
||||||
_ => Token::Tuple(tokens),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<Token>, M>>()?
|
|
||||||
}
|
}
|
||||||
// Same result type (`MulticallResult`)
|
// Same result type (`MulticallResult`)
|
||||||
v @ (MulticallVersion::Multicall2 | MulticallVersion::Multicall3) => {
|
MulticallVersion::Multicall2 | MulticallVersion::Multicall3 => {
|
||||||
let is_v2 = v == MulticallVersion::Multicall2;
|
let call = if self.version.is_v2() {
|
||||||
let call = if is_v2 { self.as_try_aggregate() } else { self.as_aggregate_3() };
|
self.as_try_aggregate()
|
||||||
let return_data = ContractCall::call(&call).await?;
|
|
||||||
self.calls
|
|
||||||
.iter()
|
|
||||||
.zip(return_data.into_iter())
|
|
||||||
.map(|(call, res)| {
|
|
||||||
let bytes = &res.return_data;
|
|
||||||
// Always return an empty Bytes token for calls that return no data
|
|
||||||
let res_token: Token = if bytes.is_empty() {
|
|
||||||
Token::Bytes(Default::default())
|
|
||||||
} else if res.success {
|
|
||||||
// Decode using call.function
|
|
||||||
let mut res_tokens = call
|
|
||||||
.function
|
|
||||||
.decode_output(bytes)
|
|
||||||
.map_err(ContractError::DecodingError)?;
|
|
||||||
match res_tokens.len() {
|
|
||||||
0 => Token::Tuple(vec![]),
|
|
||||||
1 => res_tokens.remove(0),
|
|
||||||
_ => Token::Tuple(res_tokens),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Call reverted
|
self.as_aggregate_3()
|
||||||
|
};
|
||||||
|
let results = ContractCall::call(&call).await?;
|
||||||
|
self.parse_call_result(results.into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For each call and its `return_data`: if `success` is true, parses `return_data` with the
|
||||||
|
/// call's function outputs, otherwise returns the bytes in `Err`.
|
||||||
|
fn parse_call_result(
|
||||||
|
&self,
|
||||||
|
return_data: impl Iterator<Item = MulticallResult>,
|
||||||
|
) -> Result<Vec<StdResult<Token, Bytes>>, M> {
|
||||||
|
let mut results = Vec::with_capacity(self.calls.len());
|
||||||
|
for (call, MulticallResult { success, return_data }) in self.calls.iter().zip(return_data) {
|
||||||
|
let result = if !success || return_data.is_empty() {
|
||||||
// v2: In the function call to `tryAggregate`, the `allow_failure` check
|
// v2: In the function call to `tryAggregate`, the `allow_failure` check
|
||||||
// is done on a per-transaction basis, and we set this transaction-wide
|
// is done on a per-transaction basis, and we set this transaction-wide
|
||||||
// check to true when *any* call is allowed to fail. If this is true
|
// check to true when *any* call is allowed to fail. If this is true
|
||||||
// then a call that is not allowed to revert (`call.allow_failure`) may
|
// then a call that is not allowed to revert (`call.allow_failure`) may
|
||||||
// still do so because of other calls that are in the same multicall
|
// still do so because of other calls that are in the same multicall
|
||||||
// aggregate.
|
// aggregate.
|
||||||
if !call.allow_failure {
|
if !success && !call.allow_failure {
|
||||||
return Err(MulticallError::IllegalRevert)
|
return Err(MulticallError::IllegalRevert)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode with "Error(string)" (0x08c379a0)
|
Err(return_data)
|
||||||
if bytes.len() >= 4 && bytes[..4] == [0x08, 0xc3, 0x79, 0xa0] {
|
|
||||||
Token::String(
|
|
||||||
String::decode(&bytes[4..]).map_err(ContractError::AbiError)?,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Token::Bytes(bytes.to_vec())
|
let mut res_tokens = call.function.decode_output(return_data.as_ref())?;
|
||||||
}
|
Ok(if res_tokens.len() == 1 {
|
||||||
};
|
res_tokens.pop().unwrap()
|
||||||
|
} else {
|
||||||
// (bool, (...))
|
Token::Tuple(res_tokens)
|
||||||
Ok(Token::Tuple(vec![Token::Bool(res.success), res_token]))
|
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<Token>, M>>()?
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
results.push(result);
|
||||||
Ok(tokens)
|
}
|
||||||
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs and broadcasts a batch of transactions by using the Multicall contract as proxy,
|
/// Signs and broadcasts a batch of transactions by using the Multicall contract as proxy,
|
||||||
|
@ -837,15 +870,15 @@ impl<M: Middleware> Multicall<M> {
|
||||||
MulticallVersion::Multicall2 => self.as_try_aggregate().tx,
|
MulticallVersion::Multicall2 => self.as_try_aggregate().tx,
|
||||||
MulticallVersion::Multicall3 => self.as_aggregate_3_value().tx,
|
MulticallVersion::Multicall3 => self.as_aggregate_3_value().tx,
|
||||||
};
|
};
|
||||||
|
let client: &M = self.contract.client_ref();
|
||||||
self.contract
|
client
|
||||||
.client_ref()
|
|
||||||
.send_transaction(tx, self.block.map(Into::into))
|
.send_transaction(tx, self.block.map(Into::into))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| MulticallError::ContractError(ContractError::MiddlewareError(e)))
|
.map_err(|e| MulticallError::ContractError(ContractError::MiddlewareError(e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// v1
|
/// v1
|
||||||
|
#[inline]
|
||||||
fn as_aggregate(&self) -> ContractCall<M, (U256, Vec<Bytes>)> {
|
fn as_aggregate(&self) -> ContractCall<M, (U256, Vec<Bytes>)> {
|
||||||
// Map the calls vector into appropriate types for `aggregate` function
|
// Map the calls vector into appropriate types for `aggregate` function
|
||||||
let calls: Vec<Multicall1Call> = self
|
let calls: Vec<Multicall1Call> = self
|
||||||
|
@ -862,6 +895,7 @@ impl<M: Middleware> Multicall<M> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// v2
|
/// v2
|
||||||
|
#[inline]
|
||||||
fn as_try_aggregate(&self) -> ContractCall<M, Vec<MulticallResult>> {
|
fn as_try_aggregate(&self) -> ContractCall<M, Vec<MulticallResult>> {
|
||||||
let mut allow_failure = false;
|
let mut allow_failure = false;
|
||||||
// Map the calls vector into appropriate types for `try_aggregate` function
|
// Map the calls vector into appropriate types for `try_aggregate` function
|
||||||
|
@ -885,6 +919,7 @@ impl<M: Middleware> Multicall<M> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// v3
|
/// v3
|
||||||
|
#[inline]
|
||||||
fn as_aggregate_3(&self) -> ContractCall<M, Vec<MulticallResult>> {
|
fn as_aggregate_3(&self) -> ContractCall<M, Vec<MulticallResult>> {
|
||||||
// Map the calls vector into appropriate types for `aggregate_3` function
|
// Map the calls vector into appropriate types for `aggregate_3` function
|
||||||
let calls: Vec<Multicall3Call> = self
|
let calls: Vec<Multicall3Call> = self
|
||||||
|
@ -905,6 +940,7 @@ impl<M: Middleware> Multicall<M> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// v3 + values (only .send())
|
/// v3 + values (only .send())
|
||||||
|
#[inline]
|
||||||
fn as_aggregate_3_value(&self) -> ContractCall<M, Vec<MulticallResult>> {
|
fn as_aggregate_3_value(&self) -> ContractCall<M, Vec<MulticallResult>> {
|
||||||
// Map the calls vector into appropriate types for `aggregate_3_value` function
|
// Map the calls vector into appropriate types for `aggregate_3_value` function
|
||||||
let mut total_value = U256::zero();
|
let mut total_value = U256::zero();
|
||||||
|
@ -938,13 +974,23 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// Sets the block and legacy flags on a [ContractCall] if they were set on Multicall.
|
/// Sets the block and legacy flags on a [ContractCall] if they were set on Multicall.
|
||||||
fn set_call_flags<D: Detokenize>(&self, mut call: ContractCall<M, D>) -> ContractCall<M, D> {
|
fn set_call_flags<D: Detokenize>(&self, mut call: ContractCall<M, D>) -> ContractCall<M, D> {
|
||||||
if let Some(block) = self.block {
|
if let Some(block) = self.block {
|
||||||
call = call.block(block);
|
call.block = Some(block.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.legacy {
|
if self.legacy {
|
||||||
call = call.legacy();
|
call.legacy()
|
||||||
}
|
} else {
|
||||||
|
|
||||||
call
|
call
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_error(bytes: &Bytes) -> String {
|
||||||
|
// Try decoding with "Error(string)" (0x08c379a0)
|
||||||
|
if bytes.len() >= 4 && bytes[..4] == [0x08, 0xc3, 0x79, 0xa0] {
|
||||||
|
if let Ok(string) = String::decode(&bytes[4..]) {
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bytes.to_string()
|
||||||
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
ethers_contract_derive::abigen!(Multicall3, "src/multicall/multicall_abi.json");
|
|
|
@ -7,9 +7,11 @@ use ethers_core::types::{Filter, ValueOrArray, H256};
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
mod eth_tests {
|
mod eth_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ethers_contract::{ContractInstance, EthEvent, LogMeta, Multicall, MulticallVersion};
|
use ethers_contract::{
|
||||||
|
ContractInstance, EthEvent, LogMeta, Multicall, MulticallError, MulticallVersion,
|
||||||
|
};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{encode, Detokenize, Token, Tokenizable},
|
abi::{encode, AbiEncode, Detokenize, Token, Tokenizable},
|
||||||
types::{transaction::eip712::Eip712, Address, BlockId, Bytes, H160, I256, U256},
|
types::{transaction::eip712::Eip712, Address, BlockId, Bytes, H160, I256, U256},
|
||||||
utils::{keccak256, Anvil},
|
utils::{keccak256, Anvil},
|
||||||
};
|
};
|
||||||
|
@ -635,21 +637,18 @@ mod eth_tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// build up a list of calls greater than the 16 max restriction
|
|
||||||
multicall.add_calls(
|
multicall.add_calls(
|
||||||
false,
|
false,
|
||||||
std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap())
|
std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap())
|
||||||
.take(17), // .collect(),
|
.take(17),
|
||||||
);
|
);
|
||||||
|
|
||||||
// must use `call_raw` as `.calls` > 16
|
|
||||||
let tokens = multicall.call_raw().await.unwrap();
|
let tokens = multicall.call_raw().await.unwrap();
|
||||||
// if want to use, must detokenize manually
|
|
||||||
let results: Vec<String> = tokens
|
let results: Vec<String> = tokens
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|token| {
|
.map(|result| {
|
||||||
// decode manually using Tokenizable method
|
// decode manually using Tokenizable method
|
||||||
String::from_token(token.to_owned()).unwrap()
|
String::from_token(result.unwrap()).unwrap()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(results, ["many"; 17]);
|
assert_eq!(results, ["many"; 17]);
|
||||||
|
@ -685,11 +684,11 @@ mod eth_tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let get_value_call = reverting_contract
|
let get_value_call = reverting_contract
|
||||||
.connect(client2.clone())
|
.connect(client2.clone())
|
||||||
.method::<_, String>("getValue", (false))
|
.method::<_, String>("getValue", false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let get_value_reverting_call = reverting_contract
|
let get_value_reverting_call = reverting_contract
|
||||||
.connect(client.clone())
|
.connect(client.clone())
|
||||||
.method::<_, String>("getValue", (true))
|
.method::<_, String>("getValue", true)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// .send reverts
|
// .send reverts
|
||||||
|
@ -723,36 +722,31 @@ mod eth_tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// .call reverts
|
// .call reverts
|
||||||
// don't allow revert
|
// don't allow revert -> entire call reverts
|
||||||
multicall
|
multicall.clear_calls().add_call(get_value_reverting_call.clone(), false);
|
||||||
.clear_calls()
|
assert!(matches!(
|
||||||
.add_call(get_value_reverting_call.clone(), false)
|
multicall.call::<(String,)>().await.unwrap_err(),
|
||||||
.add_call(get_value_call.clone(), false);
|
MulticallError::ContractError(_)
|
||||||
let res = multicall.call::<((bool, String), (bool, String))>().await;
|
));
|
||||||
let err = res.unwrap_err();
|
|
||||||
assert!(err.to_string().contains("Multicall3: call failed"));
|
|
||||||
|
|
||||||
// allow revert
|
// allow revert -> call doesn't revert, but returns Err(_) in raw tokens
|
||||||
multicall
|
let expected = Bytes::from_static(b"getValue revert").encode();
|
||||||
.clear_calls()
|
multicall.clear_calls().add_call(get_value_reverting_call.clone(), true);
|
||||||
.add_call(get_value_reverting_call.clone(), true)
|
assert_eq!(multicall.call_raw().await.unwrap()[0].as_ref().unwrap_err()[4..], expected[..]);
|
||||||
.add_call(get_value_call.clone(), false);
|
assert_eq!(
|
||||||
let res = multicall.call().await;
|
multicall.call::<(String,)>().await.unwrap_err().into_bytes().unwrap()[4..],
|
||||||
let data: ((bool, String), (bool, String)) = res.unwrap();
|
expected[..]
|
||||||
|
);
|
||||||
|
|
||||||
assert!(!data.0 .0); // first call reverted
|
// v2 illegal revert
|
||||||
assert_eq!(data.0 .1, "getValue revert"); // first call revert data
|
|
||||||
assert!(data.1 .0); // second call didn't revert
|
|
||||||
assert_eq!(data.1 .1, "reset third again"); // second call return data
|
|
||||||
|
|
||||||
// test v2 illegal revert
|
|
||||||
multicall
|
multicall
|
||||||
.clear_calls()
|
.clear_calls()
|
||||||
.add_call(get_value_reverting_call.clone(), false) // don't allow revert
|
.add_call(get_value_reverting_call.clone(), false) // don't allow revert
|
||||||
.add_call(get_value_call.clone(), true); // true here will result in `tryAggregate(false, ...)`
|
.add_call(get_value_call.clone(), true); // true here will result in `tryAggregate(false, ...)`
|
||||||
let res = multicall.call::<((bool, String), (bool, String))>().await;
|
assert!(matches!(
|
||||||
let err = res.unwrap_err();
|
multicall.call::<(String, String)>().await.unwrap_err(),
|
||||||
assert!(err.to_string().contains("Illegal revert"));
|
MulticallError::IllegalRevert
|
||||||
|
));
|
||||||
|
|
||||||
// test version 3
|
// test version 3
|
||||||
// aggregate3 is the same as try_aggregate except with allowing failure on a per-call basis.
|
// aggregate3 is the same as try_aggregate except with allowing failure on a per-call basis.
|
||||||
|
@ -764,71 +758,49 @@ mod eth_tests {
|
||||||
let value_tx = reverting_contract.method::<_, H256>("deposit", ()).unwrap().value(amount);
|
let value_tx = reverting_contract.method::<_, H256>("deposit", ()).unwrap().value(amount);
|
||||||
let rc_addr = reverting_contract.address();
|
let rc_addr = reverting_contract.address();
|
||||||
|
|
||||||
// add a second call because we can't decode using a single element tuple
|
let (bal_before,): (U256,) =
|
||||||
// ((bool, U256)) == (bool, U256)
|
multicall.clear_calls().add_get_eth_balance(rc_addr, false).call().await.unwrap();
|
||||||
let bal_before: ((bool, U256), (bool, U256)) = multicall
|
|
||||||
.clear_calls()
|
|
||||||
.add_get_eth_balance(rc_addr, false)
|
|
||||||
.add_get_eth_balance(rc_addr, false)
|
|
||||||
.call()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// send 2 value_tx
|
// send 2 value_tx
|
||||||
multicall.clear_calls().add_call(value_tx.clone(), false).add_call(value_tx.clone(), false);
|
multicall.clear_calls().add_call(value_tx.clone(), false).add_call(value_tx.clone(), false);
|
||||||
multicall.send().await.unwrap();
|
multicall.send().await.unwrap();
|
||||||
|
|
||||||
let bal_after: ((bool, U256), (bool, U256)) = multicall
|
let (bal_after,): (U256,) =
|
||||||
.clear_calls()
|
multicall.clear_calls().add_get_eth_balance(rc_addr, false).call().await.unwrap();
|
||||||
.add_get_eth_balance(rc_addr, false)
|
|
||||||
.add_get_eth_balance(rc_addr, false)
|
|
||||||
.call()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(bal_after.0 .1, bal_before.0 .1 + U256::from(2) * amount);
|
assert_eq!(bal_after, bal_before + U256::from(2) * amount);
|
||||||
|
|
||||||
// test specific revert cases
|
// test specific revert cases
|
||||||
// empty revert
|
// empty revert
|
||||||
let empty_revert = reverting_contract.method::<_, H256>("emptyRevert", ()).unwrap();
|
let empty_revert = reverting_contract.method::<_, H256>("emptyRevert", ()).unwrap();
|
||||||
multicall
|
multicall.clear_calls().add_call(empty_revert.clone(), true);
|
||||||
.clear_calls()
|
assert!(multicall.call::<(String,)>().await.unwrap_err().into_bytes().unwrap().is_empty());
|
||||||
.add_call(empty_revert.clone(), true)
|
|
||||||
.add_call(empty_revert.clone(), true);
|
|
||||||
let res: ((bool, Bytes), (bool, Bytes)) = multicall.call().await.unwrap();
|
|
||||||
assert!(!res.0 .0);
|
|
||||||
assert_eq!(res.0 .1, Bytes::default());
|
|
||||||
|
|
||||||
// string revert
|
// string revert
|
||||||
let string_revert =
|
let string_revert =
|
||||||
reverting_contract.method::<_, H256>("stringRevert", ("String".to_string())).unwrap();
|
reverting_contract.method::<_, H256>("stringRevert", ("String".to_string())).unwrap();
|
||||||
multicall.clear_calls().add_call(string_revert, true).add_call(empty_revert.clone(), true);
|
multicall.clear_calls().add_call(string_revert, true);
|
||||||
let res: ((bool, String), (bool, Bytes)) = multicall.call().await.unwrap();
|
assert_eq!(
|
||||||
assert!(!res.0 .0);
|
multicall.call::<(String,)>().await.unwrap_err().into_bytes().unwrap()[4..],
|
||||||
assert_eq!(res.0 .1, "String");
|
Bytes::from_static(b"String").encode()[..]
|
||||||
|
);
|
||||||
|
|
||||||
// custom error revert
|
// custom error revert
|
||||||
let custom_error = reverting_contract.method::<_, H256>("customError", ()).unwrap();
|
let custom_error = reverting_contract.method::<_, H256>("customError", ()).unwrap();
|
||||||
multicall.clear_calls().add_call(custom_error, true).add_call(empty_revert.clone(), true);
|
multicall.clear_calls().add_call(custom_error, true);
|
||||||
let res: ((bool, Bytes), (bool, Bytes)) = multicall.call().await.unwrap();
|
assert_eq!(
|
||||||
let selector = &keccak256("CustomError()")[..4];
|
multicall.call::<(Bytes,)>().await.unwrap_err().into_bytes().unwrap()[..],
|
||||||
assert!(!res.0 .0);
|
keccak256("CustomError()")[..4]
|
||||||
assert_eq!(res.0 .1.len(), 4);
|
);
|
||||||
assert_eq!(&res.0 .1[..4], selector);
|
|
||||||
|
|
||||||
// custom error with data revert
|
// custom error with data revert
|
||||||
let custom_error_with_data = reverting_contract
|
let custom_error_with_data = reverting_contract
|
||||||
.method::<_, H256>("customErrorWithData", ("Data".to_string()))
|
.method::<_, H256>("customErrorWithData", ("Data".to_string()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
multicall
|
multicall.clear_calls().add_call(custom_error_with_data, true);
|
||||||
.clear_calls()
|
let bytes = multicall.call::<(Bytes,)>().await.unwrap_err().into_bytes().unwrap();
|
||||||
.add_call(custom_error_with_data, true)
|
assert_eq!(bytes[..4], keccak256("CustomErrorWithData(string)")[..4]);
|
||||||
.add_call(empty_revert.clone(), true);
|
assert_eq!(bytes[4..], encode(&[Token::String("Data".to_string())]));
|
||||||
let res: ((bool, Bytes), (bool, Bytes)) = multicall.call().await.unwrap();
|
|
||||||
let selector = &keccak256("CustomErrorWithData(string)")[..4];
|
|
||||||
assert!(!res.0 .0);
|
|
||||||
assert_eq!(&res.0 .1[..4], selector);
|
|
||||||
assert_eq!(&res.0 .1[4..], encode(&[Token::String("Data".to_string())]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
//! Contract Functions Output types.
|
//! Contract Functions Output types.
|
||||||
// Adapted from: [rust-web3](https://github.com/tomusdrw/rust-web3/blob/master/src/contract/tokens.rs)
|
//!
|
||||||
#![allow(clippy::all)]
|
//! Adapted from [rust-web3](https://github.com/tomusdrw/rust-web3/blob/master/src/contract/tokens.rs).
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
abi::Token,
|
abi::Token,
|
||||||
types::{Address, Bytes, H256, I256, U128, U256},
|
types::{Address, Bytes, H256, I256, U128, U256},
|
||||||
};
|
};
|
||||||
|
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -22,41 +22,38 @@ pub trait Detokenize {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Detokenize for () {
|
impl Detokenize for () {
|
||||||
fn from_tokens(_: Vec<Token>) -> std::result::Result<Self, InvalidOutputType>
|
fn from_tokens(_: Vec<Token>) -> std::result::Result<Self, InvalidOutputType> {
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Tokenizable> Detokenize for T {
|
impl<T: Tokenizable> Detokenize for T {
|
||||||
fn from_tokens(mut tokens: Vec<Token>) -> Result<Self, InvalidOutputType> {
|
fn from_tokens(mut tokens: Vec<Token>) -> Result<Self, InvalidOutputType> {
|
||||||
let token = match tokens.len() {
|
let token = if tokens.len() == 1 { tokens.pop().unwrap() } else { Token::Tuple(tokens) };
|
||||||
0 => Token::Tuple(vec![]),
|
|
||||||
1 => tokens.remove(0),
|
|
||||||
_ => Token::Tuple(tokens),
|
|
||||||
};
|
|
||||||
|
|
||||||
Self::from_token(token)
|
Self::from_token(token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tokens conversion trait
|
/// Convert types into [`Token`]s.
|
||||||
pub trait Tokenize {
|
pub trait Tokenize {
|
||||||
/// Convert to list of tokens
|
/// Converts `self` into a `Vec<Token>`.
|
||||||
fn into_tokens(self) -> Vec<Token>;
|
fn into_tokens(self) -> Vec<Token>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Tokenize for &'a [Token] {
|
impl<'a> Tokenize for &'a [Token] {
|
||||||
fn into_tokens(self) -> Vec<Token> {
|
fn into_tokens(self) -> Vec<Token> {
|
||||||
flatten_tokens(self.to_vec())
|
let mut tokens = self.to_vec();
|
||||||
|
if tokens.len() == 1 {
|
||||||
|
flatten_token(tokens.pop().unwrap())
|
||||||
|
} else {
|
||||||
|
tokens
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Tokenizable> Tokenize for T {
|
impl<T: Tokenizable> Tokenize for T {
|
||||||
fn into_tokens(self) -> Vec<Token> {
|
fn into_tokens(self) -> Vec<Token> {
|
||||||
flatten_tokens(vec![self.into_token()])
|
flatten_token(self.into_token())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,13 +69,15 @@ pub trait Tokenizable {
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType>
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
|
||||||
/// Converts a specified type back into token.
|
/// Converts a specified type back into token.
|
||||||
fn into_token(self) -> Token;
|
fn into_token(self) -> Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_tuples {
|
macro_rules! impl_tuples {
|
||||||
($num: expr, $( $ty: ident : $no: tt, )+) => {
|
($num:expr, $( $ty:ident : $no:tt ),+ $(,)?) => {
|
||||||
impl<$($ty, )+> Tokenizable for ($($ty,)+) where
|
impl<$( $ty ),+> Tokenizable for ($( $ty, )+)
|
||||||
|
where
|
||||||
$(
|
$(
|
||||||
$ty: Tokenizable,
|
$ty: Tokenizable,
|
||||||
)+
|
)+
|
||||||
|
@ -92,7 +91,8 @@ macro_rules! impl_tuples {
|
||||||
)+))
|
)+))
|
||||||
},
|
},
|
||||||
other => Err(InvalidOutputType(format!(
|
other => Err(InvalidOutputType(format!(
|
||||||
"Expected `Tuple`, got {:?}",
|
"Expected `Tuple` of length {}, got {:?}",
|
||||||
|
$num,
|
||||||
other,
|
other,
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
|
@ -133,6 +133,7 @@ impl Tokenizable for Token {
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_token(self) -> Token {
|
fn into_token(self) -> Token {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -212,6 +213,18 @@ impl Tokenizable for Address {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Tokenizable for bool {
|
||||||
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
|
match token {
|
||||||
|
Token::Bool(data) => Ok(data),
|
||||||
|
other => Err(InvalidOutputType(format!("Expected `bool`, got {:?}", other))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn into_token(self) -> Token {
|
||||||
|
Token::Bool(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! eth_uint_tokenizable {
|
macro_rules! eth_uint_tokenizable {
|
||||||
($uint: ident, $name: expr) => {
|
($uint: ident, $name: expr) => {
|
||||||
impl Tokenizable for $uint {
|
impl Tokenizable for $uint {
|
||||||
|
@ -279,20 +292,99 @@ int_tokenizable!(u32, Uint);
|
||||||
int_tokenizable!(u64, Uint);
|
int_tokenizable!(u64, Uint);
|
||||||
int_tokenizable!(u128, Uint);
|
int_tokenizable!(u128, Uint);
|
||||||
|
|
||||||
impl Tokenizable for bool {
|
impl Tokenizable for Vec<u8> {
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
match token {
|
match token {
|
||||||
Token::Bool(data) => Ok(data),
|
Token::Bytes(data) => Ok(data),
|
||||||
other => Err(InvalidOutputType(format!("Expected `bool`, got {:?}", other))),
|
Token::Array(data) => data.into_iter().map(u8::from_token).collect(),
|
||||||
}
|
Token::FixedBytes(data) => Ok(data),
|
||||||
}
|
other => Err(InvalidOutputType(format!("Expected `bytes`, got {:?}", other))),
|
||||||
fn into_token(self) -> Token {
|
|
||||||
Token::Bool(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marker trait for `Tokenizable` types that are can tokenized to and from a
|
fn into_token(self) -> Token {
|
||||||
/// `Token::Array` and `Token:FixedArray`.
|
Token::Array(self.into_iter().map(Tokenizable::into_token).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TokenizableItem> Tokenizable for Vec<T> {
|
||||||
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
|
match token {
|
||||||
|
Token::FixedArray(tokens) | Token::Array(tokens) => {
|
||||||
|
tokens.into_iter().map(Tokenizable::from_token).collect()
|
||||||
|
}
|
||||||
|
other => Err(InvalidOutputType(format!("Expected `Array`, got {:?}", other))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_token(self) -> Token {
|
||||||
|
Token::Array(self.into_iter().map(Tokenizable::into_token).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Tokenizable for [u8; N] {
|
||||||
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
|
match token {
|
||||||
|
Token::FixedBytes(bytes) => {
|
||||||
|
if bytes.len() != N {
|
||||||
|
return Err(InvalidOutputType(format!(
|
||||||
|
"Expected `FixedBytes({})`, got FixedBytes({})",
|
||||||
|
N,
|
||||||
|
bytes.len()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut arr = [0; N];
|
||||||
|
arr.copy_from_slice(&bytes);
|
||||||
|
Ok(arr)
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
Err(InvalidOutputType(format!("Expected `FixedBytes({})`, got {:?}", N, other)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_token(self) -> Token {
|
||||||
|
Token::FixedBytes(self.to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TokenizableItem + Clone, const N: usize> Tokenizable for [T; N] {
|
||||||
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
|
match token {
|
||||||
|
Token::FixedArray(tokens) => {
|
||||||
|
if tokens.len() != N {
|
||||||
|
return Err(InvalidOutputType(format!(
|
||||||
|
"Expected `FixedArray({})`, got FixedArray({})",
|
||||||
|
N,
|
||||||
|
tokens.len()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut arr = ArrayVec::<T, N>::new();
|
||||||
|
let mut it = tokens.into_iter().map(T::from_token);
|
||||||
|
for _ in 0..N {
|
||||||
|
arr.push(it.next().expect("Length validated in guard; qed")?);
|
||||||
|
}
|
||||||
|
// Can't use expect here because [T; N]: Debug is not satisfied.
|
||||||
|
match arr.into_inner() {
|
||||||
|
Ok(arr) => Ok(arr),
|
||||||
|
Err(_) => panic!("All elements inserted so the array is full; qed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
Err(InvalidOutputType(format!("Expected `FixedArray({})`, got {:?}", N, other)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_token(self) -> Token {
|
||||||
|
Token::FixedArray(ArrayVec::from(self).into_iter().map(T::into_token).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker trait for `Tokenizable` types that are can tokenized to and from a `Token::Array` and
|
||||||
|
/// `Token:FixedArray`.
|
||||||
pub trait TokenizableItem: Tokenizable {}
|
pub trait TokenizableItem: Tokenizable {}
|
||||||
|
|
||||||
macro_rules! tokenizable_item {
|
macro_rules! tokenizable_item {
|
||||||
|
@ -308,9 +400,16 @@ tokenizable_item! {
|
||||||
i8, i16, i32, i64, i128, u16, u32, u64, u128, Bytes, bytes::Bytes,
|
i8, i16, i32, i64, i128, u16, u32, u64, u128, Bytes, bytes::Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: TokenizableItem> TokenizableItem for Vec<T> {}
|
||||||
|
|
||||||
|
impl<const N: usize> TokenizableItem for [u8; N] {}
|
||||||
|
|
||||||
|
impl<T: TokenizableItem + Clone, const N: usize> TokenizableItem for [T; N] {}
|
||||||
|
|
||||||
macro_rules! impl_tokenizable_item_tuple {
|
macro_rules! impl_tokenizable_item_tuple {
|
||||||
($( $ty: ident , )+) => {
|
($( $ty:ident ),+ $(,)?) => {
|
||||||
impl<$($ty, )+> TokenizableItem for ($($ty,)+) where
|
impl<$( $ty ),+> TokenizableItem for ($( $ty, )+)
|
||||||
|
where
|
||||||
$(
|
$(
|
||||||
$ty: Tokenizable,
|
$ty: Tokenizable,
|
||||||
)+
|
)+
|
||||||
|
@ -340,117 +439,15 @@ impl_tokenizable_item_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q,
|
||||||
impl_tokenizable_item_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T,);
|
impl_tokenizable_item_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T,);
|
||||||
impl_tokenizable_item_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U,);
|
impl_tokenizable_item_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U,);
|
||||||
|
|
||||||
impl Tokenizable for Vec<u8> {
|
/// Helper for flattening non-nested tokens into their inner types;
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
///
|
||||||
|
/// e.g. `(A, B, C)` would get tokenized to `Tuple([A, B, C])` when in fact we need `[A, B, C]`.
|
||||||
|
#[inline]
|
||||||
|
fn flatten_token(token: Token) -> Vec<Token> {
|
||||||
|
// flatten the tokens if required and there is no nesting
|
||||||
match token {
|
match token {
|
||||||
Token::Bytes(data) => Ok(data),
|
|
||||||
Token::Array(data) => data.into_iter().map(u8::from_token).collect(),
|
|
||||||
Token::FixedBytes(data) => Ok(data),
|
|
||||||
other => Err(InvalidOutputType(format!("Expected `bytes`, got {:?}", other))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn into_token(self) -> Token {
|
|
||||||
Token::Array(self.into_iter().map(Tokenizable::into_token).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TokenizableItem> Tokenizable for Vec<T> {
|
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
|
||||||
match token {
|
|
||||||
Token::FixedArray(tokens) | Token::Array(tokens) => {
|
|
||||||
tokens.into_iter().map(Tokenizable::from_token).collect()
|
|
||||||
}
|
|
||||||
other => Err(InvalidOutputType(format!("Expected `Array`, got {:?}", other))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_token(self) -> Token {
|
|
||||||
Token::Array(self.into_iter().map(Tokenizable::into_token).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TokenizableItem> TokenizableItem for Vec<T> {}
|
|
||||||
|
|
||||||
impl<const N: usize> Tokenizable for [u8; N] {
|
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
|
||||||
match token {
|
|
||||||
Token::FixedBytes(bytes) => {
|
|
||||||
if bytes.len() != N {
|
|
||||||
return Err(InvalidOutputType(format!(
|
|
||||||
"Expected `FixedBytes({})`, got FixedBytes({})",
|
|
||||||
N,
|
|
||||||
bytes.len()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut arr = [0; N];
|
|
||||||
arr.copy_from_slice(&bytes);
|
|
||||||
Ok(arr)
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
Err(InvalidOutputType(format!("Expected `FixedBytes({})`, got {:?}", N, other))
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_token(self) -> Token {
|
|
||||||
Token::FixedBytes(self.to_vec())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> TokenizableItem for [u8; N] {}
|
|
||||||
|
|
||||||
impl<T: TokenizableItem + Clone, const N: usize> Tokenizable for [T; N] {
|
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
|
||||||
match token {
|
|
||||||
Token::FixedArray(tokens) => {
|
|
||||||
if tokens.len() != N {
|
|
||||||
return Err(InvalidOutputType(format!(
|
|
||||||
"Expected `FixedArray({})`, got FixedArray({})",
|
|
||||||
N,
|
|
||||||
tokens.len()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut arr = ArrayVec::<T, N>::new();
|
|
||||||
let mut it = tokens.into_iter().map(T::from_token);
|
|
||||||
for _ in 0..N {
|
|
||||||
arr.push(it.next().expect("Length validated in guard; qed")?);
|
|
||||||
}
|
|
||||||
// Can't use expect here because [T; N]: Debug is not satisfied.
|
|
||||||
match arr.into_inner() {
|
|
||||||
Ok(arr) => Ok(arr),
|
|
||||||
Err(_) => panic!("All elements inserted so the array is full; qed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
Err(InvalidOutputType(format!("Expected `FixedArray({})`, got {:?}", N, other))
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_token(self) -> Token {
|
|
||||||
Token::FixedArray(ArrayVec::from(self).into_iter().map(T::into_token).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TokenizableItem + Clone, const N: usize> TokenizableItem for [T; N] {}
|
|
||||||
|
|
||||||
/// Helper for flattening non-nested tokens into their inner
|
|
||||||
/// types, e.g. (A, B, C ) would get tokenized to Tuple([A, B, C])
|
|
||||||
/// when in fact we need [A, B, C].
|
|
||||||
fn flatten_tokens(mut tokens: Vec<Token>) -> Vec<Token> {
|
|
||||||
if tokens.len() == 1 {
|
|
||||||
// flatten the tokens if required
|
|
||||||
// and there is no nesting
|
|
||||||
match tokens.remove(0) {
|
|
||||||
Token::Tuple(inner) => inner,
|
Token::Tuple(inner) => inner,
|
||||||
other => vec![other],
|
token => vec![token],
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tokens
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,33 +457,33 @@ mod tests {
|
||||||
use crate::types::{Address, U256};
|
use crate::types::{Address, U256};
|
||||||
use ethabi::Token;
|
use ethabi::Token;
|
||||||
|
|
||||||
fn output<R: Detokenize>() -> R {
|
fn assert_detokenize<T: Detokenize>() -> T {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
fn should_be_able_to_compile() {
|
fn should_be_able_to_compile() {
|
||||||
let _tokens: Vec<Token> = output();
|
let _tokens: Vec<Token> = assert_detokenize();
|
||||||
let _uint: U256 = output();
|
let _uint: U256 = assert_detokenize();
|
||||||
let _address: Address = output();
|
let _address: Address = assert_detokenize();
|
||||||
let _string: String = output();
|
let _string: String = assert_detokenize();
|
||||||
let _bool: bool = output();
|
let _bool: bool = assert_detokenize();
|
||||||
let _bytes: Vec<u8> = output();
|
let _bytes: Vec<u8> = assert_detokenize();
|
||||||
|
|
||||||
let _pair: (U256, bool) = output();
|
let _pair: (U256, bool) = assert_detokenize();
|
||||||
let _vec: Vec<U256> = output();
|
let _vec: Vec<U256> = assert_detokenize();
|
||||||
let _array: [U256; 4] = output();
|
let _array: [U256; 4] = assert_detokenize();
|
||||||
let _bytes: Vec<[[u8; 1]; 64]> = output();
|
let _bytes: Vec<[[u8; 1]; 64]> = assert_detokenize();
|
||||||
|
|
||||||
let _mixed: (Vec<Vec<u8>>, [U256; 4], Vec<U256>, U256) = output();
|
let _mixed: (Vec<Vec<u8>>, [U256; 4], Vec<U256>, U256) = assert_detokenize();
|
||||||
|
|
||||||
let _ints: (i16, i32, i64, i128) = output();
|
let _ints: (i16, i32, i64, i128) = assert_detokenize();
|
||||||
let _uints: (u16, u32, u64, u128) = output();
|
let _uints: (u16, u32, u64, u128) = assert_detokenize();
|
||||||
|
|
||||||
let _tuple: (Address, Vec<Vec<u8>>) = output();
|
let _tuple: (Address, Vec<Vec<u8>>) = assert_detokenize();
|
||||||
let _vec_of_tuple: Vec<(Address, String)> = output();
|
let _vec_of_tuple: Vec<(Address, String)> = assert_detokenize();
|
||||||
let _vec_of_tuple_5: Vec<(Address, Vec<Vec<u8>>, String, U256, bool)> = output();
|
let _vec_of_tuple_5: Vec<(Address, Vec<Vec<u8>>, String, U256, bool)> = assert_detokenize();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue