2020-07-03 16:52:09 +00:00
use ethers_core ::{
abi ::{ Detokenize , Function , Token } ,
2021-10-24 18:41:50 +00:00
types ::{ Address , BlockNumber , Bytes , Chain , NameOrAddress , TxHash , U256 } ,
2020-07-03 16:52:09 +00:00
} ;
2020-09-24 21:33:09 +00:00
use ethers_providers ::Middleware ;
2020-07-03 16:52:09 +00:00
use std ::{ collections ::HashMap , str ::FromStr , sync ::Arc } ;
use crate ::{
call ::{ ContractCall , ContractError } ,
Lazy ,
} ;
mod multicall_contract ;
use multicall_contract ::MulticallContract ;
/// 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 < HashMap < U256 , Address > > = Lazy ::new ( | | {
2021-10-24 18:41:50 +00:00
fn decode_address ( input : & str ) -> Address {
Address ::from_str ( input ) . expect ( " Decoding failed " )
}
[
(
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 " ) ,
) ,
]
. into ( )
2020-07-03 16:52:09 +00:00
} ) ;
/// 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.
///
/// `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).
///
/// 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.
///
/// # Example
///
/// ```no_run
2021-08-28 21:06:29 +00:00
/// use ethers_core::{
2020-07-03 16:52:09 +00:00
/// abi::Abi,
/// types::{Address, H256, U256},
/// };
2021-08-28 21:06:29 +00:00
/// use ethers_contract::{Contract, Multicall};
/// use ethers_providers::{Middleware, Http, Provider, PendingTransaction};
2020-07-03 16:52:09 +00:00
/// use std::{convert::TryFrom, sync::Arc};
///
/// # async fn bar() -> Result<(), Box<dyn std::error::Error>> {
/// // this is a dummy address used for illustration purpose
/// let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
///
/// // (ugly way to write the ABI inline, you can otherwise read it from a file)
/// let abi: Abi = serde_json::from_str(r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#)?;
///
/// // connect to the network
2020-09-24 21:33:09 +00:00
/// let client = Provider::<Http>::try_from("https://kovan.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27")?;
2020-07-03 16:52:09 +00:00
///
/// // create the contract object. This will be used to construct the calls for multicall
2020-08-18 18:47:56 +00:00
/// let client = Arc::new(client);
2020-09-24 21:33:09 +00:00
/// let contract = Contract::<Provider<Http>>::new(address, abi, Arc::clone(&client));
2020-07-03 16:52:09 +00:00
///
/// // 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
/// // 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
2020-08-21 12:54:23 +00:00
/// let mut multicall = Multicall::new(Arc::clone(&client), None).await?;
/// multicall
2020-07-03 16:52:09 +00:00
/// .add_call(first_call)
/// .add_call(second_call);
///
/// // `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?;
///
/// // 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
/// .clear_calls()
/// .add_call(first_broadcast)
/// .add_call(second_broadcast);
///
/// // `await`ing the `send` method waits for the transaction to be broadcast, which also
/// // returns the transaction hash
/// let tx_hash = multicall.send().await?;
2020-12-17 11:26:01 +00:00
/// let _tx_receipt = PendingTransaction::new(tx_hash, &client).await?;
2020-07-03 16:52:09 +00:00
///
/// // you can also query ETH balances of multiple addresses
/// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::<Address>()?;
/// let address_2 = "ffffffffffffffffffffffffffffffffffffffff".parse::<Address>()?;
/// let multicall = multicall
/// .clear_calls()
/// .eth_balance_of(address_1)
/// .eth_balance_of(address_2);
/// let _balances: (U256, U256) = multicall.call().await?;
/// # Ok(())
/// # }
/// ```
///
/// [`new`]: method@crate::Multicall::new
/// [`block`]: method@crate::Multicall::block
/// [`add_call`]: methond@crate::Multicall::add_call
#[ derive(Clone) ]
2020-09-24 21:33:09 +00:00
pub struct Multicall < M > {
2020-07-03 16:52:09 +00:00
calls : Vec < Call > ,
block : Option < BlockNumber > ,
2020-09-24 21:33:09 +00:00
contract : MulticallContract < M > ,
2021-08-09 00:50:38 +00:00
legacy : bool ,
2020-07-03 16:52:09 +00:00
}
#[ derive(Clone) ]
/// Helper struct for managing calls to be made to the `function` in smart contract `target`
/// with `data`
pub struct Call {
target : Address ,
2020-12-31 17:19:14 +00:00
data : Bytes ,
2020-07-03 16:52:09 +00:00
function : Function ,
}
2020-09-24 21:33:09 +00:00
impl < M : Middleware > Multicall < M > {
2020-07-03 16:52:09 +00:00
/// 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.
///
/// # 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)
2020-09-24 21:33:09 +00:00
pub async fn new < C : Into < Arc < M > > > (
2020-07-03 16:52:09 +00:00
client : C ,
address : Option < Address > ,
2020-09-24 21:33:09 +00:00
) -> Result < Self , ContractError < M > > {
2020-07-03 16:52:09 +00:00
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
let address : Address = match address {
Some ( addr ) = > addr ,
None = > {
2020-09-24 21:33:09 +00:00
let chain_id = client
. get_chainid ( )
. await
. map_err ( ContractError ::MiddlewareError ) ? ;
2020-07-03 16:52:09 +00:00
match ADDRESS_BOOK . get ( & chain_id ) {
Some ( addr ) = > * addr ,
None = > panic! (
" Must either be a supported Network ID or provide Multicall contract address "
) ,
}
}
} ;
// Instantiate the multicall contract
let contract = MulticallContract ::new ( address , client ) ;
Ok ( Self {
calls : vec ! [ ] ,
block : None ,
contract ,
2021-08-09 00:50:38 +00:00
legacy : false ,
2020-07-03 16:52:09 +00:00
} )
}
2021-08-09 00:50:38 +00:00
/// Makes a legacy transaction instead of an EIP-1559 one
pub fn legacy ( mut self ) -> Self {
self . legacy = true ;
self
}
2020-07-03 16:52:09 +00:00
/// Sets the `block` field for the multicall aggregate call
pub fn block < T : Into < BlockNumber > > ( mut self , block : T ) -> Self {
self . block = Some ( block . into ( ) ) ;
self
}
/// Appends a `call` to the list of calls for the Multicall instance
///
/// # Panics
///
/// If more than the maximum number of supported calls are added. The maximum
/// limits is constrained due to tokenization/detokenization support for tuples
2020-09-24 21:33:09 +00:00
pub fn add_call < D : Detokenize > ( & mut self , call : ContractCall < M , D > ) -> & mut Self {
2021-10-16 16:45:42 +00:00
assert! (
! ( self . calls . len ( ) > = 16 ) ,
" Cannot support more than {} calls " ,
16
) ;
2020-07-03 16:52:09 +00:00
2021-08-09 00:50:38 +00:00
match ( call . tx . to ( ) , call . tx . data ( ) ) {
2020-07-03 16:52:09 +00:00
( Some ( NameOrAddress ::Address ( target ) ) , Some ( data ) ) = > {
let call = Call {
2021-08-09 00:50:38 +00:00
target : * target ,
data : data . clone ( ) ,
2020-07-03 16:52:09 +00:00
function : call . function ,
} ;
self . calls . push ( call ) ;
self
}
_ = > self ,
}
}
/// Appends a `call` to the list of calls for the Multicall instance for querying
/// the ETH balance of an address
///
/// # Panics
///
/// If more than the maximum number of supported calls are added. The maximum
/// limits is constrained due to tokenization/detokenization support for tuples
2020-08-21 12:54:23 +00:00
pub fn eth_balance_of ( & mut self , addr : Address ) -> & mut Self {
2020-07-03 16:52:09 +00:00
let call = self . contract . get_eth_balance ( addr ) ;
self . add_call ( call )
}
/// 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
///
/// ```no_run
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
2021-08-28 21:06:29 +00:00
/// # use ethers_core::{abi::Abi, types::{Address, H256}};
/// # use ethers_providers::{Provider, Http};
/// # use ethers_contract::{Multicall, Contract};
2020-07-03 16:52:09 +00:00
/// # use std::{sync::Arc, convert::TryFrom};
/// #
2020-09-24 21:33:09 +00:00
/// # let client = Provider::<Http>::try_from("http://localhost:8545")?;
2020-07-03 16:52:09 +00:00
/// # let client = Arc::new(client);
/// #
2020-10-27 10:57:01 +00:00
/// # let abi: Abi = serde_json::from_str("")?;
2020-07-03 16:52:09 +00:00
/// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
2020-09-24 21:33:09 +00:00
/// # let contract = Contract::<Provider<Http>>::new(address, abi, client.clone());
2020-07-03 16:52:09 +00:00
/// #
/// # let broadcast_1 = contract.method::<_, H256>("setValue", "some value".to_owned())?;
/// # let broadcast_2 = contract.method::<_, H256>("setValue", "new value".to_owned())?;
/// #
2020-08-21 12:54:23 +00:00
/// let mut multicall = Multicall::new(client, None).await?;
/// multicall
2020-07-03 16:52:09 +00:00
/// .add_call(broadcast_1)
/// .add_call(broadcast_2);
///
/// let _tx_hash = multicall.send().await?;
///
/// # let call_1 = contract.method::<_, String>("getValue", ())?;
/// # let call_2 = contract.method::<_, Address>("lastSender", ())?;
2020-08-21 12:54:23 +00:00
/// multicall
2020-07-03 16:52:09 +00:00
/// .clear_calls()
/// .add_call(call_1)
/// .add_call(call_2);
/// let return_data: (String, Address) = multicall.call().await?;
/// # Ok(())
/// # }
/// ```
2020-08-21 12:54:23 +00:00
pub fn clear_calls ( & mut self ) -> & mut Self {
2020-07-03 16:52:09 +00:00
self . calls . clear ( ) ;
self
}
/// Queries the Ethereum blockchain via an `eth_call`, but via the Multicall contract.
///
2020-09-24 21:33:09 +00:00
/// It returns a [`ContractError<M>`] if there is any error in the RPC call or while
2020-07-03 16:52:09 +00:00
/// detokenizing the tokens back to the expected return type. The return type must be
/// annonated while calling this method.
///
/// ```no_run
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
2021-08-28 21:06:29 +00:00
/// # use ethers_core::types::{U256, Address};
/// # use ethers_providers::{Provider, Http};
/// # use ethers_contract::Multicall;
2020-07-03 16:52:09 +00:00
/// # use std::convert::TryFrom;
/// #
2020-09-24 21:33:09 +00:00
/// # let client = Provider::<Http>::try_from("http://localhost:8545")?;
2020-07-03 16:52:09 +00:00
/// #
/// # let multicall = Multicall::new(client, None).await?;
/// // If the Solidity function calls has the following return types:
/// // 1. `returns (uint256)`
/// // 2. `returns (string, address)`
/// // 3. `returns (bool)`
/// let result: (U256, (String, Address), bool) = multicall.call().await?;
/// # Ok(())
/// # }
/// ```
///
/// Note: this method _does not_ send a transaction from your account
///
2020-09-24 21:33:09 +00:00
/// [`ContractError<M>`]: crate::ContractError<M>
pub async fn call < D : Detokenize > ( & self ) -> Result < D , ContractError < M > > {
2020-07-03 16:52:09 +00:00
let contract_call = self . as_contract_call ( ) ;
// Fetch response from the Multicall contract
let ( _block_number , return_data ) = contract_call . call ( ) . await ? ;
// Decode return data into ABI tokens
let tokens = self
. calls
. iter ( )
. zip ( & return_data )
. map ( | ( call , bytes ) | {
2021-07-24 18:53:40 +00:00
let mut tokens : Vec < Token > = call . function . decode_output ( bytes ) ? ;
2020-07-03 16:52:09 +00:00
Ok ( match tokens . len ( ) {
0 = > Token ::Tuple ( vec! [ ] ) ,
2021-03-18 05:31:49 +00:00
1 = > tokens . remove ( 0 ) ,
2020-07-03 16:52:09 +00:00
_ = > Token ::Tuple ( tokens ) ,
} )
} )
2020-09-24 21:33:09 +00:00
. collect ::< Result < Vec < Token > , ContractError < M > > > ( ) ? ;
2020-07-03 16:52:09 +00:00
// Form tokens that represent tuples
let tokens = vec! [ Token ::Tuple ( tokens ) ] ;
// Detokenize from the tokens into the provided tuple D
let data = D ::from_tokens ( tokens ) ? ;
Ok ( data )
}
/// Signs and broadcasts a batch of transactions by using the Multicall contract as proxy.
///
/// ```no_run
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
2021-08-28 21:06:29 +00:00
/// # use ethers_providers::{Provider, Http};
/// # use ethers_contract::Multicall;
2020-07-03 16:52:09 +00:00
/// # use std::convert::TryFrom;
2020-09-24 21:33:09 +00:00
/// # let client = Provider::<Http>::try_from("http://localhost:8545")?;
2020-07-03 16:52:09 +00:00
/// # let multicall = Multicall::new(client, None).await?;
/// let tx_hash = multicall.send().await?;
/// # 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
2020-09-24 21:33:09 +00:00
pub async fn send ( & self ) -> Result < TxHash , ContractError < M > > {
2020-07-03 16:52:09 +00:00
let contract_call = self . as_contract_call ( ) ;
// Broadcast transaction and return the transaction hash
2020-12-17 11:26:01 +00:00
// 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 ? ;
2020-07-03 16:52:09 +00:00
Ok ( tx_hash )
}
2020-09-24 21:33:09 +00:00
fn as_contract_call ( & self ) -> ContractCall < M , ( U256 , Vec < Vec < u8 > > ) > {
2020-07-03 16:52:09 +00:00
// Map the Multicall struct into appropriate types for `aggregate` function
let calls : Vec < ( Address , Vec < u8 > ) > = self
. calls
. iter ( )
2020-12-31 17:19:14 +00:00
. map ( | call | ( call . target , call . data . to_vec ( ) ) )
2020-07-03 16:52:09 +00:00
. collect ( ) ;
// Construct the ContractCall for `aggregate` function to broadcast the transaction
2021-08-09 00:50:38 +00:00
let mut contract_call = self . contract . aggregate ( calls ) ;
2020-07-03 16:52:09 +00:00
if let Some ( block ) = self . block {
2021-08-09 00:50:38 +00:00
contract_call = contract_call . block ( block )
} ;
if self . legacy {
contract_call = contract_call . legacy ( ) ;
} ;
contract_call
2020-07-03 16:52:09 +00:00
}
}