feat(contract): update Multicall to Multicall3 (#1584)
* chore: update multicall_contract binding * feat: update multicall_contract.rs to Multicall3 * fix multicall_contract.rs exports * update test Multicall.sol contract * feat: update Multicall to Multicall3 * update exports * update tests * perf: use aggregate3 when no value is being sent * feat: handle revert data * test: add multicall v2 and v3 tests * fix: clippy * docs: add documentation, improve comments * docs: add more documentation * fix: solidity minimum version for Multicall.sol * fix: multicall_contract.rs imports * docs: add explanation for previous commit * docs * docs * fix: remove unused re-export, feature gate Multicall * fix: address review * chore: improve error handling * chore: export MulticallError * docs
This commit is contained in:
parent
0b04ffe787
commit
edc00054b1
|
@ -25,8 +25,15 @@ pub use log::{decode_logs, EthLogDecode, LogMeta};
|
||||||
|
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "abigen"))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
||||||
mod multicall;
|
mod multicall;
|
||||||
pub use multicall::Multicall;
|
#[cfg(any(test, feature = "abigen"))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
||||||
|
pub use multicall::{
|
||||||
|
Multicall, MulticallContract, MulticallError, 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
|
||||||
/// type-safe ABI bindings generators.
|
/// type-safe ABI bindings generators.
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Detokenize, Function, Token},
|
abi::{AbiDecode, Detokenize, Function, Token},
|
||||||
types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, U256},
|
types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, H160, U256},
|
||||||
};
|
};
|
||||||
use ethers_providers::Middleware;
|
use ethers_providers::Middleware;
|
||||||
|
|
||||||
use std::{collections::HashMap, str::FromStr, sync::Arc};
|
use std::{convert::TryFrom, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
call::{ContractCall, ContractError},
|
call::{ContractCall, ContractError},
|
||||||
|
@ -12,55 +12,160 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod multicall_contract;
|
mod multicall_contract;
|
||||||
use multicall_contract::MulticallContract;
|
use multicall_contract::multicall_3::{
|
||||||
|
Call as Multicall1Call, Call3 as Multicall3Call, Call3Value as Multicall3CallValue,
|
||||||
|
Result as MulticallResult,
|
||||||
|
};
|
||||||
|
|
||||||
/// A lazily computed hash map with the Ethereum network IDs as keys and the corresponding
|
// Export the contract interface
|
||||||
/// Multicall smart contract addresses as values
|
pub use multicall_contract::multicall_3::Multicall3 as MulticallContract;
|
||||||
pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
|
||||||
fn decode_address(input: &str) -> Address {
|
|
||||||
Address::from_str(input).expect("Decoding failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// The Multicall3 contract address that is deployed in [`MULTICALL_SUPPORTED_CHAIN_IDS`]:
|
||||||
|
/// [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11)
|
||||||
|
pub const MULTICALL_ADDRESS: Address = H160([
|
||||||
|
0xca, 0x11, 0xbd, 0xe0, 0x59, 0x77, 0xb3, 0x63, 0x11, 0x67, 0x02, 0x88, 0x62, 0xbe, 0x2a, 0x17,
|
||||||
|
0x39, 0x76, 0xca, 0x11,
|
||||||
|
]);
|
||||||
|
|
||||||
|
/// The chain IDs that [`MULTICALL_ADDRESS`] has been deployed to.
|
||||||
|
/// Taken from: https://github.com/mds1/multicall#multicall3-contract-addresses
|
||||||
|
pub static MULTICALL_SUPPORTED_CHAIN_IDS: Lazy<[U256; 47]> = Lazy::new(|| {
|
||||||
|
use Chain::*;
|
||||||
[
|
[
|
||||||
(Chain::Mainnet.into(), decode_address("eefba1e63905ef1d7acba5a8513c70307c1ce441")),
|
U256::from(Mainnet), // Mainnet
|
||||||
(Chain::Rinkeby.into(), decode_address("42ad527de7d4e9d9d011ac45b31d8551f8fe9821")),
|
U256::from(Kovan), // Kovan
|
||||||
(Chain::Goerli.into(), decode_address("77dca2c955b15e9de4dbbcf1246b4b85b651e50e")),
|
U256::from(Rinkeby), // Rinkeby
|
||||||
(Chain::Kovan.into(), decode_address("2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a")),
|
U256::from(Goerli), // Goerli
|
||||||
(Chain::XDai.into(), decode_address("b5b692a88bdfc81ca69dcb1d924f59f0413a602a")),
|
U256::from(Ropsten), // Ropsten
|
||||||
(Chain::Polygon.into(), decode_address("11ce4B23bD875D7F5C6a31084f55fDe1e9A87507")),
|
U256::from(Sepolia), // Sepolia
|
||||||
(Chain::PolygonMumbai.into(), decode_address("08411ADd0b5AA8ee47563b146743C13b3556c9Cc")),
|
U256::from(Optimism), // Optimism
|
||||||
(Chain::Fantom.into(), decode_address("C30EB95BC3ff9D322C4300b65a1575F09b4a3eB1")),
|
U256::from(OptimismKovan), // OptimismKovan
|
||||||
(Chain::FantomTestnet.into(), decode_address("280A512EB24Fb655395E0C52D06dcf2dE5253172")),
|
U256::from(420), // OptimismGoerli
|
||||||
|
U256::from(Arbitrum), // Arbitrum
|
||||||
|
U256::from(421613), // ArbitrumGoerli,
|
||||||
|
U256::from(ArbitrumTestnet), // Arbitrum Rinkeby
|
||||||
|
U256::from(Polygon), // Polygon
|
||||||
|
U256::from(PolygonMumbai), // PolygonMumbai
|
||||||
|
U256::from(XDai), // XDai
|
||||||
|
U256::from(Avalanche), // Avalanche
|
||||||
|
U256::from(AvalancheFuji), // AvalancheFuji
|
||||||
|
U256::from(FantomTestnet), // FantomTestnet
|
||||||
|
U256::from(Fantom), // Fantom
|
||||||
|
U256::from(BinanceSmartChain), // BinanceSmartChain
|
||||||
|
U256::from(BinanceSmartChainTestnet), // BinanceSmartChainTestnet
|
||||||
|
U256::from(Moonbeam), // Moonbeam
|
||||||
|
U256::from(Moonriver), // Moonriver
|
||||||
|
U256::from(Moonbase), // Moonbase
|
||||||
|
U256::from(1666600000), // Harmony0
|
||||||
|
U256::from(1666600001), // Harmony1
|
||||||
|
U256::from(1666600002), // Harmony2
|
||||||
|
U256::from(1666600003), // Harmony3
|
||||||
|
U256::from(Cronos), // Cronos
|
||||||
|
U256::from(122), // Fuse
|
||||||
|
U256::from(19), // Songbird
|
||||||
|
U256::from(16), // CostonTestnet
|
||||||
|
U256::from(288), // Boba
|
||||||
|
U256::from(Aurora), // Aurora
|
||||||
|
U256::from(592), // Astar
|
||||||
|
U256::from(66), // OKC
|
||||||
|
U256::from(128), // Heco
|
||||||
|
U256::from(1088), // Metis
|
||||||
|
U256::from(Rsk), // Rsk
|
||||||
|
U256::from(31), // RskTestnet
|
||||||
|
U256::from(Evmos), // Evmos
|
||||||
|
U256::from(EvmosTestnet), // EvmosTestnet
|
||||||
|
U256::from(71402), // Godwoken
|
||||||
|
U256::from(71401), // GodwokenTestnet
|
||||||
|
U256::from(8217), // Klaytn
|
||||||
|
U256::from(2001), // Milkomeda
|
||||||
|
U256::from(321), // KCC
|
||||||
]
|
]
|
||||||
.into()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum MulticallError<M: Middleware> {
|
||||||
|
#[error(transparent)]
|
||||||
|
ContractError(#[from] ContractError<M>),
|
||||||
|
|
||||||
|
#[error("Chain ID {0} is currently not supported by Multicall. Provide an address instead.")]
|
||||||
|
InvalidChainId(U256),
|
||||||
|
|
||||||
|
#[error("Illegal revert: Multicall2 call reverted when it wasn't allowed to.")]
|
||||||
|
IllegalRevert,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T, M> = std::result::Result<T, MulticallError<M>>;
|
||||||
|
|
||||||
|
/// The version of the [`Multicall`](super::Multicall).
|
||||||
|
/// Used to determine which methods of the Multicall smart contract to use:
|
||||||
|
/// - [`Multicall`] : `aggregate((address,bytes)[])`
|
||||||
|
/// - [`Multicall2`] : `try_aggregate(bool, (address,bytes)[])`
|
||||||
|
/// - [`Multicall3`] : `aggregate3((address,bool,bytes)[])` or
|
||||||
|
/// `aggregate3Value((address,bool,uint256,bytes)[])`
|
||||||
|
///
|
||||||
|
/// [`Multicall`]: #variant.Multicall
|
||||||
|
/// [`Multicall2`]: #variant.Multicall2
|
||||||
|
/// [`Multicall3`]: #variant.Multicall3
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum MulticallVersion {
|
||||||
|
Multicall = 1,
|
||||||
|
Multicall2 = 2,
|
||||||
|
#[default]
|
||||||
|
Multicall3 = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MulticallVersion> for u8 {
|
||||||
|
fn from(v: MulticallVersion) -> Self {
|
||||||
|
v as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for MulticallVersion {
|
||||||
|
type Error = String;
|
||||||
|
fn try_from(v: u8) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match v {
|
||||||
|
1 => Ok(MulticallVersion::Multicall),
|
||||||
|
2 => Ok(MulticallVersion::Multicall2),
|
||||||
|
3 => Ok(MulticallVersion::Multicall3),
|
||||||
|
_ => Err(format!("Invalid Multicall version: {}. Accepted values: 1, 2, 3.", v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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/0xeefba1e63905ef1d7acba5a8513c70307c1ce441#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 made.
|
/// and the user provided list of transactions to be called or executed on chain.
|
||||||
///
|
///
|
||||||
/// `Multicall` can instantiate the Multicall contract instance from the chain ID of the client
|
/// `Multicall` can be instantiated asynchronously from the chain ID of the provided client using
|
||||||
/// supplied to [`new`]. It supports the Ethereum mainnet, as well as testnets
|
/// [`new`] or synchronously by providing a chain ID in [`new_with_chain`]. This, by default, uses
|
||||||
/// [Rinkeby](https://rinkeby.etherscan.io/address/0x42ad527de7d4e9d9d011ac45b31d8551f8fe9821#code),
|
/// [`MULTICALL_ADDRESS`], but can be overridden by providing `Some(address)`.
|
||||||
/// [Goerli](https://goerli.etherscan.io/address/0x77dca2c955b15e9de4dbbcf1246b4b85b651e50e) and
|
/// A list of all the supported chains is available [`here`](https://github.com/mds1/multicall#multicall3-contract-addresses).
|
||||||
/// [Kovan](https://kovan.etherscan.io/address/0x2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a#code).
|
|
||||||
///
|
///
|
||||||
/// Additionally, the `block` number can be provided for the call by using the [`block`] method.
|
/// Set the contract's version by using [`version`].
|
||||||
/// Build on the `Multicall` instance by adding calls using the [`add_call`] method.
|
///
|
||||||
|
/// The `block` number can be provided for the call by using [`block`].
|
||||||
|
///
|
||||||
|
/// Transactions default to `EIP1559`. This can be changed by using [`legacy`].
|
||||||
|
///
|
||||||
|
/// Build on the `Multicall` instance by adding calls using [`add_call`] and call or broadcast them
|
||||||
|
/// all at once by using [`call`] and [`send`] respectively.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
/// Using Multicall (version 1):
|
||||||
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use ethers_core::{
|
/// use ethers_core::{
|
||||||
/// abi::Abi,
|
/// abi::Abi,
|
||||||
/// types::{Address, H256, U256},
|
/// types::{Address, H256, U256},
|
||||||
/// };
|
/// };
|
||||||
/// use ethers_contract::{Contract, Multicall};
|
/// use ethers_contract::{Contract, Multicall, MulticallVersion};
|
||||||
/// use ethers_providers::{Middleware, Http, Provider, PendingTransaction};
|
/// use ethers_providers::{Middleware, Http, Provider, PendingTransaction};
|
||||||
/// use std::{convert::TryFrom, sync::Arc};
|
/// use std::{convert::TryFrom, sync::Arc};
|
||||||
///
|
///
|
||||||
/// # async fn bar() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn bar() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// // this is a dummy address used for illustration purpose
|
/// // this is a dummy address used for illustration purposes
|
||||||
/// let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
|
/// let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
|
||||||
///
|
///
|
||||||
/// // (ugly way to write the ABI inline, you can otherwise read it from a file)
|
/// // (ugly way to write the ABI inline, you can otherwise read it from a file)
|
||||||
|
@ -71,34 +176,40 @@ pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
||||||
///
|
///
|
||||||
/// // 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);
|
||||||
/// let contract = Contract::<Provider<Http>>::new(address, abi, Arc::clone(&client));
|
/// let contract = Contract::<Provider<Http>>::new(address, abi, client.clone());
|
||||||
///
|
///
|
||||||
/// // note that these [`ContractCall`]s are futures, and need to be `.await`ed to resolve.
|
/// // note that these [`ContractCall`]s are futures, and need to be `.await`ed to resolve.
|
||||||
/// // But we will let `Multicall` to take care of that for us
|
/// // But we will let `Multicall` to take care of that for us
|
||||||
/// 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 the Kovan testnet, 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(Arc::clone(&client), None).await?;
|
/// let mut multicall = Multicall::new(client.clone(), None).await?.version(MulticallVersion::Multicall);
|
||||||
/// multicall
|
/// multicall
|
||||||
/// .add_call(first_call)
|
/// .add_call(first_call, false)
|
||||||
/// .add_call(second_call);
|
/// .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.
|
||||||
/// let first_broadcast = contract.method::<_, H256>("setValue", "some value".to_owned())?;
|
/// let first_broadcast = contract.method::<_, H256>("setValue", "some value".to_owned())?;
|
||||||
/// let second_broadcast = contract.method::<_, H256>("setValue", "new value".to_owned())?;
|
/// let second_broadcast = contract.method::<_, H256>("setValue", "new value".to_owned())?;
|
||||||
/// let multicall = multicall
|
/// multicall
|
||||||
/// .clear_calls()
|
/// .clear_calls()
|
||||||
/// .add_call(first_broadcast)
|
/// .add_call(first_broadcast, false)
|
||||||
/// .add_call(second_broadcast);
|
/// .add_call(second_broadcast, false);
|
||||||
///
|
///
|
||||||
/// // `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
|
||||||
|
@ -108,23 +219,42 @@ pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
||||||
/// // 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>()?;
|
||||||
/// let multicall = multicall
|
///
|
||||||
|
/// // using version 1
|
||||||
|
/// multicall = multicall.version(MulticallVersion::Multicall);
|
||||||
|
/// multicall
|
||||||
/// .clear_calls()
|
/// .clear_calls()
|
||||||
/// .eth_balance_of(address_1)
|
/// .eth_balance_of(address_1, false)
|
||||||
/// .eth_balance_of(address_2);
|
/// .eth_balance_of(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()
|
||||||
|
/// .eth_balance_of(address_1, false)
|
||||||
|
/// .eth_balance_of(address_2, false);
|
||||||
|
/// let _balances: ((bool, U256), (bool, U256)) = multicall.call().await?;
|
||||||
|
///
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`new`]: method@crate::Multicall::new
|
/// [`new`]: #method.new
|
||||||
/// [`block`]: method@crate::Multicall::block
|
/// [`new_with_chain`]: #method.new_with_chain
|
||||||
/// [`add_call`]: method@crate::Multicall::add_call
|
/// [`version`]: #method.version
|
||||||
|
/// [`block`]: #method.block
|
||||||
|
/// [`legacy`]: #method.legacy
|
||||||
|
/// [`add_call`]: #method.add_call
|
||||||
|
/// [`call`]: #method.call
|
||||||
|
/// [`send`]: #method.send
|
||||||
|
#[must_use = "Multicall does nothing unless you use `call` or `send`"]
|
||||||
pub struct Multicall<M> {
|
pub struct Multicall<M> {
|
||||||
calls: Vec<Call>,
|
version: MulticallVersion,
|
||||||
block: Option<BlockNumber>,
|
|
||||||
contract: MulticallContract<M>,
|
|
||||||
legacy: bool,
|
legacy: bool,
|
||||||
|
block: Option<BlockNumber>,
|
||||||
|
calls: Vec<Call>,
|
||||||
|
contract: MulticallContract<M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M> Clone for Multicall<M> {
|
impl<M> Clone for Multicall<M> {
|
||||||
|
@ -134,75 +264,182 @@ impl<M> Clone for Multicall<M> {
|
||||||
block: self.block,
|
block: self.block,
|
||||||
contract: self.contract.clone(),
|
contract: self.contract.clone(),
|
||||||
legacy: self.legacy,
|
legacy: self.legacy,
|
||||||
|
version: self.version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
impl<M: Middleware> std::fmt::Debug for Multicall<M> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Multicall")
|
||||||
|
.field("version", &self.version)
|
||||||
|
.field("legacy", &self.legacy)
|
||||||
|
.field("block", &self.block)
|
||||||
|
.field("calls", &self.calls)
|
||||||
|
.field("contract", &self.contract)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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`.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct Call {
|
pub struct Call {
|
||||||
target: Address,
|
target: Address,
|
||||||
data: Bytes,
|
data: Bytes,
|
||||||
|
value: U256,
|
||||||
|
allow_failure: bool,
|
||||||
function: Function,
|
function: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Middleware> Multicall<M> {
|
impl<M: Middleware> Multicall<M> {
|
||||||
/// Creates a new Multicall instance from the provided client. If provided with an `address`,
|
/// Creates a new Multicall instance from the provided client. If provided with an `address`,
|
||||||
/// it instantiates the Multicall contract with that address. Otherwise it fetches the address
|
/// it instantiates the Multicall contract with that address, otherwise it defaults to
|
||||||
/// from the address book.
|
/// [`MULTICALL_ADDRESS`].
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a [`MulticallError`] if the provider returns an error while getting
|
||||||
|
/// `network_version`.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// If a `None` address is provided, and the provided client also does not belong to one of
|
///
|
||||||
/// the supported network IDs (mainnet, kovan, rinkeby and goerli)
|
/// If a `None` address is provided and the client's network is
|
||||||
pub async fn new<C: Into<Arc<M>>>(
|
/// [not supported](MULTICALL_SUPPORTED_CHAIN_IDS).
|
||||||
client: C,
|
pub async fn new(client: impl Into<Arc<M>>, address: Option<Address>) -> Result<Self, M> {
|
||||||
address: Option<Address>,
|
|
||||||
) -> Result<Self, ContractError<M>> {
|
|
||||||
let client = client.into();
|
let client = client.into();
|
||||||
|
|
||||||
// Fetch chain id and the corresponding address of Multicall contract
|
// Fetch chain id and the corresponding address of Multicall contract
|
||||||
// preference is given to Multicall contract's address if provided
|
// preference is given to Multicall contract's address if provided
|
||||||
// otherwise check the address book for the client's chain ID
|
// otherwise check the supported chain IDs for the client's chain ID
|
||||||
let address: Address = match address {
|
let address: Address = match address {
|
||||||
Some(addr) => addr,
|
Some(addr) => addr,
|
||||||
None => {
|
None => {
|
||||||
let chain_id =
|
let chain_id =
|
||||||
client.get_chainid().await.map_err(ContractError::MiddlewareError)?;
|
client.get_chainid().await.map_err(ContractError::MiddlewareError)?;
|
||||||
match ADDRESS_BOOK.get(&chain_id) {
|
if !MULTICALL_SUPPORTED_CHAIN_IDS.contains(&chain_id) {
|
||||||
Some(addr) => *addr,
|
return Err(MulticallError::InvalidChainId(chain_id))
|
||||||
None => panic!(
|
|
||||||
"Must either be a supported Network ID or provide Multicall contract address"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
MULTICALL_ADDRESS
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Instantiate the multicall contract
|
// Instantiate the multicall contract
|
||||||
let contract = MulticallContract::new(address, client);
|
let contract = MulticallContract::new(address, client);
|
||||||
|
|
||||||
Ok(Self { calls: vec![], block: None, contract, legacy: false })
|
Ok(Self {
|
||||||
|
version: MulticallVersion::Multicall3,
|
||||||
|
legacy: false,
|
||||||
|
block: None,
|
||||||
|
calls: vec![],
|
||||||
|
contract,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a legacy transaction instead of an EIP-1559 one
|
/// Creates a new Multicall instance synchronously from the provided client and address or chain
|
||||||
#[must_use]
|
/// ID. Uses the [default multicall address](MULTICALL_ADDRESS) if no address is provided.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a [`MulticallError`] if the provided chain_id is not in the
|
||||||
|
/// [supported networks](MULTICALL_SUPPORTED_CHAIN_IDS).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If neither an address or chain_id are provided. Since this is not an async function, it will
|
||||||
|
/// not be able to query `net_version` to check if it is supported by the default multicall
|
||||||
|
/// address. Use new(client, None).await instead.
|
||||||
|
pub fn new_with_chain_id(
|
||||||
|
client: impl Into<Arc<M>>,
|
||||||
|
address: Option<Address>,
|
||||||
|
chain_id: Option<impl Into<U256>>,
|
||||||
|
) -> Result<Self, M> {
|
||||||
|
// If no address is provided, check if chain_id is supported and use the default multicall
|
||||||
|
// address.
|
||||||
|
let address: Address = match address {
|
||||||
|
Some(addr) => addr,
|
||||||
|
None => {
|
||||||
|
// Can't fetch chain_id from provider since we're not in an async function so we
|
||||||
|
// panic instead.
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
MULTICALL_ADDRESS
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Instantiate the multicall contract
|
||||||
|
let contract = MulticallContract::new(address, client.into());
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
version: MulticallVersion::Multicall3,
|
||||||
|
legacy: false,
|
||||||
|
block: None,
|
||||||
|
calls: vec![],
|
||||||
|
contract,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes which functions to use when making the contract call. The default is 3. Version
|
||||||
|
/// differences (adapted from [here](https://github.com/mds1/multicall#multicall---)):
|
||||||
|
///
|
||||||
|
/// - Multicall (v1): This is the recommended version for simple calls. The original contract
|
||||||
|
/// containing an aggregate method to batch calls. Each call returns only the return data and
|
||||||
|
/// none are allowed to fail.
|
||||||
|
///
|
||||||
|
/// - Multicall2 (v2): The same as Multicall, but provides additional methods that allow either
|
||||||
|
/// all or no calls within the batch to fail. Included for backward compatibility. Use v3 to
|
||||||
|
/// allow failure on a per-call basis.
|
||||||
|
///
|
||||||
|
/// - Multicall3 (v3): This is the recommended version for allowing failing calls. It's cheaper
|
||||||
|
/// to use (so you can fit more calls into a single request), and it adds an aggregate3 method
|
||||||
|
/// so you can specify whether calls are allowed to fail on a per-call basis.
|
||||||
|
///
|
||||||
|
/// Note: all these versions are available in the same contract address ([`MULTICALL_ADDRESS`])
|
||||||
|
/// so changing version just changes the methods used, not the contract address.
|
||||||
|
pub fn version(mut self, version: MulticallVersion) -> Self {
|
||||||
|
self.version = version;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a legacy transaction instead of an EIP-1559 one.
|
||||||
pub fn legacy(mut self) -> Self {
|
pub fn legacy(mut self) -> Self {
|
||||||
self.legacy = true;
|
self.legacy = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the `block` field for the multicall aggregate call
|
/// Sets the `block` field for the multicall aggregate call.
|
||||||
#[must_use]
|
pub fn block(mut self, block: impl Into<BlockNumber>) -> Self {
|
||||||
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
|
||||||
self.block = Some(block.into());
|
self.block = Some(block.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Appends a `call` to the list of calls for the Multicall instance
|
/// Appends a `call` to the list of calls for the Multicall instance.
|
||||||
pub fn add_call<D: Detokenize>(&mut self, call: ContractCall<M, D>) -> &mut Self {
|
///
|
||||||
|
/// 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_call<D: Detokenize>(
|
||||||
|
&mut self,
|
||||||
|
call: ContractCall<M, D>,
|
||||||
|
allow_failure: bool,
|
||||||
|
) -> &mut Self {
|
||||||
match (call.tx.to(), call.tx.data()) {
|
match (call.tx.to(), call.tx.data()) {
|
||||||
(Some(NameOrAddress::Address(target)), Some(data)) => {
|
(Some(NameOrAddress::Address(target)), Some(data)) => {
|
||||||
let call = Call { target: *target, data: data.clone(), function: call.function };
|
let call = Call {
|
||||||
|
target: *target,
|
||||||
|
data: data.clone(),
|
||||||
|
value: call.tx.value().cloned().unwrap_or_default(),
|
||||||
|
allow_failure,
|
||||||
|
function: call.function,
|
||||||
|
};
|
||||||
self.calls.push(call);
|
self.calls.push(call);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -215,15 +452,18 @@ impl<M: Middleware> Multicall<M> {
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// If more than the maximum number of supported calls are added. The maximum
|
/// If more than the maximum number of supported calls are added (16). The maximum limit is
|
||||||
/// limits is constrained due to tokenization/detokenization support for tuples
|
/// constrained due to tokenization/detokenization support for tuples.
|
||||||
pub fn eth_balance_of(&mut self, addr: Address) -> &mut Self {
|
pub fn eth_balance_of(&mut self, addr: Address, allow_failure: bool) -> &mut Self {
|
||||||
let call = self.contract.get_eth_balance(addr);
|
let call = self.contract.get_eth_balance(addr);
|
||||||
self.add_call(call)
|
self.add_call(call, allow_failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the batch of calls from the Multicall instance. Re-use the already instantiated
|
/// Clears the batch of calls from the Multicall instance.
|
||||||
/// Multicall, to send a different batch of transactions or do another aggregate query
|
/// Re-use the already instantiated Multicall to send a different batch of transactions or do
|
||||||
|
/// another aggregate query.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
@ -244,8 +484,8 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// #
|
/// #
|
||||||
/// let mut multicall = Multicall::new(client, None).await?;
|
/// let mut multicall = Multicall::new(client, None).await?;
|
||||||
/// multicall
|
/// multicall
|
||||||
/// .add_call(broadcast_1)
|
/// .add_call(broadcast_1, false)
|
||||||
/// .add_call(broadcast_2);
|
/// .add_call(broadcast_2, false);
|
||||||
///
|
///
|
||||||
/// let _tx_hash = multicall.send().await?;
|
/// let _tx_hash = multicall.send().await?;
|
||||||
///
|
///
|
||||||
|
@ -253,9 +493,12 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// # let call_2 = contract.method::<_, Address>("lastSender", ())?;
|
/// # let call_2 = contract.method::<_, Address>("lastSender", ())?;
|
||||||
/// multicall
|
/// multicall
|
||||||
/// .clear_calls()
|
/// .clear_calls()
|
||||||
/// .add_call(call_1)
|
/// .add_call(call_1, false)
|
||||||
/// .add_call(call_2);
|
/// .add_call(call_2, false);
|
||||||
|
/// // Version 1:
|
||||||
/// let return_data: (String, Address) = multicall.call().await?;
|
/// let return_data: (String, Address) = multicall.call().await?;
|
||||||
|
/// // Version 2 and above (each call returns also the success status as the first element):
|
||||||
|
/// let return_data: ((bool, String), (bool, Address)) = multicall.call().await?;
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -264,11 +507,23 @@ impl<M: Middleware> Multicall<M> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries the Ethereum blockchain via an `eth_call`, but via the Multicall contract.
|
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract.
|
||||||
///
|
///
|
||||||
/// It returns a [`ContractError<M>`] if there is any error in the RPC call or while
|
/// Note: this method _does not_ send a transaction from your account.
|
||||||
/// detokenizing the tokens back to the expected return type. The return type must be
|
///
|
||||||
/// annonated while calling this method.
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing
|
||||||
|
/// the tokens back to the expected return type.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// The return type must be annonated while 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>> {
|
||||||
|
@ -284,31 +539,29 @@ 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):
|
||||||
|
/// let result: ((bool, U256), (bool, (String, Address)), (bool, bool)) = multicall.call().await?;
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
pub async fn call<D: Detokenize>(&self) -> Result<D, M> {
|
||||||
/// # Panics
|
assert!(self.calls.len() < 16, "Cannot decode more than 16 calls");
|
||||||
///
|
|
||||||
/// 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>> {
|
|
||||||
assert!(self.calls.len() < 16, "Cannot decode more than {} calls", 16);
|
|
||||||
let tokens = self.call_raw().await?;
|
let tokens = self.call_raw().await?;
|
||||||
let tokens = vec![Token::Tuple(tokens)];
|
let tokens = vec![Token::Tuple(tokens)];
|
||||||
let data = D::from_tokens(tokens)?;
|
let data = D::from_tokens(tokens).map_err(ContractError::DetokenizationError)?;
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries the Ethereum blockchain via an `eth_call`, but via the Multicall contract and
|
/// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract and
|
||||||
/// without detokenization.
|
/// without detokenization.
|
||||||
///
|
///
|
||||||
/// It returns a [`ContractError<M>`] if there is any error in the RPC call.
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a [`MulticallError`] if there are any errors in the RPC call.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
@ -330,28 +583,94 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// Note: this method _does not_ send a transaction from your account
|
/// Note: this method _does not_ send a transaction from your account
|
||||||
///
|
///
|
||||||
/// [`ContractError<M>`]: crate::ContractError<M>
|
/// [`ContractError<M>`]: crate::ContractError<M>
|
||||||
|
pub async fn call_raw(&self) -> Result<Vec<Token>, M> {
|
||||||
|
// Different call result types based on version
|
||||||
|
let tokens: Vec<Token> = match self.version {
|
||||||
|
MulticallVersion::Multicall => {
|
||||||
|
let call = self.as_aggregate();
|
||||||
|
let (_, return_data) = call.call().await?;
|
||||||
|
self.calls
|
||||||
|
.iter()
|
||||||
|
.zip(&return_data)
|
||||||
|
.map(|(call, bytes)| {
|
||||||
|
let mut tokens: Vec<Token> = call
|
||||||
|
.function
|
||||||
|
.decode_output(bytes.as_ref())
|
||||||
|
.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`)
|
||||||
|
v @ (MulticallVersion::Multicall2 | MulticallVersion::Multicall3) => {
|
||||||
|
let is_v2 = v == MulticallVersion::Multicall2;
|
||||||
|
let call = if is_v2 { self.as_try_aggregate() } else { self.as_aggregate_3() };
|
||||||
|
let return_data = call.call().await?;
|
||||||
|
self.calls
|
||||||
|
.iter()
|
||||||
|
.zip(&return_data)
|
||||||
|
.map(|(call, res)| {
|
||||||
|
let ret = &res.return_data;
|
||||||
|
let res_token: Token = if res.success {
|
||||||
|
// Decode using call.function
|
||||||
|
let mut res_tokens = call
|
||||||
|
.function
|
||||||
|
.decode_output(ret)
|
||||||
|
.map_err(ContractError::DecodingError)?;
|
||||||
|
match res_tokens.len() {
|
||||||
|
0 => Token::Tuple(vec![]),
|
||||||
|
1 => res_tokens.remove(0),
|
||||||
|
_ => Token::Tuple(res_tokens),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Call reverted
|
||||||
|
|
||||||
|
// v2: In the function call to `tryAggregate`, the `allow_failure` check
|
||||||
|
// 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
|
||||||
|
// 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
|
||||||
|
// aggregate.
|
||||||
|
if !call.allow_failure {
|
||||||
|
return Err(MulticallError::IllegalRevert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with "Error(string)" (0x08c379a0)
|
||||||
|
if ret.len() >= 4 && ret[..4] == [0x08, 0xc3, 0x79, 0xa0] {
|
||||||
|
Token::String(
|
||||||
|
String::decode(&ret[4..]).map_err(ContractError::AbiError)?,
|
||||||
|
)
|
||||||
|
} else if ret.is_empty() {
|
||||||
|
Token::String(String::new())
|
||||||
|
} else {
|
||||||
|
Token::Bytes(ret.to_vec())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// (bool, (...))
|
||||||
|
Ok(Token::Tuple(vec![Token::Bool(res.success), res_token]))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Token>, 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?;
|
|
||||||
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),
|
|
||||||
_ => Token::Tuple(tokens),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<Token>, ContractError<M>>>()?;
|
|
||||||
Ok(tokens)
|
Ok(tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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,
|
||||||
|
/// returning the transaction hash once the transaction confirms.
|
||||||
|
///
|
||||||
|
/// Note: this method will broadcast a transaction from an account, meaning it must have
|
||||||
|
/// sufficient funds for gas and transaction value.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a [`MulticallError`] if there are any errors in the RPC call.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
@ -364,27 +683,46 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
pub async fn send(&self) -> Result<TxHash, M> {
|
||||||
/// Note: this method sends a transaction from your account, and will return an error
|
|
||||||
/// if you do not have sufficient funds to pay for gas
|
|
||||||
pub async fn send(&self) -> Result<TxHash, ContractError<M>> {
|
|
||||||
let contract_call = self.as_contract_call();
|
|
||||||
|
|
||||||
// Broadcast transaction and return the transaction hash
|
// Broadcast transaction and return the transaction hash
|
||||||
// TODO: Can we make this return a PendingTransaction directly instead?
|
// TODO: Can we make this return a PendingTransaction directly instead?
|
||||||
// Seems hard due to `returns a value referencing data owned by the current function`
|
// Seems hard due to `returns a value referencing data owned by the current function`
|
||||||
let tx_hash = *contract_call.send().await?;
|
|
||||||
|
// running clippy --fix on this throws E0597
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
|
let tx_hash = match self.version {
|
||||||
|
MulticallVersion::Multicall => {
|
||||||
|
let call = self.as_aggregate();
|
||||||
|
let hash = *call.send().await?;
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
MulticallVersion::Multicall2 => {
|
||||||
|
let call = self.as_try_aggregate();
|
||||||
|
let hash = *call.send().await?;
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
MulticallVersion::Multicall3 => {
|
||||||
|
let call = self.as_aggregate_3_value();
|
||||||
|
let hash = *call.send().await?;
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(tx_hash)
|
Ok(tx_hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_contract_call(&self) -> ContractCall<M, (U256, Vec<Bytes>)> {
|
/// v1
|
||||||
// Map the Multicall struct into appropriate types for `aggregate` function
|
fn as_aggregate(&self) -> ContractCall<M, (U256, Vec<Bytes>)> {
|
||||||
let calls: Vec<(Address, Bytes)> =
|
// Map the calls vector into appropriate types for `aggregate` function
|
||||||
self.calls.iter().map(|call| (call.target, call.data.clone())).collect();
|
let calls: Vec<Multicall1Call> = self
|
||||||
|
.calls
|
||||||
|
.iter()
|
||||||
|
.map(|call| Multicall1Call { target: call.target, call_data: call.data.clone() })
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Construct the ContractCall for `aggregate` function to broadcast the transaction
|
// Construct the ContractCall for `aggregate` function to broadcast the transaction
|
||||||
let mut contract_call = self.contract.aggregate(calls);
|
let mut contract_call = self.contract.aggregate(calls);
|
||||||
|
|
||||||
if let Some(block) = self.block {
|
if let Some(block) = self.block {
|
||||||
contract_call = contract_call.block(block)
|
contract_call = contract_call.block(block)
|
||||||
};
|
};
|
||||||
|
@ -395,4 +733,99 @@ impl<M: Middleware> Multicall<M> {
|
||||||
|
|
||||||
contract_call
|
contract_call
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// v2
|
||||||
|
fn as_try_aggregate(&self) -> ContractCall<M, Vec<MulticallResult>> {
|
||||||
|
let mut allow_failure = false;
|
||||||
|
// Map the calls vector into appropriate types for `try_aggregate` function
|
||||||
|
let calls: Vec<Multicall1Call> = self
|
||||||
|
.calls
|
||||||
|
.iter()
|
||||||
|
.map(|call| {
|
||||||
|
// Allow entire call failure if at least one call is allowed to fail.
|
||||||
|
// To avoid iterating multiple times, equivalent of:
|
||||||
|
// self.calls.iter().any(|call| call.allow_failure)
|
||||||
|
allow_failure = allow_failure || call.allow_failure;
|
||||||
|
Multicall1Call { target: call.target, call_data: call.data.clone() }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Construct the ContractCall for `try_aggregate` function to broadcast the transaction
|
||||||
|
let mut contract_call = self.contract.try_aggregate(!allow_failure, calls);
|
||||||
|
|
||||||
|
if let Some(block) = self.block {
|
||||||
|
contract_call = contract_call.block(block)
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.legacy {
|
||||||
|
contract_call = contract_call.legacy();
|
||||||
|
};
|
||||||
|
|
||||||
|
contract_call
|
||||||
|
}
|
||||||
|
|
||||||
|
/// v3
|
||||||
|
fn as_aggregate_3(&self) -> ContractCall<M, Vec<MulticallResult>> {
|
||||||
|
// Map the calls vector into appropriate types for `aggregate_3` function
|
||||||
|
let calls: Vec<Multicall3Call> = self
|
||||||
|
.calls
|
||||||
|
.iter()
|
||||||
|
.map(|call| Multicall3Call {
|
||||||
|
target: call.target,
|
||||||
|
call_data: call.data.clone(),
|
||||||
|
allow_failure: call.allow_failure,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Construct the ContractCall for `aggregate_3` function to broadcast the transaction
|
||||||
|
let mut contract_call = self.contract.aggregate_3(calls);
|
||||||
|
|
||||||
|
if let Some(block) = self.block {
|
||||||
|
contract_call = contract_call.block(block)
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.legacy {
|
||||||
|
contract_call = contract_call.legacy();
|
||||||
|
};
|
||||||
|
|
||||||
|
contract_call
|
||||||
|
}
|
||||||
|
|
||||||
|
/// v3 + values (only .send())
|
||||||
|
fn as_aggregate_3_value(&self) -> ContractCall<M, Vec<MulticallResult>> {
|
||||||
|
// Map the calls vector into appropriate types for `aggregate_3_value` function
|
||||||
|
let mut total_value = U256::zero();
|
||||||
|
let calls: Vec<Multicall3CallValue> = self
|
||||||
|
.calls
|
||||||
|
.iter()
|
||||||
|
.map(|call| {
|
||||||
|
total_value += call.value;
|
||||||
|
Multicall3CallValue {
|
||||||
|
target: call.target,
|
||||||
|
call_data: call.data.clone(),
|
||||||
|
allow_failure: call.allow_failure,
|
||||||
|
value: call.value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if total_value.is_zero() {
|
||||||
|
// No value is being sent
|
||||||
|
self.as_aggregate_3()
|
||||||
|
} else {
|
||||||
|
// Construct the ContractCall for `aggregate_3_value` function to broadcast the
|
||||||
|
// transaction
|
||||||
|
let mut contract_call = self.contract.aggregate_3_value(calls);
|
||||||
|
|
||||||
|
if let Some(block) = self.block {
|
||||||
|
contract_call = contract_call.block(block)
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.legacy {
|
||||||
|
contract_call = contract_call.legacy();
|
||||||
|
};
|
||||||
|
|
||||||
|
contract_call.value(total_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -6,9 +6,9 @@ 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::{LogMeta, Multicall};
|
use ethers_contract::{LogMeta, Multicall, MulticallVersion};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Detokenize, Token, Tokenizable},
|
abi::{encode, Detokenize, Token, Tokenizable},
|
||||||
types::{transaction::eip712::Eip712, Address, BlockId, Bytes, I256, U256},
|
types::{transaction::eip712::Eip712, Address, BlockId, Bytes, I256, U256},
|
||||||
utils::{keccak256, Anvil},
|
utils::{keccak256, Anvil},
|
||||||
};
|
};
|
||||||
|
@ -359,8 +359,8 @@ mod eth_tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn multicall_aggregate() {
|
async fn multicall_aggregate() {
|
||||||
// get ABI and bytecode for the Multcall contract
|
// get ABI and bytecode for the Multicall contract
|
||||||
let (multicall_abi, multicall_bytecode) = compile_contract("Multicall", "Multicall.sol");
|
let (multicall_abi, multicall_bytecode) = compile_contract("Multicall3", "Multicall.sol");
|
||||||
|
|
||||||
// get ABI and bytecode for the NotSoSimpleStorage contract
|
// get ABI and bytecode for the NotSoSimpleStorage contract
|
||||||
let (not_so_simple_abi, not_so_simple_bytecode) =
|
let (not_so_simple_abi, not_so_simple_bytecode) =
|
||||||
|
@ -441,7 +441,14 @@ mod eth_tests {
|
||||||
// initiate the Multicall instance and add calls one by one in builder style
|
// initiate the Multicall instance and add calls one by one in builder style
|
||||||
let mut multicall = Multicall::new(client4.clone(), Some(addr)).await.unwrap();
|
let mut multicall = Multicall::new(client4.clone(), Some(addr)).await.unwrap();
|
||||||
|
|
||||||
multicall.add_call(value).add_call(value2).add_call(last_sender).add_call(last_sender2);
|
// Set version to 1
|
||||||
|
multicall = multicall.version(MulticallVersion::Multicall);
|
||||||
|
|
||||||
|
multicall
|
||||||
|
.add_call(value, false)
|
||||||
|
.add_call(value2, false)
|
||||||
|
.add_call(last_sender, false)
|
||||||
|
.add_call(last_sender2, false);
|
||||||
|
|
||||||
let return_data: (String, (String, Address), Address, Address) =
|
let return_data: (String, (String, Address), Address, Address) =
|
||||||
multicall.call().await.unwrap();
|
multicall.call().await.unwrap();
|
||||||
|
@ -467,7 +474,7 @@ mod eth_tests {
|
||||||
// go. Now we will use the `.send()` functionality to broadcast a batch of transactions
|
// go. Now we will use the `.send()` functionality to broadcast a batch of transactions
|
||||||
// in one go
|
// in one go
|
||||||
let mut multicall_send = multicall.clone();
|
let mut multicall_send = multicall.clone();
|
||||||
multicall_send.clear_calls().add_call(broadcast).add_call(broadcast2);
|
multicall_send.clear_calls().add_call(broadcast, false).add_call(broadcast2, false);
|
||||||
|
|
||||||
// broadcast the transaction and wait for it to be mined
|
// broadcast the transaction and wait for it to be mined
|
||||||
let tx_hash = multicall_send.legacy().send().await.unwrap();
|
let tx_hash = multicall_send.legacy().send().await.unwrap();
|
||||||
|
@ -492,9 +499,9 @@ mod eth_tests {
|
||||||
// so should have 100 ETH
|
// so should have 100 ETH
|
||||||
multicall
|
multicall
|
||||||
.clear_calls()
|
.clear_calls()
|
||||||
.eth_balance_of(addrs[4])
|
.eth_balance_of(addrs[4], false)
|
||||||
.eth_balance_of(addrs[5])
|
.eth_balance_of(addrs[5], false)
|
||||||
.eth_balance_of(addrs[6]);
|
.eth_balance_of(addrs[6], false);
|
||||||
|
|
||||||
let balances: (U256, U256, U256) = multicall.call().await.unwrap();
|
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.0, U256::from(10_000_000_000_000_000_000_000u128));
|
||||||
|
@ -517,7 +524,7 @@ mod eth_tests {
|
||||||
// build up a list of calls greater than the 16 max restriction
|
// build up a list of calls greater than the 16 max restriction
|
||||||
for i in 0..=16 {
|
for i in 0..=16 {
|
||||||
let call = simple_contract.method::<_, String>("getValue", ()).unwrap();
|
let call = simple_contract.method::<_, String>("getValue", ()).unwrap();
|
||||||
multicall.add_call(call);
|
multicall.add_call(call, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// must use `call_raw` as `.calls` > 16
|
// must use `call_raw` as `.calls` > 16
|
||||||
|
@ -531,6 +538,182 @@ mod eth_tests {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(results, ["many"; 17]);
|
assert_eq!(results, ["many"; 17]);
|
||||||
|
|
||||||
|
// test version 2
|
||||||
|
multicall = multicall.version(MulticallVersion::Multicall2);
|
||||||
|
|
||||||
|
// deploy contract with reverting methods
|
||||||
|
let reverting_contract = {
|
||||||
|
let (abi, bytecode) =
|
||||||
|
compile_contract("SimpleRevertingStorage", "SimpleRevertingStorage.sol");
|
||||||
|
let f = ContractFactory::new(abi, bytecode, client.clone());
|
||||||
|
f.deploy("This contract can revert".to_string()).unwrap().send().await.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
// reset value
|
||||||
|
reverting_contract
|
||||||
|
.connect(client2.clone())
|
||||||
|
.method::<_, H256>("setValue", ("reset third".to_owned(), false))
|
||||||
|
.unwrap()
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// create calls
|
||||||
|
let set_value_call = reverting_contract
|
||||||
|
.connect(client.clone())
|
||||||
|
.method::<_, H256>("setValue", ("this didn't revert".to_owned(), false))
|
||||||
|
.unwrap();
|
||||||
|
let set_value_reverting_call = reverting_contract
|
||||||
|
.connect(client3.clone())
|
||||||
|
.method::<_, H256>("setValue", ("this reverted".to_owned(), true))
|
||||||
|
.unwrap();
|
||||||
|
let get_value_call = reverting_contract
|
||||||
|
.connect(client2.clone())
|
||||||
|
.method::<_, String>("getValue", (false))
|
||||||
|
.unwrap();
|
||||||
|
let get_value_reverting_call = reverting_contract
|
||||||
|
.connect(client.clone())
|
||||||
|
.method::<_, String>("getValue", (true))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// .send reverts
|
||||||
|
// don't allow revert
|
||||||
|
multicall
|
||||||
|
.clear_calls()
|
||||||
|
.add_call(set_value_reverting_call.clone(), false)
|
||||||
|
.add_call(set_value_call.clone(), false);
|
||||||
|
multicall.send().await.unwrap_err();
|
||||||
|
|
||||||
|
// value has not changed
|
||||||
|
assert_eq!(get_value_call.clone().call().await.unwrap(), "reset third");
|
||||||
|
|
||||||
|
// allow revert
|
||||||
|
multicall
|
||||||
|
.clear_calls()
|
||||||
|
.add_call(set_value_reverting_call.clone(), true)
|
||||||
|
.add_call(set_value_call.clone(), false);
|
||||||
|
multicall.send().await.unwrap();
|
||||||
|
|
||||||
|
// value has changed
|
||||||
|
assert_eq!(get_value_call.clone().call().await.unwrap(), "this didn't revert");
|
||||||
|
|
||||||
|
// reset value again
|
||||||
|
reverting_contract
|
||||||
|
.connect(client2.clone())
|
||||||
|
.method::<_, H256>("setValue", ("reset third again".to_owned(), false))
|
||||||
|
.unwrap()
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// .call reverts
|
||||||
|
// don't allow revert
|
||||||
|
multicall
|
||||||
|
.clear_calls()
|
||||||
|
.add_call(get_value_reverting_call.clone(), false)
|
||||||
|
.add_call(get_value_call.clone(), false);
|
||||||
|
let res = multicall.call::<((bool, String), (bool, String))>().await;
|
||||||
|
let err = res.unwrap_err();
|
||||||
|
assert!(err.to_string().contains("Multicall3: call failed"));
|
||||||
|
|
||||||
|
// allow revert
|
||||||
|
multicall
|
||||||
|
.clear_calls()
|
||||||
|
.add_call(get_value_reverting_call.clone(), true)
|
||||||
|
.add_call(get_value_call.clone(), false);
|
||||||
|
let res = multicall.call().await;
|
||||||
|
let data: ((bool, String), (bool, String)) = res.unwrap();
|
||||||
|
|
||||||
|
assert!(!data.0 .0); // first call reverted
|
||||||
|
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
|
||||||
|
.clear_calls()
|
||||||
|
.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, ...)`
|
||||||
|
let res = multicall.call::<((bool, String), (bool, String))>().await;
|
||||||
|
let err = res.unwrap_err();
|
||||||
|
assert!(err.to_string().contains("Illegal revert"));
|
||||||
|
|
||||||
|
// test version 3
|
||||||
|
// aggregate3 is the same as try_aggregate except with allowing failure on a per-call basis.
|
||||||
|
// no need to test that
|
||||||
|
multicall = multicall.version(MulticallVersion::Multicall3);
|
||||||
|
|
||||||
|
// .send with value
|
||||||
|
let amount = U256::from(100);
|
||||||
|
let value_tx = reverting_contract.method::<_, H256>("deposit", ()).unwrap().value(amount);
|
||||||
|
let rc_addr = reverting_contract.address();
|
||||||
|
|
||||||
|
// add a second call because we can't decode using a single element tuple
|
||||||
|
// ((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)
|
||||||
|
.call()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// send 2 value_tx
|
||||||
|
multicall.clear_calls().add_call(value_tx.clone(), false).add_call(value_tx.clone(), false);
|
||||||
|
multicall.send().await.unwrap();
|
||||||
|
|
||||||
|
let bal_after: ((bool, U256), (bool, U256)) = multicall
|
||||||
|
.clear_calls()
|
||||||
|
.eth_balance_of(rc_addr, false)
|
||||||
|
.eth_balance_of(rc_addr, false)
|
||||||
|
.call()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bal_after.0 .1, bal_before.0 .1 + U256::from(2) * amount);
|
||||||
|
|
||||||
|
// test specific revert cases
|
||||||
|
// empty revert
|
||||||
|
let empty_revert = reverting_contract.method::<_, H256>("emptyRevert", ()).unwrap();
|
||||||
|
multicall
|
||||||
|
.clear_calls()
|
||||||
|
.add_call(empty_revert.clone(), true)
|
||||||
|
.add_call(empty_revert.clone(), true);
|
||||||
|
let res: ((bool, String), (bool, String)) = multicall.call().await.unwrap();
|
||||||
|
assert!(!res.0 .0);
|
||||||
|
assert_eq!(res.0 .1, "");
|
||||||
|
|
||||||
|
// string revert
|
||||||
|
let string_revert =
|
||||||
|
reverting_contract.method::<_, H256>("stringRevert", ("String".to_string())).unwrap();
|
||||||
|
multicall.clear_calls().add_call(string_revert, true).add_call(empty_revert.clone(), true);
|
||||||
|
let res: ((bool, String), (bool, String)) = multicall.call().await.unwrap();
|
||||||
|
assert!(!res.0 .0);
|
||||||
|
assert_eq!(res.0 .1, "String");
|
||||||
|
|
||||||
|
// custom error revert
|
||||||
|
let custom_error = reverting_contract.method::<_, H256>("customError", ()).unwrap();
|
||||||
|
multicall.clear_calls().add_call(custom_error, true).add_call(empty_revert.clone(), true);
|
||||||
|
let res: ((bool, Bytes), (bool, String)) = multicall.call().await.unwrap();
|
||||||
|
let selector = &keccak256("CustomError()")[..4];
|
||||||
|
assert!(!res.0 .0);
|
||||||
|
assert_eq!(res.0 .1.len(), 4);
|
||||||
|
assert_eq!(&res.0 .1[..4], selector);
|
||||||
|
|
||||||
|
// custom error with data revert
|
||||||
|
let custom_error_with_data = reverting_contract
|
||||||
|
.method::<_, H256>("customErrorWithData", ("Data".to_string()))
|
||||||
|
.unwrap();
|
||||||
|
multicall
|
||||||
|
.clear_calls()
|
||||||
|
.add_call(custom_error_with_data, true)
|
||||||
|
.add_call(empty_revert.clone(), true);
|
||||||
|
let res: ((bool, Bytes), (bool, String)) = 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,45 +1,259 @@
|
||||||
pragma solidity >=0.5.0;
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity >=0.6.0;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
/// @title Multicall - Aggregate results from multiple read-only function calls
|
/// @title Multicall3
|
||||||
|
/// @notice Aggregate results from multiple function calls
|
||||||
|
/// @dev Multicall & Multicall2 backwards-compatible
|
||||||
|
/// @dev Aggregate methods are marked `payable` to save 24 gas per call
|
||||||
/// @author Michael Elliot <mike@makerdao.com>
|
/// @author Michael Elliot <mike@makerdao.com>
|
||||||
/// @author Joshua Levine <joshua@makerdao.com>
|
/// @author Joshua Levine <joshua@makerdao.com>
|
||||||
/// @author Nick Johnson <arachnid@notdot.net>
|
/// @author Nick Johnson <arachnid@notdot.net>
|
||||||
|
/// @author Andreas Bigger <andreas@nascent.xyz>
|
||||||
contract Multicall {
|
/// @author Matt Solomon <matt@mattsolomon.dev>
|
||||||
|
contract Multicall3 {
|
||||||
struct Call {
|
struct Call {
|
||||||
address target;
|
address target;
|
||||||
bytes callData;
|
bytes callData;
|
||||||
}
|
}
|
||||||
function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
|
|
||||||
|
struct Call3 {
|
||||||
|
address target;
|
||||||
|
bool allowFailure;
|
||||||
|
bytes callData;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Call3Value {
|
||||||
|
address target;
|
||||||
|
bool allowFailure;
|
||||||
|
uint256 value;
|
||||||
|
bytes callData;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Result {
|
||||||
|
bool success;
|
||||||
|
bytes returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Backwards-compatible call aggregation with Multicall
|
||||||
|
/// @param calls An array of Call structs
|
||||||
|
/// @return blockNumber The block number where the calls were executed
|
||||||
|
/// @return returnData An array of bytes containing the responses
|
||||||
|
function aggregate(Call[] calldata calls)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (uint256 blockNumber, bytes[] memory returnData)
|
||||||
|
{
|
||||||
blockNumber = block.number;
|
blockNumber = block.number;
|
||||||
returnData = new bytes[](calls.length);
|
uint256 length = calls.length;
|
||||||
for(uint256 i = 0; i < calls.length; i++) {
|
returnData = new bytes[](length);
|
||||||
(bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
|
Call calldata call;
|
||||||
require(success);
|
for (uint256 i = 0; i < length; ) {
|
||||||
returnData[i] = ret;
|
bool success;
|
||||||
|
call = calls[i];
|
||||||
|
(success, returnData[i]) = call.target.call(call.callData);
|
||||||
|
require(success, "Multicall3: call failed");
|
||||||
|
unchecked {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Helper functions
|
|
||||||
function getEthBalance(address addr) public view returns (uint256 balance) {
|
/// @notice Backwards-compatible with Multicall2
|
||||||
balance = addr.balance;
|
/// @notice Aggregate calls without requiring success
|
||||||
|
/// @param requireSuccess If true, require all calls to succeed
|
||||||
|
/// @param calls An array of Call structs
|
||||||
|
/// @return returnData An array of Result structs
|
||||||
|
function tryAggregate(bool requireSuccess, Call[] calldata calls)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (Result[] memory returnData)
|
||||||
|
{
|
||||||
|
uint256 length = calls.length;
|
||||||
|
returnData = new Result[](length);
|
||||||
|
Call calldata call;
|
||||||
|
for (uint256 i = 0; i < length; ) {
|
||||||
|
Result memory result = returnData[i];
|
||||||
|
call = calls[i];
|
||||||
|
(result.success, result.returnData) = call.target.call(call.callData);
|
||||||
|
if (requireSuccess) require(result.success, "Multicall3: call failed");
|
||||||
|
unchecked {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @notice Backwards-compatible with Multicall2
|
||||||
|
/// @notice Aggregate calls and allow failures using tryAggregate
|
||||||
|
/// @param calls An array of Call structs
|
||||||
|
/// @return blockNumber The block number where the calls were executed
|
||||||
|
/// @return blockHash The hash of the block where the calls were executed
|
||||||
|
/// @return returnData An array of Result structs
|
||||||
|
function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (
|
||||||
|
uint256 blockNumber,
|
||||||
|
bytes32 blockHash,
|
||||||
|
Result[] memory returnData
|
||||||
|
)
|
||||||
|
{
|
||||||
|
blockNumber = block.number;
|
||||||
|
blockHash = blockhash(block.number);
|
||||||
|
returnData = tryAggregate(requireSuccess, calls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Backwards-compatible with Multicall2
|
||||||
|
/// @notice Aggregate calls and allow failures using tryAggregate
|
||||||
|
/// @param calls An array of Call structs
|
||||||
|
/// @return blockNumber The block number where the calls were executed
|
||||||
|
/// @return blockHash The hash of the block where the calls were executed
|
||||||
|
/// @return returnData An array of Result structs
|
||||||
|
function blockAndAggregate(Call[] calldata calls)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (
|
||||||
|
uint256 blockNumber,
|
||||||
|
bytes32 blockHash,
|
||||||
|
Result[] memory returnData
|
||||||
|
)
|
||||||
|
{
|
||||||
|
(blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Aggregate calls, ensuring each returns success if required
|
||||||
|
/// @param calls An array of Call3 structs
|
||||||
|
/// @return returnData An array of Result structs
|
||||||
|
function aggregate3(Call3[] calldata calls)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (Result[] memory returnData)
|
||||||
|
{
|
||||||
|
uint256 length = calls.length;
|
||||||
|
returnData = new Result[](length);
|
||||||
|
Call3 calldata calli;
|
||||||
|
for (uint256 i = 0; i < length; ) {
|
||||||
|
Result memory result = returnData[i];
|
||||||
|
calli = calls[i];
|
||||||
|
(result.success, result.returnData) = calli.target.call(calli.callData);
|
||||||
|
assembly {
|
||||||
|
// Revert if the call fails and failure is not allowed
|
||||||
|
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
|
||||||
|
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
|
||||||
|
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
|
||||||
|
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||||
|
// set data offset
|
||||||
|
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
|
||||||
|
// set length of revert string
|
||||||
|
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
|
||||||
|
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
|
||||||
|
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
|
||||||
|
revert(0x00, 0x64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unchecked {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Aggregate calls with a msg value
|
||||||
|
/// @notice Reverts if msg.value is less than the sum of the call values
|
||||||
|
/// @param calls An array of Call3Value structs
|
||||||
|
/// @return returnData An array of Result structs
|
||||||
|
function aggregate3Value(Call3Value[] calldata calls)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (Result[] memory returnData)
|
||||||
|
{
|
||||||
|
uint256 valAccumulator;
|
||||||
|
uint256 length = calls.length;
|
||||||
|
returnData = new Result[](length);
|
||||||
|
Call3Value calldata calli;
|
||||||
|
for (uint256 i = 0; i < length; ) {
|
||||||
|
Result memory result = returnData[i];
|
||||||
|
calli = calls[i];
|
||||||
|
uint256 val = calli.value;
|
||||||
|
// Humanity will be a Type V Kardashev Civilization before this overflows - andreas
|
||||||
|
// ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256
|
||||||
|
unchecked {
|
||||||
|
valAccumulator += val;
|
||||||
|
}
|
||||||
|
(result.success, result.returnData) = calli.target.call{ value: val }(calli.callData);
|
||||||
|
assembly {
|
||||||
|
// Revert if the call fails and failure is not allowed
|
||||||
|
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
|
||||||
|
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
|
||||||
|
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
|
||||||
|
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||||
|
// set data offset
|
||||||
|
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
|
||||||
|
// set length of revert string
|
||||||
|
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
|
||||||
|
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
|
||||||
|
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
|
||||||
|
revert(0x00, 0x84)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unchecked {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finally, make sure the msg.value = SUM(call[0...i].value)
|
||||||
|
require(msg.value == valAccumulator, "Multicall3: value mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Returns the block hash for the given block number
|
||||||
|
/// @param blockNumber The block number
|
||||||
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
|
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
|
||||||
blockHash = blockhash(blockNumber);
|
blockHash = blockhash(blockNumber);
|
||||||
}
|
}
|
||||||
function getLastBlockHash() public view returns (bytes32 blockHash) {
|
|
||||||
blockHash = blockhash(block.number - 1);
|
/// @notice Returns the block number
|
||||||
}
|
function getBlockNumber() public view returns (uint256 blockNumber) {
|
||||||
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
|
blockNumber = block.number;
|
||||||
timestamp = block.timestamp;
|
|
||||||
}
|
|
||||||
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
|
|
||||||
difficulty = block.difficulty;
|
|
||||||
}
|
|
||||||
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
|
|
||||||
gaslimit = block.gaslimit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @notice Returns the block coinbase
|
||||||
function getCurrentBlockCoinbase() public view returns (address coinbase) {
|
function getCurrentBlockCoinbase() public view returns (address coinbase) {
|
||||||
coinbase = block.coinbase;
|
coinbase = block.coinbase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @notice Returns the block difficulty
|
||||||
|
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
|
||||||
|
difficulty = block.difficulty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Returns the block gas limit
|
||||||
|
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
|
||||||
|
gaslimit = block.gaslimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Returns the block timestamp
|
||||||
|
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
|
||||||
|
timestamp = block.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Returns the (ETH) balance of a given address
|
||||||
|
function getEthBalance(address addr) public view returns (uint256 balance) {
|
||||||
|
balance = addr.balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Returns the block hash of the last block
|
||||||
|
function getLastBlockHash() public view returns (bytes32 blockHash) {
|
||||||
|
unchecked {
|
||||||
|
blockHash = blockhash(block.number - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Gets the base fee of the given block
|
||||||
|
/// @notice Can revert if the BASEFEE opcode is not implemented by the given chain
|
||||||
|
function getBasefee() public view returns (uint256 basefee) {
|
||||||
|
basefee = block.basefee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Returns the chain id
|
||||||
|
function getChainId() public view returns (uint256 chainid) {
|
||||||
|
chainid = block.chainid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
pragma solidity >=0.8.4;
|
||||||
|
|
||||||
|
contract SimpleRevertingStorage {
|
||||||
|
event ValueChanged(
|
||||||
|
address indexed author,
|
||||||
|
address indexed oldAuthor,
|
||||||
|
string oldValue,
|
||||||
|
string newValue
|
||||||
|
);
|
||||||
|
|
||||||
|
address public lastSender;
|
||||||
|
string _value;
|
||||||
|
string _otherValue;
|
||||||
|
|
||||||
|
constructor(string memory value) {
|
||||||
|
emit ValueChanged(msg.sender, address(0), _value, value);
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(bool rev) external view returns (string memory) {
|
||||||
|
require(!rev, "getValue revert");
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue(string memory value, bool rev) external {
|
||||||
|
require(!rev, "setValue revert");
|
||||||
|
emit ValueChanged(msg.sender, lastSender, _value, value);
|
||||||
|
_value = value;
|
||||||
|
lastSender = msg.sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
event Deposit(uint256 value);
|
||||||
|
|
||||||
|
function deposit() external payable {
|
||||||
|
emit Deposit(msg.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyRevert() external pure {
|
||||||
|
revert();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringRevert(string calldata data) external pure {
|
||||||
|
revert(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
error CustomError();
|
||||||
|
|
||||||
|
function customError() external pure {
|
||||||
|
revert CustomError();
|
||||||
|
}
|
||||||
|
|
||||||
|
error CustomErrorWithData(string);
|
||||||
|
|
||||||
|
function customErrorWithData(string calldata data) external pure {
|
||||||
|
revert CustomErrorWithData(data);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue