diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs index 9d40688e..13ef0e16 100644 --- a/ethers-contract/src/lib.rs +++ b/ethers-contract/src/lib.rs @@ -25,8 +25,15 @@ pub use log::{decode_logs, EthLogDecode, LogMeta}; pub mod stream; +#[cfg(any(test, feature = "abigen"))] +#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))] 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 /// type-safe ABI bindings generators. diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs index 275b4416..de30403d 100644 --- a/ethers-contract/src/multicall/mod.rs +++ b/ethers-contract/src/multicall/mod.rs @@ -1,10 +1,10 @@ use ethers_core::{ - abi::{Detokenize, Function, Token}, - types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, U256}, + abi::{AbiDecode, Detokenize, Function, Token}, + types::{Address, BlockNumber, Bytes, Chain, NameOrAddress, TxHash, H160, U256}, }; use ethers_providers::Middleware; -use std::{collections::HashMap, str::FromStr, sync::Arc}; +use std::{convert::TryFrom, sync::Arc}; use crate::{ call::{ContractCall, ContractError}, @@ -12,55 +12,160 @@ use crate::{ }; 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 -/// Multicall smart contract addresses as values -pub static ADDRESS_BOOK: Lazy> = Lazy::new(|| { - fn decode_address(input: &str) -> Address { - Address::from_str(input).expect("Decoding failed") - } +// 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`]: +/// [`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")), - (Chain::Rinkeby.into(), decode_address("42ad527de7d4e9d9d011ac45b31d8551f8fe9821")), - (Chain::Goerli.into(), decode_address("77dca2c955b15e9de4dbbcf1246b4b85b651e50e")), - (Chain::Kovan.into(), decode_address("2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a")), - (Chain::XDai.into(), decode_address("b5b692a88bdfc81ca69dcb1d924f59f0413a602a")), - (Chain::Polygon.into(), decode_address("11ce4B23bD875D7F5C6a31084f55fDe1e9A87507")), - (Chain::PolygonMumbai.into(), decode_address("08411ADd0b5AA8ee47563b146743C13b3556c9Cc")), - (Chain::Fantom.into(), decode_address("C30EB95BC3ff9D322C4300b65a1575F09b4a3eB1")), - (Chain::FantomTestnet.into(), decode_address("280A512EB24Fb655395E0C52D06dcf2dE5253172")), + U256::from(Mainnet), // Mainnet + U256::from(Kovan), // Kovan + U256::from(Rinkeby), // Rinkeby + U256::from(Goerli), // Goerli + U256::from(Ropsten), // Ropsten + U256::from(Sepolia), // Sepolia + U256::from(Optimism), // Optimism + U256::from(OptimismKovan), // OptimismKovan + 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 { + #[error(transparent)] + ContractError(#[from] ContractError), + + #[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 = std::result::Result>; + +/// 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 for u8 { + fn from(v: MulticallVersion) -> Self { + v as u8 + } +} + +impl TryFrom for MulticallVersion { + type Error = String; + fn try_from(v: u8) -> std::result::Result { + 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. -/// It stores an instance of the [`Multicall` smart contract](https://etherscan.io/address/0xeefba1e63905ef1d7acba5a8513c70307c1ce441#code) -/// and the user provided list of transactions to be made. +/// 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. /// -/// `Multicall` can instantiate the Multicall contract instance from the chain ID of the client -/// supplied to [`new`]. It supports the Ethereum mainnet, as well as testnets -/// [Rinkeby](https://rinkeby.etherscan.io/address/0x42ad527de7d4e9d9d011ac45b31d8551f8fe9821#code), -/// [Goerli](https://goerli.etherscan.io/address/0x77dca2c955b15e9de4dbbcf1246b4b85b651e50e) and -/// [Kovan](https://kovan.etherscan.io/address/0x2cc8688c5f75e365aaeeb4ea8d6a480405a48d2a#code). +/// `Multicall` can be instantiated asynchronously from the chain ID of the provided client using +/// [`new`] or synchronously by providing a chain ID in [`new_with_chain`]. This, by default, uses +/// [`MULTICALL_ADDRESS`], but can be overridden by providing `Some(address)`. +/// A list of all the supported chains is available [`here`](https://github.com/mds1/multicall#multicall3-contract-addresses). /// -/// Additionally, the `block` number can be provided for the call by using the [`block`] method. -/// Build on the `Multicall` instance by adding calls using the [`add_call`] method. +/// Set the contract's version by using [`version`]. +/// +/// 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 /// +/// Using Multicall (version 1): +/// /// ```no_run /// use ethers_core::{ /// abi::Abi, /// types::{Address, H256, U256}, /// }; -/// use ethers_contract::{Contract, Multicall}; +/// use ethers_contract::{Contract, Multicall, MulticallVersion}; /// use ethers_providers::{Middleware, Http, Provider, PendingTransaction}; /// use std::{convert::TryFrom, sync::Arc}; /// /// # async fn bar() -> Result<(), Box> { -/// // this is a dummy address used for illustration purpose +/// // this is a dummy address used for illustration purposes /// let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::
()?; /// /// // (ugly way to write the ABI inline, you can otherwise read it from a file) @@ -71,34 +176,40 @@ pub static ADDRESS_BOOK: Lazy> = Lazy::new(|| { /// /// // create the contract object. This will be used to construct the calls for multicall /// let client = Arc::new(client); -/// let contract = Contract::>::new(address, abi, Arc::clone(&client)); +/// let contract = Contract::>::new(address, abi, client.clone()); /// /// // 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 /// let first_call = contract.method::<_, String>("getValue", ())?; /// 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 /// // for the Multicall contract, you can pass the `Some(multicall_addr)` argument. -/// // Construction of the `Multicall` instance follows the builder pattern -/// let mut multicall = Multicall::new(Arc::clone(&client), None).await?; +/// // Construction of the `Multicall` instance follows the builder pattern: +/// let mut multicall = Multicall::new(client.clone(), None).await?.version(MulticallVersion::Multicall); /// multicall -/// .add_call(first_call) -/// .add_call(second_call); +/// .add_call(first_call, false) +/// .add_call(second_call, false); /// /// // `await`ing on the `call` method lets us fetch the return values of both the above calls /// // in one single RPC call /// 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. /// // 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 second_broadcast = contract.method::<_, H256>("setValue", "new value".to_owned())?; -/// let multicall = multicall +/// multicall /// .clear_calls() -/// .add_call(first_broadcast) -/// .add_call(second_broadcast); +/// .add_call(first_broadcast, false) +/// .add_call(second_broadcast, false); /// /// // `await`ing the `send` method waits for the transaction to be broadcast, which also /// // returns the transaction hash @@ -108,23 +219,42 @@ pub static ADDRESS_BOOK: Lazy> = Lazy::new(|| { /// // you can also query ETH balances of multiple addresses /// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::
()?; /// let address_2 = "ffffffffffffffffffffffffffffffffffffffff".parse::
()?; -/// let multicall = multicall +/// +/// // using version 1 +/// multicall = multicall.version(MulticallVersion::Multicall); +/// multicall /// .clear_calls() -/// .eth_balance_of(address_1) -/// .eth_balance_of(address_2); +/// .eth_balance_of(address_1, false) +/// .eth_balance_of(address_2, false); /// 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(()) /// # } /// ``` /// -/// [`new`]: method@crate::Multicall::new -/// [`block`]: method@crate::Multicall::block -/// [`add_call`]: method@crate::Multicall::add_call +/// [`new`]: #method.new +/// [`new_with_chain`]: #method.new_with_chain +/// [`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 { - calls: Vec, - block: Option, - contract: MulticallContract, + version: MulticallVersion, legacy: bool, + block: Option, + calls: Vec, + contract: MulticallContract, } impl Clone for Multicall { @@ -134,75 +264,182 @@ impl Clone for Multicall { block: self.block, contract: self.contract.clone(), legacy: self.legacy, + version: self.version, } } } -#[derive(Clone)] +impl std::fmt::Debug for Multicall { + 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` -/// with `data` +/// with `data`. +#[derive(Clone, Debug)] pub struct Call { target: Address, data: Bytes, + value: U256, + allow_failure: bool, function: Function, } impl Multicall { /// 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 - /// from the address book. + /// it instantiates the Multicall contract with that address, otherwise it defaults to + /// [`MULTICALL_ADDRESS`]. + /// + /// # Errors + /// + /// Returns a [`MulticallError`] if the provider returns an error while getting + /// `network_version`. /// /// # 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) - pub async fn new>>( - client: C, - address: Option
, - ) -> Result> { + /// + /// If a `None` address is provided and the client's network is + /// [not supported](MULTICALL_SUPPORTED_CHAIN_IDS). + pub async fn new(client: impl Into>, address: Option
) -> Result { let client = client.into(); // Fetch chain id and the corresponding address of Multicall contract // 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 { Some(addr) => addr, None => { let chain_id = client.get_chainid().await.map_err(ContractError::MiddlewareError)?; - match ADDRESS_BOOK.get(&chain_id) { - Some(addr) => *addr, - None => panic!( - "Must either be a supported Network ID or provide Multicall contract address" - ), + 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); - 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 - #[must_use] + /// Creates a new Multicall instance synchronously from the provided client and address or chain + /// 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>, + address: Option
, + chain_id: Option>, + ) -> Result { + // 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 { self.legacy = true; self } - /// Sets the `block` field for the multicall aggregate call - #[must_use] - pub fn block>(mut self, block: T) -> Self { + /// Sets the `block` field for the multicall aggregate call. + pub fn block(mut self, block: impl Into) -> Self { self.block = Some(block.into()); self } - /// Appends a `call` to the list of calls for the Multicall instance - pub fn add_call(&mut self, call: ContractCall) -> &mut Self { + /// Appends a `call` to the list of calls for the Multicall instance. + /// + /// Version specific details: + /// - 1: `allow_failure` is ignored. + /// - >=2: `allow_failure` specifies whether or not this call is allowed to revert in the + /// multicall. + /// - 3: Transaction values are used when broadcasting transactions with [`send`], otherwise + /// they are always ignored. + /// + /// [`send`]: #method.send + pub fn add_call( + &mut self, + call: ContractCall, + allow_failure: bool, + ) -> &mut Self { match (call.tx.to(), call.tx.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 } @@ -215,15 +452,18 @@ impl Multicall { /// /// # Panics /// - /// If more than the maximum number of supported calls are added. The maximum - /// limits is constrained due to tokenization/detokenization support for tuples - pub fn eth_balance_of(&mut self, addr: Address) -> &mut Self { + /// If more than the maximum number of supported calls are added (16). The maximum limit is + /// constrained due to tokenization/detokenization support for tuples. + pub fn eth_balance_of(&mut self, addr: Address, allow_failure: bool) -> &mut Self { 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 - /// Multicall, to send a different batch of transactions or do another aggregate query + /// Clears the batch of calls from the Multicall instance. + /// Re-use the already instantiated Multicall to send a different batch of transactions or do + /// another aggregate query. + /// + /// # Examples /// /// ```no_run /// # async fn foo() -> Result<(), Box> { @@ -244,8 +484,8 @@ impl Multicall { /// # /// let mut multicall = Multicall::new(client, None).await?; /// multicall - /// .add_call(broadcast_1) - /// .add_call(broadcast_2); + /// .add_call(broadcast_1, false) + /// .add_call(broadcast_2, false); /// /// let _tx_hash = multicall.send().await?; /// @@ -253,9 +493,12 @@ impl Multicall { /// # let call_2 = contract.method::<_, Address>("lastSender", ())?; /// multicall /// .clear_calls() - /// .add_call(call_1) - /// .add_call(call_2); + /// .add_call(call_1, false) + /// .add_call(call_2, false); + /// // Version 1: /// 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(()) /// # } /// ``` @@ -264,11 +507,23 @@ impl Multicall { 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`] if there is any error in the RPC call or while - /// detokenizing the tokens back to the expected return type. The return type must be - /// annonated while calling this method. + /// Note: this method _does not_ send a transaction from your account. + /// + /// # Errors + /// + /// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing + /// the tokens back to the expected return type. + /// + /// # 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 /// # async fn foo() -> Result<(), Box> { @@ -284,31 +539,29 @@ impl Multicall { /// // 1. `returns (uint256)` /// // 2. `returns (string, address)` /// // 3. `returns (bool)` + /// // Version 1: /// 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(()) /// # } /// ``` - /// - /// # Panics - /// - /// If more than the maximum number of supported calls are added. The maximum - /// limits is constrained due to tokenization/detokenization support for tuples - /// - /// Note: this method _does not_ send a transaction from your account - /// - /// [`ContractError`]: crate::ContractError - pub async fn call(&self) -> Result> { - assert!(self.calls.len() < 16, "Cannot decode more than {} calls", 16); + pub async fn call(&self) -> Result { + assert!(self.calls.len() < 16, "Cannot decode more than 16 calls"); let tokens = self.call_raw().await?; let tokens = vec![Token::Tuple(tokens)]; - let data = D::from_tokens(tokens)?; + let data = D::from_tokens(tokens).map_err(ContractError::DetokenizationError)?; 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. /// - /// It returns a [`ContractError`] 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 /// # async fn foo() -> Result<(), Box> { @@ -330,28 +583,94 @@ impl Multicall { /// Note: this method _does not_ send a transaction from your account /// /// [`ContractError`]: crate::ContractError + pub async fn call_raw(&self) -> Result, M> { + // Different call result types based on version + let tokens: Vec = 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 = 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::, 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::, M>>()? + } + }; - pub async fn call_raw(&self) -> Result, ContractError> { - 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 = call.function.decode_output(bytes.as_ref())?; - Ok(match tokens.len() { - 0 => Token::Tuple(vec![]), - 1 => tokens.remove(0), - _ => Token::Tuple(tokens), - }) - }) - .collect::, ContractError>>()?; 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 /// # async fn foo() -> Result<(), Box> { @@ -364,27 +683,46 @@ impl Multicall { /// # Ok(()) /// # } /// ``` - /// - /// 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> { - let contract_call = self.as_contract_call(); - + pub async fn send(&self) -> Result { // Broadcast transaction and return the transaction hash // TODO: Can we make this return a PendingTransaction directly instead? // 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) } - fn as_contract_call(&self) -> ContractCall)> { - // Map the Multicall struct into appropriate types for `aggregate` function - let calls: Vec<(Address, Bytes)> = - self.calls.iter().map(|call| (call.target, call.data.clone())).collect(); + /// v1 + fn as_aggregate(&self) -> ContractCall)> { + // Map the calls vector into appropriate types for `aggregate` function + let calls: Vec = 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 let mut contract_call = self.contract.aggregate(calls); + if let Some(block) = self.block { contract_call = contract_call.block(block) }; @@ -395,4 +733,99 @@ impl Multicall { contract_call } + + /// v2 + fn as_try_aggregate(&self) -> ContractCall> { + let mut allow_failure = false; + // Map the calls vector into appropriate types for `try_aggregate` function + let calls: Vec = 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> { + // Map the calls vector into appropriate types for `aggregate_3` function + let calls: Vec = 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> { + // Map the calls vector into appropriate types for `aggregate_3_value` function + let mut total_value = U256::zero(); + let calls: Vec = 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) + } + } } diff --git a/ethers-contract/src/multicall/multicall_contract.rs b/ethers-contract/src/multicall/multicall_contract.rs index 33ccc625..0b0c7c2a 100644 --- a/ethers-contract/src/multicall/multicall_contract.rs +++ b/ethers-contract/src/multicall/multicall_contract.rs @@ -1,97 +1,909 @@ -pub use multicallcontract_mod::*; -mod multicallcontract_mod { +#[allow(clippy::too_many_arguments, non_camel_case_types)] +pub mod multicall_3 { + #![allow(clippy::enum_variant_names)] #![allow(dead_code)] + #![allow(clippy::type_complexity)] #![allow(unused_imports)] - use crate::{ - builders::{ContractCall, Event}, - Contract, Lazy, - }; - use ethers_core::{ - abi::{Abi, Detokenize, InvalidOutputType, Token, Tokenizable}, - types::*, - }; - use ethers_providers::{JsonRpcClient, Middleware}; - #[doc = "MulticallContract was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs"] - use std::sync::Arc; - pub static MULTICALLCONTRACT_ABI: Lazy = Lazy::new(|| { - serde_json :: from_str ( "[{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct MulticallContract.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"aggregate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes[]\",\"name\":\"returnData\",\"type\":\"bytes[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockCoinbase\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"coinbase\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockDifficulty\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"difficulty\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockGasLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"gaslimit\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getEthBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLastBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" ) . expect ( "invalid abi" ) - }); - pub struct MulticallContract(Contract); - impl Clone for MulticallContract { + // Some macros like EthAbiType and EthAbiCodec expand into using "ethers_contract" which is not + // defined here + mod ethers_contract { + pub use crate::*; + } + #[doc = "Multicall3 was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs"] + pub static MULTICALL3_ABI: ethers_contract::Lazy = + ethers_contract::Lazy::new(|| { + ethers_core :: utils :: __serde_json :: from_str ("[\n {\n \"inputs\": [\n {\n \"components\": [\n { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Call[]\",\n \"name\": \"calls\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"aggregate\",\n \"outputs\": [\n { \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" },\n { \"internalType\": \"bytes[]\", \"name\": \"returnData\", \"type\": \"bytes[]\" }\n ],\n \"stateMutability\": \"payable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"components\": [\n { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n { \"internalType\": \"bool\", \"name\": \"allowFailure\", \"type\": \"bool\" },\n { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Call3[]\",\n \"name\": \"calls\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"aggregate3\",\n \"outputs\": [\n {\n \"components\": [\n { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Result[]\",\n \"name\": \"returnData\",\n \"type\": \"tuple[]\"\n }\n ],\n \"stateMutability\": \"payable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"components\": [\n { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n { \"internalType\": \"bool\", \"name\": \"allowFailure\", \"type\": \"bool\" },\n { \"internalType\": \"uint256\", \"name\": \"value\", \"type\": \"uint256\" },\n { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Call3Value[]\",\n \"name\": \"calls\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"aggregate3Value\",\n \"outputs\": [\n {\n \"components\": [\n { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Result[]\",\n \"name\": \"returnData\",\n \"type\": \"tuple[]\"\n }\n ],\n \"stateMutability\": \"payable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"components\": [\n { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Call[]\",\n \"name\": \"calls\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"blockAndAggregate\",\n \"outputs\": [\n { \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" },\n { \"internalType\": \"bytes32\", \"name\": \"blockHash\", \"type\": \"bytes32\" },\n {\n \"components\": [\n { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Result[]\",\n \"name\": \"returnData\",\n \"type\": \"tuple[]\"\n }\n ],\n \"stateMutability\": \"payable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getBasefee\",\n \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"basefee\", \"type\": \"uint256\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [{ \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" }],\n \"name\": \"getBlockHash\",\n \"outputs\": [{ \"internalType\": \"bytes32\", \"name\": \"blockHash\", \"type\": \"bytes32\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getBlockNumber\",\n \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getChainId\",\n \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"chainid\", \"type\": \"uint256\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getCurrentBlockCoinbase\",\n \"outputs\": [{ \"internalType\": \"address\", \"name\": \"coinbase\", \"type\": \"address\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getCurrentBlockDifficulty\",\n \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"difficulty\", \"type\": \"uint256\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getCurrentBlockGasLimit\",\n \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"gaslimit\", \"type\": \"uint256\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getCurrentBlockTimestamp\",\n \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"timestamp\", \"type\": \"uint256\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [{ \"internalType\": \"address\", \"name\": \"addr\", \"type\": \"address\" }],\n \"name\": \"getEthBalance\",\n \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"balance\", \"type\": \"uint256\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getLastBlockHash\",\n \"outputs\": [{ \"internalType\": \"bytes32\", \"name\": \"blockHash\", \"type\": \"bytes32\" }],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n { \"internalType\": \"bool\", \"name\": \"requireSuccess\", \"type\": \"bool\" },\n {\n \"components\": [\n { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Call[]\",\n \"name\": \"calls\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"tryAggregate\",\n \"outputs\": [\n {\n \"components\": [\n { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Result[]\",\n \"name\": \"returnData\",\n \"type\": \"tuple[]\"\n }\n ],\n \"stateMutability\": \"payable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n { \"internalType\": \"bool\", \"name\": \"requireSuccess\", \"type\": \"bool\" },\n {\n \"components\": [\n { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Call[]\",\n \"name\": \"calls\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"tryBlockAndAggregate\",\n \"outputs\": [\n { \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" },\n { \"internalType\": \"bytes32\", \"name\": \"blockHash\", \"type\": \"bytes32\" },\n {\n \"components\": [\n { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n ],\n \"internalType\": \"struct Multicall3.Result[]\",\n \"name\": \"returnData\",\n \"type\": \"tuple[]\"\n }\n ],\n \"stateMutability\": \"payable\",\n \"type\": \"function\"\n }\n]\n") . expect ("invalid abi") + }); + pub struct Multicall3(ethers_contract::Contract); + impl Clone for Multicall3 { fn clone(&self) -> Self { - MulticallContract(self.0.clone()) + Multicall3(self.0.clone()) } } - impl std::ops::Deref for MulticallContract { - type Target = Contract; + impl std::ops::Deref for Multicall3 { + type Target = ethers_contract::Contract; fn deref(&self) -> &Self::Target { &self.0 } } - impl std::fmt::Debug for MulticallContract { + impl std::fmt::Debug for Multicall3 { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_tuple(stringify!(MulticallContract)).field(&self.address()).finish() + f.debug_tuple(stringify!(Multicall3)).field(&self.address()).finish() } } - impl MulticallContract { + impl Multicall3 { #[doc = r" Creates a new contract instance with the specified `ethers`"] - #[doc = r" client at the given `Address`. The contract derefs to a `ethers::Contract`"] + #[doc = r" client at the given `Address`. The contract derefs to a `crate`"] #[doc = r" object"] - pub fn new, C: Into>>(address: T, client: C) -> Self { - let contract = - Contract::new(address.into(), MULTICALLCONTRACT_ABI.clone(), client.into()); - Self(contract) + pub fn new>( + address: T, + client: ::std::sync::Arc, + ) -> Self { + ethers_contract::Contract::new(address.into(), MULTICALL3_ABI.clone(), client).into() } #[doc = "Calls the contract's `aggregate` (0x252dba42) function"] pub fn aggregate( &self, - calls: Vec<(Address, Bytes)>, - ) -> ContractCall)> { + calls: ::std::vec::Vec, + ) -> ethers_contract::builders::ContractCall< + M, + (ethers_core::types::U256, ::std::vec::Vec), + > { self.0 .method_hash([37, 45, 186, 66], calls) .expect("method not found (this should never happen)") } + #[doc = "Calls the contract's `aggregate3` (0x82ad56cb) function"] + pub fn aggregate_3( + &self, + calls: ::std::vec::Vec, + ) -> ethers_contract::builders::ContractCall> { + self.0 + .method_hash([130, 173, 86, 203], calls) + .expect("method not found (this should never happen)") + } + #[doc = "Calls the contract's `aggregate3Value` (0x174dea71) function"] + pub fn aggregate_3_value( + &self, + calls: ::std::vec::Vec, + ) -> ethers_contract::builders::ContractCall> { + self.0 + .method_hash([23, 77, 234, 113], calls) + .expect("method not found (this should never happen)") + } + #[doc = "Calls the contract's `blockAndAggregate` (0xc3077fa9) function"] + pub fn block_and_aggregate( + &self, + calls: ::std::vec::Vec, + ) -> ethers_contract::builders::ContractCall< + M, + (ethers_core::types::U256, [u8; 32], ::std::vec::Vec), + > { + self.0 + .method_hash([195, 7, 127, 169], calls) + .expect("method not found (this should never happen)") + } + #[doc = "Calls the contract's `getBasefee` (0x3e64a696) function"] + pub fn get_basefee( + &self, + ) -> ethers_contract::builders::ContractCall { + self.0 + .method_hash([62, 100, 166, 150], ()) + .expect("method not found (this should never happen)") + } + #[doc = "Calls the contract's `getBlockHash` (0xee82ac5e) function"] + pub fn get_block_hash( + &self, + block_number: ethers_core::types::U256, + ) -> ethers_contract::builders::ContractCall { + self.0 + .method_hash([238, 130, 172, 94], block_number) + .expect("method not found (this should never happen)") + } + #[doc = "Calls the contract's `getBlockNumber` (0x42cbb15c) function"] + pub fn get_block_number( + &self, + ) -> ethers_contract::builders::ContractCall { + self.0 + .method_hash([66, 203, 177, 92], ()) + .expect("method not found (this should never happen)") + } + #[doc = "Calls the contract's `getChainId` (0x3408e470) function"] + pub fn get_chain_id( + &self, + ) -> ethers_contract::builders::ContractCall { + self.0 + .method_hash([52, 8, 228, 112], ()) + .expect("method not found (this should never happen)") + } + #[doc = "Calls the contract's `getCurrentBlockCoinbase` (0xa8b0574e) function"] + pub fn get_current_block_coinbase( + &self, + ) -> ethers_contract::builders::ContractCall { + self.0 + .method_hash([168, 176, 87, 78], ()) + .expect("method not found (this should never happen)") + } #[doc = "Calls the contract's `getCurrentBlockDifficulty` (0x72425d9d) function"] - pub fn get_current_block_difficulty(&self) -> ContractCall { + pub fn get_current_block_difficulty( + &self, + ) -> ethers_contract::builders::ContractCall { self.0 .method_hash([114, 66, 93, 157], ()) .expect("method not found (this should never happen)") } #[doc = "Calls the contract's `getCurrentBlockGasLimit` (0x86d516e8) function"] - pub fn get_current_block_gas_limit(&self) -> ContractCall { + pub fn get_current_block_gas_limit( + &self, + ) -> ethers_contract::builders::ContractCall { self.0 .method_hash([134, 213, 22, 232], ()) .expect("method not found (this should never happen)") } #[doc = "Calls the contract's `getCurrentBlockTimestamp` (0x0f28c97d) function"] - pub fn get_current_block_timestamp(&self) -> ContractCall { + pub fn get_current_block_timestamp( + &self, + ) -> ethers_contract::builders::ContractCall { self.0 .method_hash([15, 40, 201, 125], ()) .expect("method not found (this should never happen)") } - #[doc = "Calls the contract's `getCurrentBlockCoinbase` (0xa8b0574e) function"] - pub fn get_current_block_coinbase(&self) -> ContractCall { - self.0 - .method_hash([168, 176, 87, 78], ()) - .expect("method not found (this should never happen)") - } - #[doc = "Calls the contract's `getBlockHash` (0xee82ac5e) function"] - pub fn get_block_hash(&self, block_number: U256) -> ContractCall { - self.0 - .method_hash([238, 130, 172, 94], block_number) - .expect("method not found (this should never happen)") - } #[doc = "Calls the contract's `getEthBalance` (0x4d2301cc) function"] - pub fn get_eth_balance(&self, addr: Address) -> ContractCall { + pub fn get_eth_balance( + &self, + addr: ethers_core::types::Address, + ) -> ethers_contract::builders::ContractCall { self.0 .method_hash([77, 35, 1, 204], addr) .expect("method not found (this should never happen)") } #[doc = "Calls the contract's `getLastBlockHash` (0x27e86d6e) function"] - pub fn get_last_block_hash(&self) -> ContractCall { + pub fn get_last_block_hash(&self) -> ethers_contract::builders::ContractCall { self.0 .method_hash([39, 232, 109, 110], ()) .expect("method not found (this should never happen)") } + #[doc = "Calls the contract's `tryAggregate` (0xbce38bd7) function"] + pub fn try_aggregate( + &self, + require_success: bool, + calls: ::std::vec::Vec, + ) -> ethers_contract::builders::ContractCall> { + self.0 + .method_hash([188, 227, 139, 215], (require_success, calls)) + .expect("method not found (this should never happen)") + } + #[doc = "Calls the contract's `tryBlockAndAggregate` (0x399542e9) function"] + pub fn try_block_and_aggregate( + &self, + require_success: bool, + calls: ::std::vec::Vec, + ) -> ethers_contract::builders::ContractCall< + M, + (ethers_core::types::U256, [u8; 32], ::std::vec::Vec), + > { + self.0 + .method_hash([57, 149, 66, 233], (require_success, calls)) + .expect("method not found (this should never happen)") + } + } + impl From> for Multicall3 { + fn from(contract: ethers_contract::Contract) -> Self { + Self(contract) + } + } + #[doc = "Container type for all input parameters for the `aggregate` function with signature `aggregate((address,bytes)[])` and selector `[37, 45, 186, 66]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "aggregate", abi = "aggregate((address,bytes)[])")] + pub struct AggregateCall { + pub calls: ::std::vec::Vec, + } + #[doc = "Container type for all input parameters for the `aggregate3` function with signature `aggregate3((address,bool,bytes)[])` and selector `[130, 173, 86, 203]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "aggregate3", abi = "aggregate3((address,bool,bytes)[])")] + pub struct Aggregate3Call { + pub calls: ::std::vec::Vec, + } + #[doc = "Container type for all input parameters for the `aggregate3Value` function with signature `aggregate3Value((address,bool,uint256,bytes)[])` and selector `[23, 77, 234, 113]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "aggregate3Value", abi = "aggregate3Value((address,bool,uint256,bytes)[])")] + pub struct Aggregate3ValueCall { + pub calls: ::std::vec::Vec, + } + #[doc = "Container type for all input parameters for the `blockAndAggregate` function with signature `blockAndAggregate((address,bytes)[])` and selector `[195, 7, 127, 169]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "blockAndAggregate", abi = "blockAndAggregate((address,bytes)[])")] + pub struct BlockAndAggregateCall { + pub calls: ::std::vec::Vec, + } + #[doc = "Container type for all input parameters for the `getBasefee` function with signature `getBasefee()` and selector `[62, 100, 166, 150]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getBasefee", abi = "getBasefee()")] + pub struct GetBasefeeCall; + #[doc = "Container type for all input parameters for the `getBlockHash` function with signature `getBlockHash(uint256)` and selector `[238, 130, 172, 94]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getBlockHash", abi = "getBlockHash(uint256)")] + pub struct GetBlockHashCall { + pub block_number: ethers_core::types::U256, + } + #[doc = "Container type for all input parameters for the `getBlockNumber` function with signature `getBlockNumber()` and selector `[66, 203, 177, 92]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getBlockNumber", abi = "getBlockNumber()")] + pub struct GetBlockNumberCall; + #[doc = "Container type for all input parameters for the `getChainId` function with signature `getChainId()` and selector `[52, 8, 228, 112]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getChainId", abi = "getChainId()")] + pub struct GetChainIdCall; + #[doc = "Container type for all input parameters for the `getCurrentBlockCoinbase` function with signature `getCurrentBlockCoinbase()` and selector `[168, 176, 87, 78]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getCurrentBlockCoinbase", abi = "getCurrentBlockCoinbase()")] + pub struct GetCurrentBlockCoinbaseCall; + #[doc = "Container type for all input parameters for the `getCurrentBlockDifficulty` function with signature `getCurrentBlockDifficulty()` and selector `[114, 66, 93, 157]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getCurrentBlockDifficulty", abi = "getCurrentBlockDifficulty()")] + pub struct GetCurrentBlockDifficultyCall; + #[doc = "Container type for all input parameters for the `getCurrentBlockGasLimit` function with signature `getCurrentBlockGasLimit()` and selector `[134, 213, 22, 232]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getCurrentBlockGasLimit", abi = "getCurrentBlockGasLimit()")] + pub struct GetCurrentBlockGasLimitCall; + #[doc = "Container type for all input parameters for the `getCurrentBlockTimestamp` function with signature `getCurrentBlockTimestamp()` and selector `[15, 40, 201, 125]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getCurrentBlockTimestamp", abi = "getCurrentBlockTimestamp()")] + pub struct GetCurrentBlockTimestampCall; + #[doc = "Container type for all input parameters for the `getEthBalance` function with signature `getEthBalance(address)` and selector `[77, 35, 1, 204]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getEthBalance", abi = "getEthBalance(address)")] + pub struct GetEthBalanceCall { + pub addr: ethers_core::types::Address, + } + #[doc = "Container type for all input parameters for the `getLastBlockHash` function with signature `getLastBlockHash()` and selector `[39, 232, 109, 110]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "getLastBlockHash", abi = "getLastBlockHash()")] + pub struct GetLastBlockHashCall; + #[doc = "Container type for all input parameters for the `tryAggregate` function with signature `tryAggregate(bool,(address,bytes)[])` and selector `[188, 227, 139, 215]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "tryAggregate", abi = "tryAggregate(bool,(address,bytes)[])")] + pub struct TryAggregateCall { + pub require_success: bool, + pub calls: ::std::vec::Vec, + } + #[doc = "Container type for all input parameters for the `tryBlockAndAggregate` function with signature `tryBlockAndAggregate(bool,(address,bytes)[])` and selector `[57, 149, 66, 233]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthCall, + ethers_contract :: EthDisplay, + )] + #[ethcall(name = "tryBlockAndAggregate", abi = "tryBlockAndAggregate(bool,(address,bytes)[])")] + pub struct TryBlockAndAggregateCall { + pub require_success: bool, + pub calls: ::std::vec::Vec, + } + #[derive(Debug, Clone, PartialEq, Eq, ethers_contract :: EthAbiType)] + pub enum Multicall3Calls { + Aggregate(AggregateCall), + Aggregate3(Aggregate3Call), + Aggregate3Value(Aggregate3ValueCall), + BlockAndAggregate(BlockAndAggregateCall), + GetBasefee(GetBasefeeCall), + GetBlockHash(GetBlockHashCall), + GetBlockNumber(GetBlockNumberCall), + GetChainId(GetChainIdCall), + GetCurrentBlockCoinbase(GetCurrentBlockCoinbaseCall), + GetCurrentBlockDifficulty(GetCurrentBlockDifficultyCall), + GetCurrentBlockGasLimit(GetCurrentBlockGasLimitCall), + GetCurrentBlockTimestamp(GetCurrentBlockTimestampCall), + GetEthBalance(GetEthBalanceCall), + GetLastBlockHash(GetLastBlockHashCall), + TryAggregate(TryAggregateCall), + TryBlockAndAggregate(TryBlockAndAggregateCall), + } + impl ethers_core::abi::AbiDecode for Multicall3Calls { + fn decode( + data: impl AsRef<[u8]>, + ) -> ::std::result::Result { + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::Aggregate(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::Aggregate3(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::Aggregate3Value(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::BlockAndAggregate(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::GetBasefee(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::GetBlockHash(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::GetBlockNumber(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::GetChainId(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::GetCurrentBlockCoinbase(decoded)) + } + if let Ok(decoded) = + ::decode( + data.as_ref(), + ) + { + return Ok(Multicall3Calls::GetCurrentBlockDifficulty(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::GetCurrentBlockGasLimit(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::GetCurrentBlockTimestamp(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::GetEthBalance(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::GetLastBlockHash(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::TryAggregate(decoded)) + } + if let Ok(decoded) = + ::decode(data.as_ref()) + { + return Ok(Multicall3Calls::TryBlockAndAggregate(decoded)) + } + Err(ethers_core::abi::Error::InvalidData.into()) + } + } + impl ethers_core::abi::AbiEncode for Multicall3Calls { + fn encode(self) -> Vec { + match self { + Multicall3Calls::Aggregate(element) => element.encode(), + Multicall3Calls::Aggregate3(element) => element.encode(), + Multicall3Calls::Aggregate3Value(element) => element.encode(), + Multicall3Calls::BlockAndAggregate(element) => element.encode(), + Multicall3Calls::GetBasefee(element) => element.encode(), + Multicall3Calls::GetBlockHash(element) => element.encode(), + Multicall3Calls::GetBlockNumber(element) => element.encode(), + Multicall3Calls::GetChainId(element) => element.encode(), + Multicall3Calls::GetCurrentBlockCoinbase(element) => element.encode(), + Multicall3Calls::GetCurrentBlockDifficulty(element) => element.encode(), + Multicall3Calls::GetCurrentBlockGasLimit(element) => element.encode(), + Multicall3Calls::GetCurrentBlockTimestamp(element) => element.encode(), + Multicall3Calls::GetEthBalance(element) => element.encode(), + Multicall3Calls::GetLastBlockHash(element) => element.encode(), + Multicall3Calls::TryAggregate(element) => element.encode(), + Multicall3Calls::TryBlockAndAggregate(element) => element.encode(), + } + } + } + impl ::std::fmt::Display for Multicall3Calls { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match self { + Multicall3Calls::Aggregate(element) => element.fmt(f), + Multicall3Calls::Aggregate3(element) => element.fmt(f), + Multicall3Calls::Aggregate3Value(element) => element.fmt(f), + Multicall3Calls::BlockAndAggregate(element) => element.fmt(f), + Multicall3Calls::GetBasefee(element) => element.fmt(f), + Multicall3Calls::GetBlockHash(element) => element.fmt(f), + Multicall3Calls::GetBlockNumber(element) => element.fmt(f), + Multicall3Calls::GetChainId(element) => element.fmt(f), + Multicall3Calls::GetCurrentBlockCoinbase(element) => element.fmt(f), + Multicall3Calls::GetCurrentBlockDifficulty(element) => element.fmt(f), + Multicall3Calls::GetCurrentBlockGasLimit(element) => element.fmt(f), + Multicall3Calls::GetCurrentBlockTimestamp(element) => element.fmt(f), + Multicall3Calls::GetEthBalance(element) => element.fmt(f), + Multicall3Calls::GetLastBlockHash(element) => element.fmt(f), + Multicall3Calls::TryAggregate(element) => element.fmt(f), + Multicall3Calls::TryBlockAndAggregate(element) => element.fmt(f), + } + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: AggregateCall) -> Self { + Multicall3Calls::Aggregate(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: Aggregate3Call) -> Self { + Multicall3Calls::Aggregate3(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: Aggregate3ValueCall) -> Self { + Multicall3Calls::Aggregate3Value(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: BlockAndAggregateCall) -> Self { + Multicall3Calls::BlockAndAggregate(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetBasefeeCall) -> Self { + Multicall3Calls::GetBasefee(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetBlockHashCall) -> Self { + Multicall3Calls::GetBlockHash(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetBlockNumberCall) -> Self { + Multicall3Calls::GetBlockNumber(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetChainIdCall) -> Self { + Multicall3Calls::GetChainId(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetCurrentBlockCoinbaseCall) -> Self { + Multicall3Calls::GetCurrentBlockCoinbase(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetCurrentBlockDifficultyCall) -> Self { + Multicall3Calls::GetCurrentBlockDifficulty(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetCurrentBlockGasLimitCall) -> Self { + Multicall3Calls::GetCurrentBlockGasLimit(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetCurrentBlockTimestampCall) -> Self { + Multicall3Calls::GetCurrentBlockTimestamp(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetEthBalanceCall) -> Self { + Multicall3Calls::GetEthBalance(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: GetLastBlockHashCall) -> Self { + Multicall3Calls::GetLastBlockHash(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: TryAggregateCall) -> Self { + Multicall3Calls::TryAggregate(var) + } + } + impl ::std::convert::From for Multicall3Calls { + fn from(var: TryBlockAndAggregateCall) -> Self { + Multicall3Calls::TryBlockAndAggregate(var) + } + } + #[doc = "Container type for all return fields from the `aggregate` function with signature `aggregate((address,bytes)[])` and selector `[37, 45, 186, 66]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct AggregateReturn { + pub block_number: ethers_core::types::U256, + pub return_data: ::std::vec::Vec, + } + #[doc = "Container type for all return fields from the `aggregate3` function with signature `aggregate3((address,bool,bytes)[])` and selector `[130, 173, 86, 203]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct Aggregate3Return { + pub return_data: ::std::vec::Vec, + } + #[doc = "Container type for all return fields from the `aggregate3Value` function with signature `aggregate3Value((address,bool,uint256,bytes)[])` and selector `[23, 77, 234, 113]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct Aggregate3ValueReturn { + pub return_data: ::std::vec::Vec, + } + #[doc = "Container type for all return fields from the `blockAndAggregate` function with signature `blockAndAggregate((address,bytes)[])` and selector `[195, 7, 127, 169]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct BlockAndAggregateReturn { + pub block_number: ethers_core::types::U256, + pub block_hash: [u8; 32], + pub return_data: ::std::vec::Vec, + } + #[doc = "Container type for all return fields from the `getBasefee` function with signature `getBasefee()` and selector `[62, 100, 166, 150]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetBasefeeReturn { + pub basefee: ethers_core::types::U256, + } + #[doc = "Container type for all return fields from the `getBlockHash` function with signature `getBlockHash(uint256)` and selector `[238, 130, 172, 94]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetBlockHashReturn { + pub block_hash: [u8; 32], + } + #[doc = "Container type for all return fields from the `getBlockNumber` function with signature `getBlockNumber()` and selector `[66, 203, 177, 92]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetBlockNumberReturn { + pub block_number: ethers_core::types::U256, + } + #[doc = "Container type for all return fields from the `getChainId` function with signature `getChainId()` and selector `[52, 8, 228, 112]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetChainIdReturn { + pub chainid: ethers_core::types::U256, + } + #[doc = "Container type for all return fields from the `getCurrentBlockCoinbase` function with signature `getCurrentBlockCoinbase()` and selector `[168, 176, 87, 78]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetCurrentBlockCoinbaseReturn { + pub coinbase: ethers_core::types::Address, + } + #[doc = "Container type for all return fields from the `getCurrentBlockDifficulty` function with signature `getCurrentBlockDifficulty()` and selector `[114, 66, 93, 157]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetCurrentBlockDifficultyReturn { + pub difficulty: ethers_core::types::U256, + } + #[doc = "Container type for all return fields from the `getCurrentBlockGasLimit` function with signature `getCurrentBlockGasLimit()` and selector `[134, 213, 22, 232]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetCurrentBlockGasLimitReturn { + pub gaslimit: ethers_core::types::U256, + } + #[doc = "Container type for all return fields from the `getCurrentBlockTimestamp` function with signature `getCurrentBlockTimestamp()` and selector `[15, 40, 201, 125]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetCurrentBlockTimestampReturn { + pub timestamp: ethers_core::types::U256, + } + #[doc = "Container type for all return fields from the `getEthBalance` function with signature `getEthBalance(address)` and selector `[77, 35, 1, 204]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetEthBalanceReturn { + pub balance: ethers_core::types::U256, + } + #[doc = "Container type for all return fields from the `getLastBlockHash` function with signature `getLastBlockHash()` and selector `[39, 232, 109, 110]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct GetLastBlockHashReturn { + pub block_hash: [u8; 32], + } + #[doc = "Container type for all return fields from the `tryAggregate` function with signature `tryAggregate(bool,(address,bytes)[])` and selector `[188, 227, 139, 215]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct TryAggregateReturn { + pub return_data: ::std::vec::Vec, + } + #[doc = "Container type for all return fields from the `tryBlockAndAggregate` function with signature `tryBlockAndAggregate(bool,(address,bytes)[])` and selector `[57, 149, 66, 233]`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct TryBlockAndAggregateReturn { + pub block_number: ethers_core::types::U256, + pub block_hash: [u8; 32], + pub return_data: ::std::vec::Vec, + } + #[doc = "`Call(address,bytes)`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct Call { + pub target: ethers_core::types::Address, + pub call_data: ethers_core::types::Bytes, + } + #[doc = "`Call3(address,bool,bytes)`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct Call3 { + pub target: ethers_core::types::Address, + pub allow_failure: bool, + pub call_data: ethers_core::types::Bytes, + } + #[doc = "`Call3Value(address,bool,uint256,bytes)`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct Call3Value { + pub target: ethers_core::types::Address, + pub allow_failure: bool, + pub value: ethers_core::types::U256, + pub call_data: ethers_core::types::Bytes, + } + #[doc = "`Result(bool,bytes)`"] + #[derive( + Clone, + Debug, + Default, + Eq, + PartialEq, + ethers_contract :: EthAbiType, + ethers_contract :: EthAbiCodec, + )] + pub struct Result { + pub success: bool, + pub return_data: ethers_core::types::Bytes, } } diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs index 6a593c5c..915a17b9 100644 --- a/ethers-contract/tests/it/contract.rs +++ b/ethers-contract/tests/it/contract.rs @@ -6,9 +6,9 @@ use ethers_core::types::{Filter, ValueOrArray, H256}; #[cfg(not(feature = "celo"))] mod eth_tests { use super::*; - use ethers_contract::{LogMeta, Multicall}; + use ethers_contract::{LogMeta, Multicall, MulticallVersion}; use ethers_core::{ - abi::{Detokenize, Token, Tokenizable}, + abi::{encode, Detokenize, Token, Tokenizable}, types::{transaction::eip712::Eip712, Address, BlockId, Bytes, I256, U256}, utils::{keccak256, Anvil}, }; @@ -359,8 +359,8 @@ mod eth_tests { #[tokio::test] async fn multicall_aggregate() { - // get ABI and bytecode for the Multcall contract - let (multicall_abi, multicall_bytecode) = compile_contract("Multicall", "Multicall.sol"); + // get ABI and bytecode for the Multicall contract + let (multicall_abi, multicall_bytecode) = compile_contract("Multicall3", "Multicall.sol"); // get ABI and bytecode for the NotSoSimpleStorage contract 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 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) = 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 // in one go 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 let tx_hash = multicall_send.legacy().send().await.unwrap(); @@ -492,9 +499,9 @@ mod eth_tests { // so should have 100 ETH multicall .clear_calls() - .eth_balance_of(addrs[4]) - .eth_balance_of(addrs[5]) - .eth_balance_of(addrs[6]); + .eth_balance_of(addrs[4], false) + .eth_balance_of(addrs[5], false) + .eth_balance_of(addrs[6], false); let balances: (U256, U256, U256) = multicall.call().await.unwrap(); 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 for i in 0..=16 { let call = simple_contract.method::<_, String>("getValue", ()).unwrap(); - multicall.add_call(call); + multicall.add_call(call, false); } // must use `call_raw` as `.calls` > 16 @@ -531,6 +538,182 @@ mod eth_tests { }) .collect(); 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] diff --git a/ethers-contract/tests/solidity-contracts/Multicall.sol b/ethers-contract/tests/solidity-contracts/Multicall.sol index 7e6cd973..2088dcbd 100644 --- a/ethers-contract/tests/solidity-contracts/Multicall.sol +++ b/ethers-contract/tests/solidity-contracts/Multicall.sol @@ -1,45 +1,259 @@ -pragma solidity >=0.5.0; +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0; 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 /// @author Joshua Levine /// @author Nick Johnson - -contract Multicall { +/// @author Andreas Bigger +/// @author Matt Solomon +contract Multicall3 { struct Call { address target; 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; - returnData = new bytes[](calls.length); - for(uint256 i = 0; i < calls.length; i++) { - (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); - require(success); - returnData[i] = ret; + uint256 length = calls.length; + returnData = new bytes[](length); + Call calldata call; + for (uint256 i = 0; i < length; ) { + 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) { - balance = addr.balance; + + /// @notice Backwards-compatible with Multicall2 + /// @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) { blockHash = blockhash(blockNumber); } - function getLastBlockHash() public view returns (bytes32 blockHash) { - blockHash = blockhash(block.number - 1); - } - function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { - 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 number + function getBlockNumber() public view returns (uint256 blockNumber) { + blockNumber = block.number; } + + /// @notice Returns the block coinbase function getCurrentBlockCoinbase() public view returns (address 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; + } } diff --git a/ethers-contract/tests/solidity-contracts/SimpleRevertingStorage.sol b/ethers-contract/tests/solidity-contracts/SimpleRevertingStorage.sol new file mode 100644 index 00000000..81f5ec44 --- /dev/null +++ b/ethers-contract/tests/solidity-contracts/SimpleRevertingStorage.sol @@ -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); + } +}