Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
feature: bubble up revert to contract error
Browse files Browse the repository at this point in the history
  • Loading branch information
prestwich committed Feb 21, 2023
1 parent 2e6900c commit 3a8a8dc
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 23 deletions.
52 changes: 43 additions & 9 deletions ethers-contract/src/call.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![allow(clippy::return_self_not_must_use)]

use crate::EthError;

use super::base::{decode_function_data, AbiError};
use ethers_core::{
abi::{AbiDecode, AbiEncode, Detokenize, Function, InvalidOutputType, Tokenizable},
Expand All @@ -11,7 +13,7 @@ use ethers_core::{
};
use ethers_providers::{
call_raw::{CallBuilder, RawCall},
Middleware, PendingTransaction, ProviderError,
JsonRpcError, Middleware, MiddlewareError, PendingTransaction, ProviderError,
};

use std::{
Expand Down Expand Up @@ -54,12 +56,16 @@ pub enum ContractError<M: Middleware> {
DetokenizationError(#[from] InvalidOutputType),

/// Thrown when a middleware call fails
#[error("{0}")]
MiddlewareError(M::Error),
#[error("{e}")]
MiddlewareError { e: M::Error },

/// Thrown when a provider call fails
#[error("{0}")]
ProviderError(ProviderError),
#[error("{e}")]
ProviderError { e: ProviderError },

/// Contract reverted
#[error("Contract reverted with data: {0}")]
Revert(Bytes),

/// Thrown during deployment if a constructor argument was passed in the `deploy`
/// call but a constructor was not present in the ABI
Expand All @@ -72,6 +78,34 @@ pub enum ContractError<M: Middleware> {
ContractNotDeployed,
}

impl<M: Middleware> ContractError<M> {
pub fn decode_revert<Err: EthError>(&self) -> Option<Err> {
match self {
ContractError::Revert(data) => Err::decode(data).ok(),
_ => None,
}
}

/// Convert a Middleware Error 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 }
}
}
}

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.
/// This type alias exists to preserve backwards compatibility with
/// less-abstract Contracts.
Expand Down Expand Up @@ -177,7 +211,7 @@ where
.borrow()
.estimate_gas(&self.tx, self.block)
.await
.map_err(ContractError::MiddlewareError)
.map_err(ContractError::from_middleware_error)
}

/// Queries the blockchain via an `eth_call` for the provided transaction.
Expand All @@ -195,7 +229,7 @@ where
.borrow()
.call(&self.tx, self.block)
.await
.map_err(ContractError::MiddlewareError)?;
.map_err(ContractError::from_middleware_error)?;

// decode output
let data = decode_function_data(&self.function, &bytes, false)?;
Expand All @@ -214,7 +248,7 @@ where
) -> impl RawCall<'_> + Future<Output = Result<D, ContractError<M>>> + Debug {
let call = self.call_raw_bytes();
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)
})
}
Expand All @@ -240,7 +274,7 @@ where
.borrow()
.send_transaction(self.tx.clone(), self.block)
.await
.map_err(ContractError::MiddlewareError)
.map_err(ContractError::from_middleware_error)
}
}

Expand Down
8 changes: 8 additions & 0 deletions ethers-contract/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ use ethers_core::{
types::Selector,
utils::id,
};
use ethers_providers::JsonRpcError;
use std::borrow::Cow;

/// A helper trait for types that represents a custom error type
pub trait EthError: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
fn from_rpc_response(response: &JsonRpcError) -> Option<Self>
where
Self: Sized,
{
response.as_revert_data().and_then(|data| Self::decode(data).ok())
}

/// The name of the error
fn error_name() -> Cow<'static, str>;

Expand Down
12 changes: 6 additions & 6 deletions ethers-contract/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ where
.borrow()
.watch(&self.filter)
.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)?))))
}

Expand All @@ -209,7 +209,7 @@ where
.borrow()
.watch(&self.filter)
.await
.map_err(ContractError::MiddlewareError)?;
.map_err(ContractError::from_middleware_error)?;
Ok(EventStream::new(
filter.id,
filter,
Expand Down Expand Up @@ -243,7 +243,7 @@ where
.borrow()
.subscribe_logs(&self.filter)
.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)?))))
}

Expand All @@ -259,7 +259,7 @@ where
.borrow()
.subscribe_logs(&self.filter)
.await
.map_err(ContractError::MiddlewareError)?;
.map_err(ContractError::from_middleware_error)?;
Ok(EventStream::new(
filter.id,
filter,
Expand All @@ -285,7 +285,7 @@ where
.borrow()
.get_logs(&self.filter)
.await
.map_err(ContractError::MiddlewareError)?;
.map_err(ContractError::from_middleware_error)?;
let events = logs
.into_iter()
.map(|log| Ok(parse_log(log)?))
Expand All @@ -301,7 +301,7 @@ where
.borrow()
.get_logs(&self.filter)
.await
.map_err(ContractError::MiddlewareError)?;
.map_err(ContractError::from_middleware_error)?;
let events = logs
.into_iter()
.map(|log| {
Expand Down
4 changes: 2 additions & 2 deletions ethers-contract/src/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ where
.borrow()
.call(&self.tx, Some(self.block.into()))
.await
.map_err(ContractError::MiddlewareError)?;
.map_err(ContractError::from_middleware_error)?;

// TODO: It would be nice to handle reverts in a structured way.
Ok(())
Expand Down Expand Up @@ -282,7 +282,7 @@ where
.borrow()
.send_transaction(self.tx, Some(self.block.into()))
.await
.map_err(ContractError::MiddlewareError)?;
.map_err(ContractError::from_middleware_error)?;

// TODO: Should this be calculated "optimistically" by address/nonce?
let receipt = pending_tx
Expand Down
9 changes: 6 additions & 3 deletions ethers-contract/src/multicall/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,11 @@ impl<M: Middleware> Multicall<M> {
let address: Address = match address {
Some(addr) => addr,
None => {
let chain_id =
client.get_chainid().await.map_err(ContractError::MiddlewareError)?.as_u64();
let chain_id = client
.get_chainid()
.await
.map_err(ContractError::from_middleware_error)?
.as_u64();
if !MULTICALL_SUPPORTED_CHAIN_IDS.contains(&chain_id) {
return Err(MulticallError::InvalidChainId(chain_id))
}
Expand Down Expand Up @@ -842,7 +845,7 @@ impl<M: Middleware> Multicall<M> {
.client_ref()
.send_transaction(tx, self.block.map(Into::into))
.await
.map_err(|e| MulticallError::ContractError(ContractError::MiddlewareError(e)))
.map_err(|e| MulticallError::ContractError(ContractError::from_middleware_error(e)))
}

/// v1
Expand Down
5 changes: 2 additions & 3 deletions ethers-middleware/src/transformer/ds_proxy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl DsProxy {
Some(addr) => addr,
None => {
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) {
Some(addr) => *addr,
None => panic!(
Expand All @@ -112,8 +112,7 @@ impl DsProxy {
.legacy()
.send()
.await?
.await
.map_err(ContractError::ProviderError)?
.await?
.ok_or(ContractError::ContractNotDeployed)?;

// decode the event log to get the address of the deployed contract.
Expand Down

0 comments on commit 3a8a8dc

Please sign in to comment.