Expose contract revert errors in the ContractError struct (#2172)
* feature: spelunk for revert errors * feature: bubble up revert to contract error * feature: bubble up reverts to multicall * fix: correctly remove signature when deserializing EthErrors * chore: remove redundant test * chore: clippy * fix: allow empty revert string * docs: add all missing rustdoc for ethers-contract * chore: rustfmt * chore: Changelog * fix: danipope test comment
This commit is contained in:
parent
ee5e3e52c0
commit
2090bf560e
|
@ -302,6 +302,11 @@
|
||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
|
|
||||||
|
- (Breaking) Add `Revert` to `ContractError`. Add `impl EthError for String`.
|
||||||
|
Modify existing `ContractError` variants to prevent accidental improper
|
||||||
|
usage. Change `MulticallError` to use `ContractError::Revert`. Add
|
||||||
|
convenience methods to decode errors from reverts.
|
||||||
|
[#2172](https://github.com/gakonst/ethers-rs/pull/2172)
|
||||||
- (Breaking) Improve Multicall result handling
|
- (Breaking) Improve Multicall result handling
|
||||||
[#2164](https://github.com/gakonst/ethers-rs/pull/2105)
|
[#2164](https://github.com/gakonst/ethers-rs/pull/2105)
|
||||||
- (Breaking) Make `Event` objects generic over borrow & remove lifetime
|
- (Breaking) Make `Event` objects generic over borrow & remove lifetime
|
||||||
|
|
|
@ -159,6 +159,8 @@ impl BaseContract {
|
||||||
decode_function_data(function, bytes, true)
|
decode_function_data(function, bytes, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decode the provided ABI encoded bytes as the output of the provided
|
||||||
|
/// function selector
|
||||||
pub fn decode_output_with_selector<D: Detokenize, T: AsRef<[u8]>>(
|
pub fn decode_output_with_selector<D: Detokenize, T: AsRef<[u8]>>(
|
||||||
&self,
|
&self,
|
||||||
signature: Selector,
|
signature: Selector,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#![allow(clippy::return_self_not_must_use)]
|
#![allow(clippy::return_self_not_must_use)]
|
||||||
|
|
||||||
|
use crate::EthError;
|
||||||
|
|
||||||
use super::base::{decode_function_data, AbiError};
|
use super::base::{decode_function_data, AbiError};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{AbiDecode, AbiEncode, Detokenize, Function, InvalidOutputType, Tokenizable},
|
abi::{AbiDecode, AbiEncode, Detokenize, Function, InvalidOutputType, Tokenizable},
|
||||||
|
@ -11,7 +13,7 @@ use ethers_core::{
|
||||||
};
|
};
|
||||||
use ethers_providers::{
|
use ethers_providers::{
|
||||||
call_raw::{CallBuilder, RawCall},
|
call_raw::{CallBuilder, RawCall},
|
||||||
Middleware, PendingTransaction, ProviderError,
|
JsonRpcError, Middleware, MiddlewareError, PendingTransaction, ProviderError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -54,12 +56,22 @@ pub enum ContractError<M: Middleware> {
|
||||||
DetokenizationError(#[from] InvalidOutputType),
|
DetokenizationError(#[from] InvalidOutputType),
|
||||||
|
|
||||||
/// Thrown when a middleware call fails
|
/// Thrown when a middleware call fails
|
||||||
#[error("{0}")]
|
#[error("{e}")]
|
||||||
MiddlewareError(M::Error),
|
MiddlewareError {
|
||||||
|
/// The underlying error
|
||||||
|
e: M::Error,
|
||||||
|
},
|
||||||
|
|
||||||
/// Thrown when a provider call fails
|
/// Thrown when a provider call fails
|
||||||
#[error("{0}")]
|
#[error("{e}")]
|
||||||
ProviderError(ProviderError),
|
ProviderError {
|
||||||
|
/// The underlying error
|
||||||
|
e: ProviderError,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Contract reverted
|
||||||
|
#[error("Contract call reverted with data: {0}")]
|
||||||
|
Revert(Bytes),
|
||||||
|
|
||||||
/// Thrown during deployment if a constructor argument was passed in the `deploy`
|
/// Thrown during deployment if a constructor argument was passed in the `deploy`
|
||||||
/// call but a constructor was not present in the ABI
|
/// call but a constructor was not present in the ABI
|
||||||
|
@ -72,6 +84,83 @@ pub enum ContractError<M: Middleware> {
|
||||||
ContractNotDeployed,
|
ContractNotDeployed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<M: Middleware> ContractError<M> {
|
||||||
|
/// If this `ContractError` is a revert, this method will retrieve a
|
||||||
|
/// reference to the underlying revert data. This ABI-encoded data could be
|
||||||
|
/// a String, or a custom Solidity error type.
|
||||||
|
///
|
||||||
|
/// ## Returns
|
||||||
|
///
|
||||||
|
/// `None` if the error is not a revert
|
||||||
|
/// `Some(data)` with the revert data, if the error is a revert
|
||||||
|
///
|
||||||
|
/// ## Note
|
||||||
|
///
|
||||||
|
/// To skip this step, consider using [`ContractError::decode_revert`]
|
||||||
|
pub fn as_revert(&self) -> Option<&Bytes> {
|
||||||
|
match self {
|
||||||
|
ContractError::Revert(data) => Some(data),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the error is a revert, false otherwise
|
||||||
|
pub fn is_revert(&self) -> bool {
|
||||||
|
matches!(self, ContractError::Revert(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode revert data into an [`EthError`] type. Returns `None` if
|
||||||
|
/// decoding fails, or if this is not a revert
|
||||||
|
pub fn decode_revert<Err: EthError>(&self) -> Option<Err> {
|
||||||
|
self.as_revert().and_then(|data| Err::decode_with_selector(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a [`MiddlewareError`] to a `ContractError`
|
||||||
|
pub fn from_middleware_error(e: M::Error) -> Self {
|
||||||
|
if let Some(data) = e.as_error_response().and_then(JsonRpcError::as_revert_data) {
|
||||||
|
ContractError::Revert(data)
|
||||||
|
} else {
|
||||||
|
ContractError::MiddlewareError { e }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a `ContractError` to a [`MiddlewareError`] if possible.
|
||||||
|
pub fn as_middleware_error(&self) -> Option<&M::Error> {
|
||||||
|
match self {
|
||||||
|
ContractError::MiddlewareError { e } => Some(e),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the error is a middleware error
|
||||||
|
pub fn is_middleware_error(&self) -> bool {
|
||||||
|
matches!(self, ContractError::MiddlewareError { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a `ContractError` to a [`ProviderError`] if possible.
|
||||||
|
pub fn as_provider_error(&self) -> Option<&ProviderError> {
|
||||||
|
match self {
|
||||||
|
ContractError::ProviderError { e } => Some(e),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the error is a provider error
|
||||||
|
pub fn is_provider_error(&self) -> bool {
|
||||||
|
matches!(self, ContractError::ProviderError { .. })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Middleware> From<ProviderError> for ContractError<M> {
|
||||||
|
fn from(e: ProviderError) -> Self {
|
||||||
|
if let Some(data) = e.as_error_response().and_then(JsonRpcError::as_revert_data) {
|
||||||
|
ContractError::Revert(data)
|
||||||
|
} else {
|
||||||
|
ContractError::ProviderError { e }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `ContractCall` is a [`FunctionCall`] object with an [`std::sync::Arc`] middleware.
|
/// `ContractCall` is a [`FunctionCall`] object with an [`std::sync::Arc`] middleware.
|
||||||
/// This type alias exists to preserve backwards compatibility with
|
/// This type alias exists to preserve backwards compatibility with
|
||||||
/// less-abstract Contracts.
|
/// less-abstract Contracts.
|
||||||
|
@ -177,7 +266,7 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.estimate_gas(&self.tx, self.block)
|
.estimate_gas(&self.tx, self.block)
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)
|
.map_err(ContractError::from_middleware_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries the blockchain via an `eth_call` for the provided transaction.
|
/// Queries the blockchain via an `eth_call` for the provided transaction.
|
||||||
|
@ -190,9 +279,12 @@ where
|
||||||
///
|
///
|
||||||
/// Note: this function _does not_ send a transaction from your account
|
/// Note: this function _does not_ send a transaction from your account
|
||||||
pub async fn call(&self) -> Result<D, ContractError<M>> {
|
pub async fn call(&self) -> Result<D, ContractError<M>> {
|
||||||
let client: &M = self.client.borrow();
|
let bytes = self
|
||||||
let bytes =
|
.client
|
||||||
client.call(&self.tx, self.block).await.map_err(ContractError::MiddlewareError)?;
|
.borrow()
|
||||||
|
.call(&self.tx, self.block)
|
||||||
|
.await
|
||||||
|
.map_err(ContractError::from_middleware_error)?;
|
||||||
|
|
||||||
// decode output
|
// decode output
|
||||||
let data = decode_function_data(&self.function, &bytes, false)?;
|
let data = decode_function_data(&self.function, &bytes, false)?;
|
||||||
|
@ -211,7 +303,7 @@ where
|
||||||
) -> impl RawCall<'_> + Future<Output = Result<D, ContractError<M>>> + Debug {
|
) -> impl RawCall<'_> + Future<Output = Result<D, ContractError<M>>> + Debug {
|
||||||
let call = self.call_raw_bytes();
|
let call = self.call_raw_bytes();
|
||||||
call.map(move |res: Result<Bytes, ProviderError>| {
|
call.map(move |res: Result<Bytes, ProviderError>| {
|
||||||
let bytes = res.map_err(ContractError::ProviderError)?;
|
let bytes = res?;
|
||||||
decode_function_data(&self.function, &bytes, false).map_err(From::from)
|
decode_function_data(&self.function, &bytes, false).map_err(From::from)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -237,7 +329,7 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.send_transaction(self.tx.clone(), self.block)
|
.send_transaction(self.tx.clone(), self.block)
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)
|
.map_err(ContractError::from_middleware_error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{AbiDecode, AbiEncode, Tokenizable},
|
abi::{AbiDecode, AbiEncode, Tokenizable},
|
||||||
types::Selector,
|
types::{Bytes, Selector},
|
||||||
utils::id,
|
utils::id,
|
||||||
};
|
};
|
||||||
|
use ethers_providers::JsonRpcError;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A helper trait for types that represents a custom error type
|
/// A helper trait for types that represents a custom error type
|
||||||
pub trait EthError: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
|
pub trait EthError: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
|
||||||
|
/// Attempt to decode from a [`JsonRpcError`] by extracting revert data
|
||||||
|
///
|
||||||
|
/// Fails if the error is not a revert, or decoding fails
|
||||||
|
fn from_rpc_response(response: &JsonRpcError) -> Option<Self> {
|
||||||
|
Self::decode_with_selector(&response.as_revert_data()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode the error from EVM revert data including an Error selector
|
||||||
|
fn decode_with_selector(data: &Bytes) -> Option<Self> {
|
||||||
|
// This will return none if selector mismatch.
|
||||||
|
<Self as AbiDecode>::decode(data.strip_prefix(&Self::selector())?).ok()
|
||||||
|
}
|
||||||
|
|
||||||
/// The name of the error
|
/// The name of the error
|
||||||
fn error_name() -> Cow<'static, str>;
|
fn error_name() -> Cow<'static, str>;
|
||||||
|
|
||||||
|
@ -18,3 +32,30 @@ pub trait EthError: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
|
||||||
id(Self::abi_signature())
|
id(Self::abi_signature())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EthError for String {
|
||||||
|
fn error_name() -> Cow<'static, str> {
|
||||||
|
Cow::Borrowed("Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn abi_signature() -> Cow<'static, str> {
|
||||||
|
Cow::Borrowed("Error(string)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use ethers_core::types::Bytes;
|
||||||
|
|
||||||
|
use super::EthError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_error() {
|
||||||
|
let multicall_revert_string: Bytes = "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000174d756c746963616c6c333a2063616c6c206661696c6564000000000000000000".parse().unwrap();
|
||||||
|
assert_eq!(String::selector().as_slice(), &multicall_revert_string[0..4]);
|
||||||
|
assert_eq!(
|
||||||
|
String::decode_with_selector(&multicall_revert_string).unwrap().as_str(),
|
||||||
|
"Multicall3: call failed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -192,7 +192,7 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.watch(&self.filter)
|
.watch(&self.filter)
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
.map_err(ContractError::from_middleware_error)?;
|
||||||
Ok(EventStream::new(filter.id, filter, Box::new(move |log| Ok(parse_log(log)?))))
|
Ok(EventStream::new(filter.id, filter, Box::new(move |log| Ok(parse_log(log)?))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.watch(&self.filter)
|
.watch(&self.filter)
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
.map_err(ContractError::from_middleware_error)?;
|
||||||
Ok(EventStream::new(
|
Ok(EventStream::new(
|
||||||
filter.id,
|
filter.id,
|
||||||
filter,
|
filter,
|
||||||
|
@ -243,10 +243,11 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.subscribe_logs(&self.filter)
|
.subscribe_logs(&self.filter)
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
.map_err(ContractError::from_middleware_error)?;
|
||||||
Ok(EventStream::new(filter.id, filter, Box::new(move |log| Ok(parse_log(log)?))))
|
Ok(EventStream::new(filter.id, filter, Box::new(move |log| Ok(parse_log(log)?))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// As [`Self::subscribe`], but includes event metadata
|
||||||
pub async fn subscribe_with_meta(
|
pub async fn subscribe_with_meta(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
|
@ -259,7 +260,7 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.subscribe_logs(&self.filter)
|
.subscribe_logs(&self.filter)
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
.map_err(ContractError::from_middleware_error)?;
|
||||||
Ok(EventStream::new(
|
Ok(EventStream::new(
|
||||||
filter.id,
|
filter.id,
|
||||||
filter,
|
filter,
|
||||||
|
@ -285,7 +286,7 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.get_logs(&self.filter)
|
.get_logs(&self.filter)
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
.map_err(ContractError::from_middleware_error)?;
|
||||||
let events = logs
|
let events = logs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|log| Ok(parse_log(log)?))
|
.map(|log| Ok(parse_log(log)?))
|
||||||
|
@ -301,7 +302,7 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.get_logs(&self.filter)
|
.get_logs(&self.filter)
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
.map_err(ContractError::from_middleware_error)?;
|
||||||
let events = logs
|
let events = logs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|log| {
|
.map(|log| {
|
||||||
|
|
|
@ -79,6 +79,7 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the block at which RPC requests are made
|
||||||
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
self.deployer.block = block.into();
|
self.deployer.block = block.into();
|
||||||
self
|
self
|
||||||
|
@ -222,6 +223,7 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the block at which requests are made
|
||||||
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
self.block = block.into();
|
self.block = block.into();
|
||||||
self
|
self
|
||||||
|
@ -247,7 +249,7 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.call(&self.tx, Some(self.block.into()))
|
.call(&self.tx, Some(self.block.into()))
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
.map_err(ContractError::from_middleware_error)?;
|
||||||
|
|
||||||
// TODO: It would be nice to handle reverts in a structured way.
|
// TODO: It would be nice to handle reverts in a structured way.
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -282,7 +284,7 @@ where
|
||||||
.borrow()
|
.borrow()
|
||||||
.send_transaction(self.tx, Some(self.block.into()))
|
.send_transaction(self.tx, Some(self.block.into()))
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
.map_err(ContractError::from_middleware_error)?;
|
||||||
|
|
||||||
// TODO: Should this be calculated "optimistically" by address/nonce?
|
// TODO: Should this be calculated "optimistically" by address/nonce?
|
||||||
let receipt = pending_tx
|
let receipt = pending_tx
|
||||||
|
@ -382,6 +384,8 @@ where
|
||||||
Self { client, abi, bytecode, _m: PhantomData }
|
Self { client, abi, bytecode, _m: PhantomData }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a deployment tx using the provided tokens as constructor
|
||||||
|
/// arguments
|
||||||
pub fn deploy_tokens(self, params: Vec<Token>) -> Result<Deployer<B, M>, ContractError<M>>
|
pub fn deploy_tokens(self, params: Vec<Token>) -> Result<Deployer<B, M>, ContractError<M>>
|
||||||
where
|
where
|
||||||
B: Clone,
|
B: Clone,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
mod contract;
|
mod contract;
|
||||||
pub use contract::{Contract, ContractInstance};
|
pub use contract::{Contract, ContractInstance};
|
||||||
|
@ -31,8 +32,10 @@ mod multicall;
|
||||||
#[cfg(any(test, feature = "abigen"))]
|
#[cfg(any(test, feature = "abigen"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
||||||
pub use multicall::{
|
pub use multicall::{
|
||||||
contract as multicall_contract, Call, Multicall, MulticallContract, MulticallError,
|
constants::{MULTICALL_ADDRESS, MULTICALL_SUPPORTED_CHAIN_IDS},
|
||||||
MulticallVersion, MULTICALL_ADDRESS, MULTICALL_SUPPORTED_CHAIN_IDS,
|
contract as multicall_contract,
|
||||||
|
error::MulticallError,
|
||||||
|
Call, Multicall, MulticallContract, MulticallVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This module exposes low lever builder structures which are only consumed by the
|
/// This module exposes low lever builder structures which are only consumed by the
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
use ethers_core::types::{Chain, H160};
|
||||||
|
|
||||||
|
/// The Multicall3 contract address that is deployed in [`MULTICALL_SUPPORTED_CHAIN_IDS`]:
|
||||||
|
/// [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11)
|
||||||
|
pub const MULTICALL_ADDRESS: H160 = 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 const MULTICALL_SUPPORTED_CHAIN_IDS: &[u64] = {
|
||||||
|
use Chain::*;
|
||||||
|
&[
|
||||||
|
Mainnet as u64, // Mainnet
|
||||||
|
Kovan as u64, // Kovan
|
||||||
|
Rinkeby as u64, // Rinkeby
|
||||||
|
Goerli as u64, // Görli
|
||||||
|
Ropsten as u64, // Ropsten
|
||||||
|
Sepolia as u64, // Sepolia
|
||||||
|
Optimism as u64, // Optimism
|
||||||
|
OptimismKovan as u64, // Optimism Kovan
|
||||||
|
OptimismGoerli as u64, // Optimism Görli
|
||||||
|
Arbitrum as u64, // Arbitrum
|
||||||
|
ArbitrumNova as u64, // Arbitrum Nova
|
||||||
|
ArbitrumGoerli as u64, // Arbitrum Görli
|
||||||
|
ArbitrumTestnet as u64, // Arbitrum Rinkeby
|
||||||
|
Polygon as u64, // Polygon
|
||||||
|
PolygonMumbai as u64, // Polygon Mumbai
|
||||||
|
XDai as u64, // Gnosis Chain
|
||||||
|
Avalanche as u64, // Avalanche
|
||||||
|
AvalancheFuji as u64, // Avalanche Fuji
|
||||||
|
FantomTestnet as u64, // Fantom Testnet
|
||||||
|
Fantom as u64, // Fantom Opera
|
||||||
|
BinanceSmartChain as u64, // BNB Smart Chain
|
||||||
|
BinanceSmartChainTestnet as u64, // BNB Smart Chain Testnet
|
||||||
|
Moonbeam as u64, // Moonbeam
|
||||||
|
Moonriver as u64, // Moonriver
|
||||||
|
Moonbase as u64, // Moonbase
|
||||||
|
1666600000, // Harmony0
|
||||||
|
1666600001, // Harmony1
|
||||||
|
1666600002, // Harmony2
|
||||||
|
1666600003, // Harmony3
|
||||||
|
Cronos as u64, // Cronos
|
||||||
|
122, // Fuse
|
||||||
|
14, // Flare Mainnet
|
||||||
|
19, // Songbird Canary Network
|
||||||
|
16, // Coston Testnet
|
||||||
|
114, // Coston2 Testnet
|
||||||
|
288, // Boba
|
||||||
|
Aurora as u64, // Aurora
|
||||||
|
592, // Astar
|
||||||
|
66, // OKC
|
||||||
|
128, // Heco Chain
|
||||||
|
1088, // Metis
|
||||||
|
Rsk as u64, // Rsk
|
||||||
|
31, // Rsk Testnet
|
||||||
|
Evmos as u64, // Evmos
|
||||||
|
EvmosTestnet as u64, // Evmos Testnet
|
||||||
|
Oasis as u64, // Oasis
|
||||||
|
42261, // Oasis Emerald ParaTime Testnet
|
||||||
|
42262, // Oasis Emerald ParaTime
|
||||||
|
Celo as u64, // Celo
|
||||||
|
CeloAlfajores as u64, // Celo Alfajores Testnet
|
||||||
|
71402, // Godwoken
|
||||||
|
71401, // Godwoken Testnet
|
||||||
|
8217, // Klaytn
|
||||||
|
2001, // Milkomeda
|
||||||
|
321, // KCC
|
||||||
|
106, // Velas
|
||||||
|
40, // Telos
|
||||||
|
]
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
use ethers_contract_derive::abigen;
|
||||||
|
|
||||||
|
abigen!(Multicall3, "src/multicall/multicall_abi.json");
|
|
@ -0,0 +1,98 @@
|
||||||
|
use ethers_core::{
|
||||||
|
abi::{self, InvalidOutputType},
|
||||||
|
types::Bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
use ethers_providers::{Middleware, ProviderError};
|
||||||
|
|
||||||
|
use crate::{ContractError, EthError};
|
||||||
|
|
||||||
|
/// Errors using the [`crate::Multicall`] system
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum MulticallError<M: Middleware> {
|
||||||
|
/// Contract call returned an error
|
||||||
|
#[error(transparent)]
|
||||||
|
ContractError(#[from] ContractError<M>),
|
||||||
|
|
||||||
|
/// Unsupported chain
|
||||||
|
#[error("Chain ID {0} is currently not supported by Multicall. Provide an address instead.")]
|
||||||
|
InvalidChainId(u64),
|
||||||
|
|
||||||
|
/// Contract call reverted when not allowed
|
||||||
|
#[error("Illegal revert: Multicall2 call reverted when it wasn't allowed to.")]
|
||||||
|
IllegalRevert,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Middleware> From<abi::Error> for MulticallError<M> {
|
||||||
|
fn from(value: abi::Error) -> Self {
|
||||||
|
Self::ContractError(ContractError::DecodingError(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Middleware> From<InvalidOutputType> for MulticallError<M> {
|
||||||
|
fn from(value: InvalidOutputType) -> Self {
|
||||||
|
Self::ContractError(ContractError::DetokenizationError(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Middleware> MulticallError<M> {
|
||||||
|
/// Convert a `MulticallError` to a the underlying error if possible.
|
||||||
|
pub fn as_contract_error(&self) -> Option<&ContractError<M>> {
|
||||||
|
match self {
|
||||||
|
MulticallError::ContractError(e) => Some(e),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the underlying error is a [`ContractError`]
|
||||||
|
pub fn is_contract_error(&self) -> bool {
|
||||||
|
matches!(self, MulticallError::ContractError(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a `MulticallError` to a the underlying error if possible.
|
||||||
|
pub fn as_middleware_error(&self) -> Option<&M::Error> {
|
||||||
|
self.as_contract_error().and_then(ContractError::as_middleware_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the underlying error is a MiddlewareError
|
||||||
|
pub fn is_middleware_error(&self) -> bool {
|
||||||
|
self.as_contract_error().map(ContractError::is_middleware_error).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a `MulticallError` to a [`ProviderError`] if possible.
|
||||||
|
pub fn as_provider_error(&self) -> Option<&ProviderError> {
|
||||||
|
self.as_contract_error().and_then(ContractError::as_provider_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the error is a provider error
|
||||||
|
pub fn is_provider_error(&self) -> bool {
|
||||||
|
self.as_contract_error().map(ContractError::is_provider_error).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this `MulticallError` is a revert, this method will retrieve a
|
||||||
|
/// reference to the underlying revert data. This ABI-encoded data could be
|
||||||
|
/// a String, or a custom Solidity error type.
|
||||||
|
///
|
||||||
|
/// ## Returns
|
||||||
|
///
|
||||||
|
/// `None` if the error is not a revert
|
||||||
|
/// `Some(data)` with the revert data, if the error is a revert
|
||||||
|
///
|
||||||
|
/// ## Note
|
||||||
|
///
|
||||||
|
/// To skip this step, consider using [`MulticallError::decode_revert`]
|
||||||
|
pub fn as_revert(&self) -> Option<&Bytes> {
|
||||||
|
self.as_contract_error().and_then(ContractError::as_revert)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the error is a revert, false otherwise
|
||||||
|
pub fn is_revert(&self) -> bool {
|
||||||
|
self.as_contract_error().map(ContractError::is_revert).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode revert data into an [`EthError`] type. Returns `None` if
|
||||||
|
/// decoding fails, or if this is not a revert
|
||||||
|
pub fn decode_revert<Err: EthError>(&self) -> Option<Err> {
|
||||||
|
self.as_revert().and_then(|data| Err::decode_with_selector(data))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,151 +1,28 @@
|
||||||
use crate::call::{ContractCall, ContractError};
|
use crate::call::{ContractCall, ContractError};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{AbiDecode, Detokenize, Function, InvalidOutputType, Token, Tokenizable},
|
abi::{Detokenize, Function, Token, Tokenizable},
|
||||||
types::{
|
types::{
|
||||||
transaction::eip2718::TypedTransaction, Address, BlockNumber, Bytes, Chain, NameOrAddress,
|
transaction::eip2718::TypedTransaction, Address, BlockNumber, Bytes, NameOrAddress, U256,
|
||||||
H160, U256,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use ethers_providers::{Middleware, PendingTransaction};
|
use ethers_providers::{Middleware, PendingTransaction};
|
||||||
use std::{convert::TryFrom, fmt, result::Result as StdResult, sync::Arc};
|
use std::{convert::TryFrom, fmt, result::Result as StdResult, sync::Arc};
|
||||||
|
|
||||||
/// The Multicall contract bindings. Auto-generated with `abigen`.
|
/// The Multicall contract bindings. Auto-generated with `abigen`.
|
||||||
pub mod contract {
|
pub mod contract;
|
||||||
ethers_contract_derive::abigen!(Multicall3, "src/multicall/multicall_abi.json");
|
|
||||||
}
|
|
||||||
pub use contract::Multicall3 as MulticallContract;
|
pub use contract::Multicall3 as MulticallContract;
|
||||||
use contract::{
|
use contract::{
|
||||||
Call as Multicall1Call, Call3 as Multicall3Call, Call3Value as Multicall3CallValue,
|
Call as Multicall1Call, Call3 as Multicall3Call, Call3Value as Multicall3CallValue,
|
||||||
Result as MulticallResult,
|
Result as MulticallResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The Multicall3 contract address that is deployed in [`MULTICALL_SUPPORTED_CHAIN_IDS`]:
|
pub mod constants;
|
||||||
/// [`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 const MULTICALL_SUPPORTED_CHAIN_IDS: &[u64] = {
|
|
||||||
use Chain::*;
|
|
||||||
&[
|
|
||||||
Mainnet as u64, // Mainnet
|
|
||||||
Kovan as u64, // Kovan
|
|
||||||
Rinkeby as u64, // Rinkeby
|
|
||||||
Goerli as u64, // Görli
|
|
||||||
Ropsten as u64, // Ropsten
|
|
||||||
Sepolia as u64, // Sepolia
|
|
||||||
Optimism as u64, // Optimism
|
|
||||||
OptimismKovan as u64, // Optimism Kovan
|
|
||||||
OptimismGoerli as u64, // Optimism Görli
|
|
||||||
Arbitrum as u64, // Arbitrum
|
|
||||||
ArbitrumNova as u64, // Arbitrum Nova
|
|
||||||
ArbitrumGoerli as u64, // Arbitrum Görli
|
|
||||||
ArbitrumTestnet as u64, // Arbitrum Rinkeby
|
|
||||||
Polygon as u64, // Polygon
|
|
||||||
PolygonMumbai as u64, // Polygon Mumbai
|
|
||||||
XDai as u64, // Gnosis Chain
|
|
||||||
Avalanche as u64, // Avalanche
|
|
||||||
AvalancheFuji as u64, // Avalanche Fuji
|
|
||||||
FantomTestnet as u64, // Fantom Testnet
|
|
||||||
Fantom as u64, // Fantom Opera
|
|
||||||
BinanceSmartChain as u64, // BNB Smart Chain
|
|
||||||
BinanceSmartChainTestnet as u64, // BNB Smart Chain Testnet
|
|
||||||
Moonbeam as u64, // Moonbeam
|
|
||||||
Moonriver as u64, // Moonriver
|
|
||||||
Moonbase as u64, // Moonbase
|
|
||||||
1666600000, // Harmony0
|
|
||||||
1666600001, // Harmony1
|
|
||||||
1666600002, // Harmony2
|
|
||||||
1666600003, // Harmony3
|
|
||||||
Cronos as u64, // Cronos
|
|
||||||
122, // Fuse
|
|
||||||
14, // Flare Mainnet
|
|
||||||
19, // Songbird Canary Network
|
|
||||||
16, // Coston Testnet
|
|
||||||
114, // Coston2 Testnet
|
|
||||||
288, // Boba
|
|
||||||
Aurora as u64, // Aurora
|
|
||||||
592, // Astar
|
|
||||||
66, // OKC
|
|
||||||
128, // Heco Chain
|
|
||||||
1088, // Metis
|
|
||||||
Rsk as u64, // Rsk
|
|
||||||
31, // Rsk Testnet
|
|
||||||
Evmos as u64, // Evmos
|
|
||||||
EvmosTestnet as u64, // Evmos Testnet
|
|
||||||
Oasis as u64, // Oasis
|
|
||||||
42261, // Oasis Emerald ParaTime Testnet
|
|
||||||
42262, // Oasis Emerald ParaTime
|
|
||||||
Celo as u64, // Celo
|
|
||||||
CeloAlfajores as u64, // Celo Alfajores Testnet
|
|
||||||
71402, // Godwoken
|
|
||||||
71401, // Godwoken Testnet
|
|
||||||
8217, // Klaytn
|
|
||||||
2001, // Milkomeda
|
|
||||||
321, // KCC
|
|
||||||
106, // Velas
|
|
||||||
40, // Telos
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Type alias for `Result<T, MulticallError<M>>`
|
/// Type alias for `Result<T, MulticallError<M>>`
|
||||||
pub type Result<T, M> = StdResult<T, MulticallError<M>>;
|
pub type Result<T, M> = StdResult<T, error::MulticallError<M>>;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
/// MultiCall error type
|
||||||
pub enum MulticallError<M: Middleware> {
|
pub mod error;
|
||||||
#[error(transparent)]
|
|
||||||
ContractError(#[from] ContractError<M>),
|
|
||||||
|
|
||||||
#[error("Chain ID {0} is currently not supported by Multicall. Provide an address instead.")]
|
|
||||||
InvalidChainId(u64),
|
|
||||||
|
|
||||||
#[error("Illegal revert: Multicall2 call reverted when it wasn't allowed to.")]
|
|
||||||
IllegalRevert,
|
|
||||||
|
|
||||||
#[error("Call reverted with data: \"{}\"", decode_error(_0))]
|
|
||||||
CallReverted(Bytes),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M: Middleware> From<ethers_core::abi::Error> for MulticallError<M> {
|
|
||||||
fn from(value: ethers_core::abi::Error) -> Self {
|
|
||||||
Self::ContractError(ContractError::DecodingError(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M: Middleware> From<InvalidOutputType> for MulticallError<M> {
|
|
||||||
fn from(value: InvalidOutputType) -> Self {
|
|
||||||
Self::ContractError(ContractError::DetokenizationError(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M: Middleware> MulticallError<M> {
|
|
||||||
pub fn into_bytes(self) -> Result<Bytes, M> {
|
|
||||||
match self {
|
|
||||||
Self::CallReverted(bytes) => Ok(bytes),
|
|
||||||
e => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the bytes that the call reverted with.
|
|
||||||
pub fn get_bytes(&self) -> Option<&Bytes> {
|
|
||||||
match self {
|
|
||||||
Self::CallReverted(bytes) => Some(bytes),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Formats the bytes that the call reverted with.
|
|
||||||
pub fn format_bytes(&self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Self::CallReverted(bytes) => Some(decode_error(bytes)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper struct for managing calls to be made to the `function` in smart contract `target`
|
/// Helper struct for managing calls to be made to the `function` in smart contract `target`
|
||||||
/// with `data`.
|
/// with `data`.
|
||||||
|
@ -171,8 +48,11 @@ pub struct Call {
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum MulticallVersion {
|
pub enum MulticallVersion {
|
||||||
|
/// V1
|
||||||
Multicall = 1,
|
Multicall = 1,
|
||||||
|
/// V2
|
||||||
Multicall2 = 2,
|
Multicall2 = 2,
|
||||||
|
/// V3
|
||||||
#[default]
|
#[default]
|
||||||
Multicall3 = 3,
|
Multicall3 = 3,
|
||||||
}
|
}
|
||||||
|
@ -196,16 +76,19 @@ impl TryFrom<u8> for MulticallVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MulticallVersion {
|
impl MulticallVersion {
|
||||||
|
/// True if call is v1
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_v1(&self) -> bool {
|
pub fn is_v1(&self) -> bool {
|
||||||
matches!(self, Self::Multicall)
|
matches!(self, Self::Multicall)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if call is v2
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_v2(&self) -> bool {
|
pub fn is_v2(&self) -> bool {
|
||||||
matches!(self, Self::Multicall2)
|
matches!(self, Self::Multicall2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if call is v3
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_v3(&self) -> bool {
|
pub fn is_v3(&self) -> bool {
|
||||||
matches!(self, Self::Multicall3)
|
matches!(self, Self::Multicall3)
|
||||||
|
@ -218,7 +101,7 @@ impl MulticallVersion {
|
||||||
///
|
///
|
||||||
/// `Multicall` can be instantiated asynchronously from the chain ID of the provided client using
|
/// `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
|
/// [`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)`.
|
/// [`constants::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).
|
/// A list of all the supported chains is available [`here`](https://github.com/mds1/multicall#multicall3-contract-addresses).
|
||||||
///
|
///
|
||||||
/// Set the contract's version by using [`version`].
|
/// Set the contract's version by using [`version`].
|
||||||
|
@ -356,17 +239,17 @@ impl<M> fmt::Debug for Multicall<M> {
|
||||||
impl<M: Middleware> Multicall<M> {
|
impl<M: Middleware> Multicall<M> {
|
||||||
/// Creates a new Multicall instance from the provided client. If provided with an `address`,
|
/// Creates a new Multicall instance from the provided client. If provided with an `address`,
|
||||||
/// it instantiates the Multicall contract with that address, otherwise it defaults to
|
/// it instantiates the Multicall contract with that address, otherwise it defaults to
|
||||||
/// [`MULTICALL_ADDRESS`].
|
/// [`constants::MULTICALL_ADDRESS`].
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns a [`MulticallError`] if the provider returns an error while getting
|
/// Returns a [`error::MulticallError`] if the provider returns an error while getting
|
||||||
/// `network_version`.
|
/// `network_version`.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// If a `None` address is provided and the client's network is
|
/// If a `None` address is provided and the client's network is
|
||||||
/// [not supported](MULTICALL_SUPPORTED_CHAIN_IDS).
|
/// [not supported](constants::MULTICALL_SUPPORTED_CHAIN_IDS).
|
||||||
pub async fn new(client: impl Into<Arc<M>>, address: Option<Address>) -> Result<Self, M> {
|
pub async fn new(client: impl Into<Arc<M>>, address: Option<Address>) -> Result<Self, M> {
|
||||||
let client = client.into();
|
let client = client.into();
|
||||||
|
|
||||||
|
@ -376,12 +259,15 @@ impl<M: Middleware> Multicall<M> {
|
||||||
let address: Address = match address {
|
let address: Address = match address {
|
||||||
Some(addr) => addr,
|
Some(addr) => addr,
|
||||||
None => {
|
None => {
|
||||||
let chain_id =
|
let chain_id = client
|
||||||
client.get_chainid().await.map_err(ContractError::MiddlewareError)?.as_u64();
|
.get_chainid()
|
||||||
if !MULTICALL_SUPPORTED_CHAIN_IDS.contains(&chain_id) {
|
.await
|
||||||
return Err(MulticallError::InvalidChainId(chain_id))
|
.map_err(ContractError::from_middleware_error)?
|
||||||
|
.as_u64();
|
||||||
|
if !constants::MULTICALL_SUPPORTED_CHAIN_IDS.contains(&chain_id) {
|
||||||
|
return Err(error::MulticallError::InvalidChainId(chain_id))
|
||||||
}
|
}
|
||||||
MULTICALL_ADDRESS
|
constants::MULTICALL_ADDRESS
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -398,12 +284,13 @@ impl<M: Middleware> Multicall<M> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new Multicall instance synchronously from the provided client and address or chain
|
/// 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.
|
/// ID. Uses the [default multicall address](constants::MULTICALL_ADDRESS) if no address is
|
||||||
|
/// provided.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns a [`MulticallError`] if the provided chain_id is not in the
|
/// Returns a [`error::MulticallError`] if the provided chain_id is not in the
|
||||||
/// [supported networks](MULTICALL_SUPPORTED_CHAIN_IDS).
|
/// [supported networks](constants::MULTICALL_SUPPORTED_CHAIN_IDS).
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
|
@ -421,10 +308,10 @@ impl<M: Middleware> Multicall<M> {
|
||||||
(Some(addr), _) => addr,
|
(Some(addr), _) => addr,
|
||||||
(_, Some(chain_id)) => {
|
(_, Some(chain_id)) => {
|
||||||
let chain_id = chain_id.into();
|
let chain_id = chain_id.into();
|
||||||
if !MULTICALL_SUPPORTED_CHAIN_IDS.contains(&chain_id) {
|
if !constants::MULTICALL_SUPPORTED_CHAIN_IDS.contains(&chain_id) {
|
||||||
return Err(MulticallError::InvalidChainId(chain_id))
|
return Err(error::MulticallError::InvalidChainId(chain_id))
|
||||||
}
|
}
|
||||||
MULTICALL_ADDRESS
|
constants::MULTICALL_ADDRESS
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Can't fetch chain_id from provider since we're not in an async function so we
|
// Can't fetch chain_id from provider since we're not in an async function so we
|
||||||
|
@ -460,8 +347,9 @@ impl<M: Middleware> Multicall<M> {
|
||||||
/// to use (so you can fit more calls into a single request), and it adds an aggregate3 method
|
/// 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.
|
/// 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`])
|
/// Note: all these versions are available in the same contract address
|
||||||
/// so changing version just changes the methods used, not the contract address.
|
/// ([`constants::MULTICALL_ADDRESS`]) so changing version just changes the methods used,
|
||||||
|
/// not the contract address.
|
||||||
pub fn version(mut self, version: MulticallVersion) -> Self {
|
pub fn version(mut self, version: MulticallVersion) -> Self {
|
||||||
self.version = version;
|
self.version = version;
|
||||||
self
|
self
|
||||||
|
@ -673,8 +561,8 @@ impl<M: Middleware> Multicall<M> {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing
|
/// Returns a [`error::MulticallError`] if there are any errors in the RPC call or while
|
||||||
/// the tokens back to the expected return type.
|
/// detokenizing the tokens back to the expected return type.
|
||||||
///
|
///
|
||||||
/// Returns an error if any call failed, even if `allow_failure` was set, or if the return data
|
/// Returns an error if any call failed, even if `allow_failure` was set, or if the return data
|
||||||
/// was empty.
|
/// was empty.
|
||||||
|
@ -707,7 +595,11 @@ impl<M: Middleware> Multicall<M> {
|
||||||
let results = self.call_raw().await?;
|
let results = self.call_raw().await?;
|
||||||
let tokens = results
|
let tokens = results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|res| res.map_err(MulticallError::CallReverted))
|
.map(|res| {
|
||||||
|
res.map_err(|data| {
|
||||||
|
error::MulticallError::ContractError(ContractError::Revert(data))
|
||||||
|
})
|
||||||
|
})
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
T::from_token(Token::Tuple(tokens)).map_err(Into::into)
|
T::from_token(Token::Tuple(tokens)).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
@ -717,8 +609,8 @@ impl<M: Middleware> Multicall<M> {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing
|
/// Returns a [`error::MulticallError`] if there are any errors in the RPC call or while
|
||||||
/// the tokens back to the expected return type.
|
/// detokenizing the tokens back to the expected return type.
|
||||||
///
|
///
|
||||||
/// Returns an error if any call failed, even if `allow_failure` was set, or if the return data
|
/// Returns an error if any call failed, even if `allow_failure` was set, or if the return data
|
||||||
/// was empty.
|
/// was empty.
|
||||||
|
@ -747,7 +639,9 @@ impl<M: Middleware> Multicall<M> {
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|res| {
|
.map(|res| {
|
||||||
res.map_err(MulticallError::CallReverted)
|
res.map_err(|data| {
|
||||||
|
error::MulticallError::ContractError(ContractError::Revert(data))
|
||||||
|
})
|
||||||
.and_then(|token| T::from_token(token).map_err(Into::into))
|
.and_then(|token| T::from_token(token).map_err(Into::into))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -763,7 +657,7 @@ impl<M: Middleware> Multicall<M> {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns a [`MulticallError`] if there are any errors in the RPC call.
|
/// Returns a [`error::MulticallError`] if there are any errors in the RPC call.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -824,7 +718,7 @@ impl<M: Middleware> Multicall<M> {
|
||||||
// still do so because of other calls that are in the same multicall
|
// still do so because of other calls that are in the same multicall
|
||||||
// aggregate.
|
// aggregate.
|
||||||
if !success && !call.allow_failure {
|
if !success && !call.allow_failure {
|
||||||
return Err(MulticallError::IllegalRevert)
|
return Err(error::MulticallError::IllegalRevert)
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(return_data)
|
Err(return_data)
|
||||||
|
@ -849,7 +743,7 @@ impl<M: Middleware> Multicall<M> {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns a [`MulticallError`] if there are any errors in the RPC call.
|
/// Returns a [`error::MulticallError`] if there are any errors in the RPC call.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -871,10 +765,9 @@ impl<M: Middleware> Multicall<M> {
|
||||||
MulticallVersion::Multicall3 => self.as_aggregate_3_value().tx,
|
MulticallVersion::Multicall3 => self.as_aggregate_3_value().tx,
|
||||||
};
|
};
|
||||||
let client: &M = self.contract.client_ref();
|
let client: &M = self.contract.client_ref();
|
||||||
client
|
client.send_transaction(tx, self.block.map(Into::into)).await.map_err(|e| {
|
||||||
.send_transaction(tx, self.block.map(Into::into))
|
error::MulticallError::ContractError(ContractError::from_middleware_error(e))
|
||||||
.await
|
})
|
||||||
.map_err(|e| MulticallError::ContractError(ContractError::MiddlewareError(e)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// v1
|
/// v1
|
||||||
|
@ -984,13 +877,3 @@ impl<M: Middleware> Multicall<M> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_error(bytes: &Bytes) -> String {
|
|
||||||
// Try decoding with "Error(string)" (0x08c379a0)
|
|
||||||
if bytes.len() >= 4 && bytes[..4] == [0x08, 0xc3, 0x79, 0xa0] {
|
|
||||||
if let Ok(string) = String::decode(&bytes[4..]) {
|
|
||||||
return string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bytes.to_string()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
//! Contains the `EventStream` type which aids in streaming access to contract
|
||||||
|
//! events
|
||||||
|
|
||||||
use crate::LogMeta;
|
use crate::LogMeta;
|
||||||
use ethers_core::types::{Log, U256};
|
use ethers_core::types::{Log, U256};
|
||||||
use futures_util::{
|
use futures_util::{
|
||||||
|
@ -19,6 +22,7 @@ type MapEvent<'a, R, E> = Box<dyn Fn(Log) -> Result<R, E> + 'a + Send + Sync>;
|
||||||
/// We use this wrapper type instead of `StreamExt::map` in order to preserve
|
/// We use this wrapper type instead of `StreamExt::map` in order to preserve
|
||||||
/// information about the filter/subscription's id.
|
/// information about the filter/subscription's id.
|
||||||
pub struct EventStream<'a, T, R, E> {
|
pub struct EventStream<'a, T, R, E> {
|
||||||
|
/// The stream ID, provided by the RPC server
|
||||||
pub id: U256,
|
pub id: U256,
|
||||||
#[pin]
|
#[pin]
|
||||||
stream: T,
|
stream: T,
|
||||||
|
@ -33,6 +37,9 @@ impl<'a, T, R, E> EventStream<'a, T, R, E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, R, E> EventStream<'a, T, R, E> {
|
impl<'a, T, R, E> EventStream<'a, T, R, E> {
|
||||||
|
/// Instantiate a new `EventStream`
|
||||||
|
///
|
||||||
|
/// Typically users should not call this directly
|
||||||
pub fn new(id: U256, stream: T, parse: MapEvent<'a, R, E>) -> Self {
|
pub fn new(id: U256, stream: T, parse: MapEvent<'a, R, E>) -> Self {
|
||||||
Self { id, stream, parse }
|
Self { id, stream, parse }
|
||||||
}
|
}
|
||||||
|
@ -128,8 +135,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A stream of two items
|
||||||
pub type SelectEither<'a, L, R> = Pin<Box<dyn Stream<Item = Either<L, R>> + 'a>>;
|
pub type SelectEither<'a, L, R> = Pin<Box<dyn Stream<Item = Either<L, R>> + 'a>>;
|
||||||
|
|
||||||
|
/// Stream for [`EventStream::select`]
|
||||||
#[pin_project]
|
#[pin_project]
|
||||||
pub struct SelectEvent<T>(#[pin] T);
|
pub struct SelectEvent<T>(#[pin] T);
|
||||||
|
|
||||||
|
|
|
@ -722,19 +722,24 @@ mod eth_tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// .call reverts
|
// .call reverts
|
||||||
// don't allow revert -> entire call reverts
|
// don't allow revert
|
||||||
multicall.clear_calls().add_call(get_value_reverting_call.clone(), false);
|
multicall
|
||||||
assert!(matches!(
|
.clear_calls()
|
||||||
multicall.call::<(String,)>().await.unwrap_err(),
|
.add_call(get_value_reverting_call.clone(), false)
|
||||||
MulticallError::ContractError(_)
|
.add_call(get_value_call.clone(), false);
|
||||||
));
|
let res = multicall.call::<(String, String)>().await;
|
||||||
|
let err = res.unwrap_err();
|
||||||
|
|
||||||
|
assert!(err.is_revert());
|
||||||
|
let message = err.decode_revert::<String>().unwrap();
|
||||||
|
assert!(message.contains("Multicall3: call failed"));
|
||||||
|
|
||||||
// allow revert -> call doesn't revert, but returns Err(_) in raw tokens
|
// allow revert -> call doesn't revert, but returns Err(_) in raw tokens
|
||||||
let expected = Bytes::from_static(b"getValue revert").encode();
|
let expected = Bytes::from_static(b"getValue revert").encode();
|
||||||
multicall.clear_calls().add_call(get_value_reverting_call.clone(), true);
|
multicall.clear_calls().add_call(get_value_reverting_call.clone(), true);
|
||||||
assert_eq!(multicall.call_raw().await.unwrap()[0].as_ref().unwrap_err()[4..], expected[..]);
|
assert_eq!(multicall.call_raw().await.unwrap()[0].as_ref().unwrap_err()[4..], expected[..]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
multicall.call::<(String,)>().await.unwrap_err().into_bytes().unwrap()[4..],
|
multicall.call::<(String,)>().await.unwrap_err().as_revert().unwrap()[4..],
|
||||||
expected[..]
|
expected[..]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -774,14 +779,14 @@ mod eth_tests {
|
||||||
// empty revert
|
// empty revert
|
||||||
let empty_revert = reverting_contract.method::<_, H256>("emptyRevert", ()).unwrap();
|
let empty_revert = reverting_contract.method::<_, H256>("emptyRevert", ()).unwrap();
|
||||||
multicall.clear_calls().add_call(empty_revert.clone(), true);
|
multicall.clear_calls().add_call(empty_revert.clone(), true);
|
||||||
assert!(multicall.call::<(String,)>().await.unwrap_err().into_bytes().unwrap().is_empty());
|
assert!(multicall.call::<(String,)>().await.unwrap_err().as_revert().unwrap().is_empty());
|
||||||
|
|
||||||
// string revert
|
// string revert
|
||||||
let string_revert =
|
let string_revert =
|
||||||
reverting_contract.method::<_, H256>("stringRevert", ("String".to_string())).unwrap();
|
reverting_contract.method::<_, H256>("stringRevert", ("String".to_string())).unwrap();
|
||||||
multicall.clear_calls().add_call(string_revert, true);
|
multicall.clear_calls().add_call(string_revert, true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
multicall.call::<(String,)>().await.unwrap_err().into_bytes().unwrap()[4..],
|
multicall.call::<(String,)>().await.unwrap_err().as_revert().unwrap()[4..],
|
||||||
Bytes::from_static(b"String").encode()[..]
|
Bytes::from_static(b"String").encode()[..]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -789,7 +794,7 @@ mod eth_tests {
|
||||||
let custom_error = reverting_contract.method::<_, H256>("customError", ()).unwrap();
|
let custom_error = reverting_contract.method::<_, H256>("customError", ()).unwrap();
|
||||||
multicall.clear_calls().add_call(custom_error, true);
|
multicall.clear_calls().add_call(custom_error, true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
multicall.call::<(Bytes,)>().await.unwrap_err().into_bytes().unwrap()[..],
|
multicall.call::<(Bytes,)>().await.unwrap_err().as_revert().unwrap()[..],
|
||||||
keccak256("CustomError()")[..4]
|
keccak256("CustomError()")[..4]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -798,7 +803,8 @@ mod eth_tests {
|
||||||
.method::<_, H256>("customErrorWithData", ("Data".to_string()))
|
.method::<_, H256>("customErrorWithData", ("Data".to_string()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
multicall.clear_calls().add_call(custom_error_with_data, true);
|
multicall.clear_calls().add_call(custom_error_with_data, true);
|
||||||
let bytes = multicall.call::<(Bytes,)>().await.unwrap_err().into_bytes().unwrap();
|
let err = multicall.call::<(Bytes,)>().await.unwrap_err();
|
||||||
|
let bytes = err.as_revert().unwrap();
|
||||||
assert_eq!(bytes[..4], keccak256("CustomErrorWithData(string)")[..4]);
|
assert_eq!(bytes[..4], keccak256("CustomErrorWithData(string)")[..4]);
|
||||||
assert_eq!(bytes[4..], encode(&[Token::String("Data".to_string())]));
|
assert_eq!(bytes[4..], encode(&[Token::String("Data".to_string())]));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(clippy::extra_unused_type_parameters)]
|
// #![allow(clippy::extra_unused_type_parameters)]
|
||||||
|
|
||||||
#[cfg(feature = "abigen")]
|
#[cfg(feature = "abigen")]
|
||||||
mod abigen;
|
mod abigen;
|
||||||
|
|
|
@ -143,7 +143,7 @@ impl Tokenizable for String {
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
match token {
|
match token {
|
||||||
Token::String(s) => Ok(s),
|
Token::String(s) => Ok(s),
|
||||||
other => Err(InvalidOutputType(format!("Expected `String`, got {:?}", other))),
|
other => Err(InvalidOutputType(format!("Expected `String`, got {other:?}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ impl Tokenizable for Bytes {
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
match token {
|
match token {
|
||||||
Token::Bytes(s) => Ok(s.into()),
|
Token::Bytes(s) => Ok(s.into()),
|
||||||
other => Err(InvalidOutputType(format!("Expected `Bytes`, got {:?}", other))),
|
other => Err(InvalidOutputType(format!("Expected `Bytes`, got {other:?}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ impl Tokenizable for bytes::Bytes {
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
match token {
|
match token {
|
||||||
Token::Bytes(s) => Ok(s.into()),
|
Token::Bytes(s) => Ok(s.into()),
|
||||||
other => Err(InvalidOutputType(format!("Expected `Bytes`, got {:?}", other))),
|
other => Err(InvalidOutputType(format!("Expected `Bytes`, got {other:?}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ impl Tokenizable for H256 {
|
||||||
match token {
|
match token {
|
||||||
Token::FixedBytes(mut s) => {
|
Token::FixedBytes(mut s) => {
|
||||||
if s.len() != 32 {
|
if s.len() != 32 {
|
||||||
return Err(InvalidOutputType(format!("Expected `H256`, got {:?}", s)))
|
return Err(InvalidOutputType(format!("Expected `H256`, got {s:?}")))
|
||||||
}
|
}
|
||||||
let mut data = [0; 32];
|
let mut data = [0; 32];
|
||||||
for (idx, val) in s.drain(..).enumerate() {
|
for (idx, val) in s.drain(..).enumerate() {
|
||||||
|
@ -191,7 +191,7 @@ impl Tokenizable for H256 {
|
||||||
}
|
}
|
||||||
Ok(data.into())
|
Ok(data.into())
|
||||||
}
|
}
|
||||||
other => Err(InvalidOutputType(format!("Expected `H256`, got {:?}", other))),
|
other => Err(InvalidOutputType(format!("Expected `H256`, got {other:?}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ impl Tokenizable for Address {
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
match token {
|
match token {
|
||||||
Token::Address(data) => Ok(data),
|
Token::Address(data) => Ok(data),
|
||||||
other => Err(InvalidOutputType(format!("Expected `Address`, got {:?}", other))),
|
other => Err(InvalidOutputType(format!("Expected `Address`, got {other:?}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ impl Tokenizable for bool {
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||||
match token {
|
match token {
|
||||||
Token::Bool(data) => Ok(data),
|
Token::Bool(data) => Ok(data),
|
||||||
other => Err(InvalidOutputType(format!("Expected `bool`, got {:?}", other))),
|
other => Err(InvalidOutputType(format!("Expected `bool`, got {other:?}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn into_token(self) -> Token {
|
fn into_token(self) -> Token {
|
||||||
|
@ -298,7 +298,7 @@ impl Tokenizable for Vec<u8> {
|
||||||
Token::Bytes(data) => Ok(data),
|
Token::Bytes(data) => Ok(data),
|
||||||
Token::Array(data) => data.into_iter().map(u8::from_token).collect(),
|
Token::Array(data) => data.into_iter().map(u8::from_token).collect(),
|
||||||
Token::FixedBytes(data) => Ok(data),
|
Token::FixedBytes(data) => Ok(data),
|
||||||
other => Err(InvalidOutputType(format!("Expected `bytes`, got {:?}", other))),
|
other => Err(InvalidOutputType(format!("Expected `bytes`, got {other:?}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,7 +313,7 @@ impl<T: TokenizableItem> Tokenizable for Vec<T> {
|
||||||
Token::FixedArray(tokens) | Token::Array(tokens) => {
|
Token::FixedArray(tokens) | Token::Array(tokens) => {
|
||||||
tokens.into_iter().map(Tokenizable::from_token).collect()
|
tokens.into_iter().map(Tokenizable::from_token).collect()
|
||||||
}
|
}
|
||||||
other => Err(InvalidOutputType(format!("Expected `Array`, got {:?}", other))),
|
other => Err(InvalidOutputType(format!("Expected `Array`, got {other:?}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,9 +338,7 @@ impl<const N: usize> Tokenizable for [u8; N] {
|
||||||
arr.copy_from_slice(&bytes);
|
arr.copy_from_slice(&bytes);
|
||||||
Ok(arr)
|
Ok(arr)
|
||||||
}
|
}
|
||||||
other => {
|
other => Err(InvalidOutputType(format!("Expected `FixedBytes({N})`, got {other:?}"))),
|
||||||
Err(InvalidOutputType(format!("Expected `FixedBytes({})`, got {:?}", N, other)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,9 +370,7 @@ impl<T: TokenizableItem + Clone, const N: usize> Tokenizable for [T; N] {
|
||||||
Err(_) => panic!("All elements inserted so the array is full; qed"),
|
Err(_) => panic!("All elements inserted so the array is full; qed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
other => {
|
other => Err(InvalidOutputType(format!("Expected `FixedArray({N})`, got {other:?}"))),
|
||||||
Err(InvalidOutputType(format!("Expected `FixedArray({})`, got {:?}", N, other)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -483,6 +479,7 @@ mod tests {
|
||||||
|
|
||||||
let _tuple: (Address, Vec<Vec<u8>>) = assert_detokenize();
|
let _tuple: (Address, Vec<Vec<u8>>) = assert_detokenize();
|
||||||
let _vec_of_tuple: Vec<(Address, String)> = assert_detokenize();
|
let _vec_of_tuple: Vec<(Address, String)> = assert_detokenize();
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
let _vec_of_tuple_5: Vec<(Address, Vec<Vec<u8>>, String, U256, bool)> = assert_detokenize();
|
let _vec_of_tuple_5: Vec<(Address, Vec<Vec<u8>>, String, U256, bool)> = assert_detokenize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,12 +539,12 @@ mod tests {
|
||||||
assert_eq!(data.0[1], 2);
|
assert_eq!(data.0[1], 2);
|
||||||
assert_eq!(data.0[2], 3);
|
assert_eq!(data.0[2], 3);
|
||||||
assert_eq!(data.0[3], 4);
|
assert_eq!(data.0[3], 4);
|
||||||
assert_eq!(data.1, true);
|
assert!(data.1);
|
||||||
|
|
||||||
// handle vector of more than one elements
|
// handle vector of more than one elements
|
||||||
let tokens = vec![Token::Bool(false), Token::Uint(U256::from(13u8))];
|
let tokens = vec![Token::Bool(false), Token::Uint(U256::from(13u8))];
|
||||||
let data: (bool, u8) = Detokenize::from_tokens(tokens).unwrap();
|
let data: (bool, u8) = Detokenize::from_tokens(tokens).unwrap();
|
||||||
assert_eq!(data.0, false);
|
assert!(!data.0);
|
||||||
assert_eq!(data.1, 13u8);
|
assert_eq!(data.1, 13u8);
|
||||||
|
|
||||||
// handle more than two tuples
|
// handle more than two tuples
|
||||||
|
@ -559,8 +556,8 @@ mod tests {
|
||||||
assert_eq!((data.0).0[1], 2);
|
assert_eq!((data.0).0[1], 2);
|
||||||
assert_eq!((data.0).0[2], 3);
|
assert_eq!((data.0).0[2], 3);
|
||||||
assert_eq!((data.0).0[3], 4);
|
assert_eq!((data.0).0[3], 4);
|
||||||
assert_eq!((data.0).1, true);
|
assert!((data.0).1);
|
||||||
assert_eq!((data.1).0, false);
|
assert!(!(data.1).0);
|
||||||
assert_eq!((data.1).1, 13u8);
|
assert_eq!((data.1).1, 13u8);
|
||||||
|
|
||||||
// error if no tokens in the vector
|
// error if no tokens in the vector
|
||||||
|
|
|
@ -95,7 +95,7 @@ impl DsProxy {
|
||||||
Some(addr) => addr,
|
Some(addr) => addr,
|
||||||
None => {
|
None => {
|
||||||
let chain_id =
|
let chain_id =
|
||||||
client.get_chainid().await.map_err(ContractError::MiddlewareError)?;
|
client.get_chainid().await.map_err(ContractError::from_middleware_error)?;
|
||||||
match ADDRESS_BOOK.get(&chain_id) {
|
match ADDRESS_BOOK.get(&chain_id) {
|
||||||
Some(addr) => *addr,
|
Some(addr) => *addr,
|
||||||
None => panic!(
|
None => panic!(
|
||||||
|
@ -112,8 +112,7 @@ impl DsProxy {
|
||||||
.legacy()
|
.legacy()
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.await
|
.await?
|
||||||
.map_err(ContractError::ProviderError)?
|
|
||||||
.ok_or(ContractError::ContractNotDeployed)?;
|
.ok_or(ContractError::ContractNotDeployed)?;
|
||||||
|
|
||||||
// decode the event log to get the address of the deployed contract.
|
// decode the event log to get the address of the deployed contract.
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
// Code adapted from: https://github.com/althea-net/guac_rs/tree/master/web3/src/jsonrpc
|
// Code adapted from: https://github.com/althea-net/guac_rs/tree/master/web3/src/jsonrpc
|
||||||
|
|
||||||
use base64::{engine::general_purpose, Engine};
|
use base64::{engine::general_purpose, Engine};
|
||||||
use ethers_core::types::U256;
|
use ethers_core::{
|
||||||
|
abi::AbiDecode,
|
||||||
|
types::{Bytes, U256},
|
||||||
|
};
|
||||||
use serde::{
|
use serde::{
|
||||||
de::{self, MapAccess, Unexpected, Visitor},
|
de::{self, MapAccess, Unexpected, Visitor},
|
||||||
Deserialize, Serialize,
|
Deserialize, Serialize,
|
||||||
|
@ -21,6 +24,48 @@ pub struct JsonRpcError {
|
||||||
pub data: Option<Value>,
|
pub data: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively traverses the value, looking for hex data that it can extract
|
||||||
|
/// Inspired by ethers-js logic:
|
||||||
|
/// https://github.com/ethers-io/ethers.js/blob/9f990c57f0486728902d4b8e049536f2bb3487ee/packages/providers/src.ts/json-rpc-provider.ts#L25-L53
|
||||||
|
fn spelunk_revert(value: &Value) -> Option<Bytes> {
|
||||||
|
match value {
|
||||||
|
Value::String(s) => s.parse().ok(),
|
||||||
|
Value::Object(o) => o.values().flat_map(spelunk_revert).next(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsonRpcError {
|
||||||
|
/// Determine if the error output of the `eth_call` RPC request is a revert
|
||||||
|
///
|
||||||
|
/// Note that this may return false positives if called on an error from
|
||||||
|
/// other RPC requests
|
||||||
|
pub fn is_revert(&self) -> bool {
|
||||||
|
// Ganache says "revert" not "reverted"
|
||||||
|
self.message.contains("revert")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to extract revert data from the JsonRpcError be recursively
|
||||||
|
/// traversing the error's data field
|
||||||
|
///
|
||||||
|
/// This returns the first hex it finds in the data object, and its
|
||||||
|
/// behavior may change with `serde_json` internal changes.
|
||||||
|
///
|
||||||
|
/// If no hex object is found, it will return an empty bytes IFF the error
|
||||||
|
/// is a revert
|
||||||
|
///
|
||||||
|
/// Inspired by ethers-js logic:
|
||||||
|
/// <https://github.com/ethers-io/ethers.js/blob/9f990c57f0486728902d4b8e049536f2bb3487ee/packages/providers/src.ts/json-rpc-provider.ts#L25-L53>
|
||||||
|
pub fn as_revert_data(&self) -> Option<Bytes> {
|
||||||
|
self.is_revert().then(|| self.data.as_ref().and_then(spelunk_revert).unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode revert data (if any) into a decodeable type
|
||||||
|
pub fn decode_revert_data<E: AbiDecode>(&self) -> Option<E> {
|
||||||
|
E::decode(&self.as_revert_data()?).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for JsonRpcError {
|
impl fmt::Display for JsonRpcError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "(code: {}, message: {}, data: {:?})", self.code, self.message, self.data)
|
write!(f, "(code: {}, message: {}, data: {:?})", self.code, self.message, self.data)
|
||||||
|
|
|
@ -4,7 +4,6 @@ use ethers::{
|
||||||
};
|
};
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio;
|
|
||||||
|
|
||||||
const HTTP_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
const HTTP_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
||||||
const V3FACTORY_ADDRESS: &str = "0x1F98431c8aD98523631AE4a59f267346ea31F984";
|
const V3FACTORY_ADDRESS: &str = "0x1F98431c8aD98523631AE4a59f267346ea31F984";
|
||||||
|
@ -40,8 +39,7 @@ async fn main() -> Result<()> {
|
||||||
let tick_spacing = U256::from_big_endian(&log.data[29..32]);
|
let tick_spacing = U256::from_big_endian(&log.data[29..32]);
|
||||||
let pool = Address::from(&log.data[44..64].try_into()?);
|
let pool = Address::from(&log.data[44..64].try_into()?);
|
||||||
println!(
|
println!(
|
||||||
"pool = {}, token0 = {}, token1 = {}, fee = {}, spacing = {}",
|
"pool = {pool}, token0 = {token0}, token1 = {token1}, fee = {fee_tier}, spacing = {tick_spacing}"
|
||||||
pool, token0, token1, fee_tier, tick_spacing,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue