1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
use ethers_core::{
    abi::{AbiDecode, AbiEncode, Tokenizable},
    types::Selector,
    utils::id,
};
#[cfg(feature = "providers")]
use ethers_providers::JsonRpcError;
use std::borrow::Cow;

/// A trait for enums unifying [`EthError`] types. This trait is usually used
/// to represent the errors that a specific contract might throw. I.e. all
/// solidity custom errors + revert strings.
///
/// This trait should be accessed via
/// [`crate::ContractError::decode_contract_revert`]. It is generally
/// unnecessary to import this trait into your code.
///
/// # Implementor's Note
///
/// We do not recommend manual implementations of this trait. Instead, use the
/// automatically generated implementation in the [`crate::abigen`] macro
///
/// However, sophisticated users may wish to represent the errors of multiple
/// contracts as a single unified enum. E.g. if your contract calls Uniswap,
/// you may wish to implement this on `pub enum MyContractOrUniswapErrors`.
/// In that case, it should be straightforward to delegate to the inner types.
pub trait ContractRevert: AbiDecode + AbiEncode + Send + Sync {
    /// Decode the error from EVM revert data including an Error selector
    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 {
            // Try with and without a prefix, just in case.
            <Self as AbiDecode>::decode(data)
                .or_else(|_| <Self as AbiDecode>::decode(&data[4..]))
                .ok()
        }
    }

    /// `true` if the selector corresponds to an error that this contract can
    /// revert. False otherwise
    fn valid_selector(selector: Selector) -> bool;
}

/// A helper trait for types that represents a custom error type
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
    #[cfg(feature = "providers")]
    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: &[u8]) -> Option<Self> {
        <Self as AbiDecode>::decode(data).ok()
    }

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

    /// Retrieves the ABI signature for the error
    fn abi_signature() -> Cow<'static, str>;

    /// The selector of the error
    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> {
        // This will return none if selector mismatch.
        <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::*;
        // Example of binary data returned by the contract after reverting with `revert
        // EmptyAddress();`.
        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::*;
        // Example of binary data returned by the contract after reverting with `revert("Multicall3:
        // call failed");`.
        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::*;
        // Example of binary data returned by the contract after reverting with `revert
        // EmptyAddress();`.
        let example_revert: Bytes = "0x7138356f".parse().unwrap();

        <EmptyAddress as EthError>::decode_with_selector(&example_revert).unwrap();
    }

    #[test]
    fn eth_error_decode_string() {
        // Example of binary data returned by the contract after reverting with `revert("Multicall3:
        // call failed");`.
        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");
    }

    /// This is an excerpt from the code generated by `Abigen` for custom errors used by a contract.
    mod error {
        ///Custom Error type `EmptyAddress` with signature `EmptyAddress()` and selector
        /// `0x7138356f`
        #[derive(
            Clone, crate::EthError, crate::EthDisplay, Default, Debug, PartialEq, Eq, Hash,
        )]
        #[etherror(name = "EmptyAddress", abi = "EmptyAddress()")]
        pub struct EmptyAddress;

        ///Custom Error type `CollateralIsZero` with signature `CollateralIsZero()` and selector
        /// `0xb4f18b02`
        #[derive(
            Clone, crate::EthError, crate::EthDisplay, Default, Debug, PartialEq, Eq, Hash,
        )]
        #[etherror(name = "CollateralIsZero", abi = "CollateralIsZero()")]
        pub struct CollateralIsZero;

        ///Container type for all of the contract's custom errors
        #[derive(Clone, crate::EthAbiType, Debug, PartialEq, Eq, Hash)]
        pub enum ExampleContractErrors {
            EmptyAddress(EmptyAddress),
            CollateralIsZero(CollateralIsZero),
            /// The standard solidity revert string, with selector
            /// Error(string) -- 0x08c379a0
            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,
                }
            }
        }
    }
}