use ethers_core::{
abi::{AbiDecode, AbiEncode, Tokenizable},
types::Selector,
utils::id,
};
#[cfg(feature = "providers")]
use ethers_providers::JsonRpcError;
use std::borrow::Cow;
pub trait ContractRevert: AbiDecode + AbiEncode + Send + Sync {
fn decode_with_selector(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None
}
let selector = data[..4].try_into().expect("checked by len");
if !Self::valid_selector(selector) {
return None
}
if selector == String::selector() {
<Self as AbiDecode>::decode(&data[4..]).ok()
} else {
<Self as AbiDecode>::decode(data)
.or_else(|_| <Self as AbiDecode>::decode(&data[4..]))
.ok()
}
}
fn valid_selector(selector: Selector) -> bool;
}
pub trait EthError: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
#[cfg(feature = "providers")]
fn from_rpc_response(response: &JsonRpcError) -> Option<Self> {
Self::decode_with_selector(&response.as_revert_data()?)
}
fn decode_with_selector(data: &[u8]) -> Option<Self> {
<Self as AbiDecode>::decode(data).ok()
}
fn error_name() -> Cow<'static, str>;
fn abi_signature() -> Cow<'static, str>;
fn selector() -> Selector {
id(Self::abi_signature())
}
}
impl EthError for String {
fn error_name() -> Cow<'static, str> {
Cow::Borrowed("Error")
}
fn decode_with_selector(data: &[u8]) -> Option<Self> {
<Self as AbiDecode>::decode(data.strip_prefix(&Self::selector())?).ok()
}
fn abi_signature() -> Cow<'static, str> {
Cow::Borrowed("Error(string)")
}
fn selector() -> Selector {
[0x08, 0xc3, 0x79, 0xa0]
}
}
#[cfg(all(test, feature = "abigen"))]
mod test {
use ethers_core::types::Bytes;
use crate::ContractRevert;
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"
);
}
#[test]
fn custom_error() {
use error::*;
let example_revert: Bytes = "0x7138356f".parse().unwrap();
let selector: [u8; 4] = example_revert.to_vec().try_into().unwrap();
assert!(ExampleContractErrors::valid_selector(selector), "selector is valid");
let e = ExampleContractErrors::decode_with_selector(&example_revert)
.expect("failed to decode revert");
assert_eq!(e, ExampleContractErrors::EmptyAddress(EmptyAddress));
}
#[test]
fn custom_error_string() {
use error::*;
let example_revert: Bytes = "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000174d756c746963616c6c333a2063616c6c206661696c6564000000000000000000".parse().unwrap();
let e = ExampleContractErrors::decode_with_selector(&example_revert)
.expect("failed to decode revert string");
assert_eq!(e, ExampleContractErrors::RevertString("Multicall3: call failed".into()));
}
#[test]
fn eth_error_decode() {
use error::*;
let example_revert: Bytes = "0x7138356f".parse().unwrap();
<EmptyAddress as EthError>::decode_with_selector(&example_revert).unwrap();
}
#[test]
fn eth_error_decode_string() {
let example_revert: Bytes = "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000174d756c746963616c6c333a2063616c6c206661696c6564000000000000000000".parse().unwrap();
let revert_string = <String as EthError>::decode_with_selector(&example_revert).unwrap();
assert_eq!(revert_string, "Multicall3: call failed");
}
mod error {
#[derive(
Clone, crate::EthError, crate::EthDisplay, Default, Debug, PartialEq, Eq, Hash,
)]
#[etherror(name = "EmptyAddress", abi = "EmptyAddress()")]
pub struct EmptyAddress;
#[derive(
Clone, crate::EthError, crate::EthDisplay, Default, Debug, PartialEq, Eq, Hash,
)]
#[etherror(name = "CollateralIsZero", abi = "CollateralIsZero()")]
pub struct CollateralIsZero;
#[derive(Clone, crate::EthAbiType, Debug, PartialEq, Eq, Hash)]
pub enum ExampleContractErrors {
EmptyAddress(EmptyAddress),
CollateralIsZero(CollateralIsZero),
RevertString(::std::string::String),
}
impl ::ethers::core::abi::AbiDecode for ExampleContractErrors {
fn decode(
data: impl AsRef<[u8]>,
) -> ::core::result::Result<Self, ::ethers::core::abi::AbiError> {
let data = data.as_ref();
if let Ok(decoded) =
<::std::string::String as ::ethers::core::abi::AbiDecode>::decode(data)
{
return Ok(Self::RevertString(decoded))
}
if let Ok(decoded) = <EmptyAddress as ::ethers::core::abi::AbiDecode>::decode(data)
{
return Ok(Self::EmptyAddress(decoded))
}
if let Ok(decoded) =
<CollateralIsZero as ::ethers::core::abi::AbiDecode>::decode(data)
{
return Ok(Self::CollateralIsZero(decoded))
}
Err(::ethers::core::abi::Error::InvalidData.into())
}
}
impl ::ethers::core::abi::AbiEncode for ExampleContractErrors {
fn encode(self) -> ::std::vec::Vec<u8> {
unimplemented!()
}
}
impl crate::ContractRevert for ExampleContractErrors {
fn valid_selector(selector: [u8; 4]) -> bool {
match selector {
[0x08, 0xc3, 0x79, 0xa0] => true,
_ if selector == <EmptyAddress as crate::EthError>::selector() => true,
_ if selector == <CollateralIsZero as crate::EthError>::selector() => true,
_ => false,
}
}
}
}
}