alloy_contract/
error.rs

1use alloy_dyn_abi::Error as AbiError;
2use alloy_primitives::{Bytes, Selector};
3use alloy_provider::PendingTransactionError;
4use alloy_sol_types::{SolError, SolInterface};
5use alloy_transport::TransportError;
6use thiserror::Error;
7
8/// Dynamic contract result type.
9pub type Result<T, E = Error> = core::result::Result<T, E>;
10
11/// Error when interacting with contracts.
12#[derive(Debug, Error)]
13pub enum Error {
14    /// Unknown function referenced.
15    #[error("unknown function: function {0} does not exist")]
16    UnknownFunction(String),
17    /// Unknown function selector referenced.
18    #[error("unknown function: function with selector {0} does not exist")]
19    UnknownSelector(Selector),
20    /// Called `deploy` with a transaction that is not a deployment transaction.
21    #[error("transaction is not a deployment transaction")]
22    NotADeploymentTransaction,
23    /// `contractAddress` was not found in the deployment transaction’s receipt.
24    #[error("missing `contractAddress` from deployment transaction receipt")]
25    ContractNotDeployed,
26    /// The contract returned no data.
27    #[error("contract call to `{0}` returned no data (\"0x\"); the called address might not be a contract")]
28    ZeroData(String, #[source] AbiError),
29    /// An error occurred ABI encoding or decoding.
30    #[error(transparent)]
31    AbiError(#[from] AbiError),
32    /// An error occurred interacting with a contract over RPC.
33    #[error(transparent)]
34    TransportError(#[from] TransportError),
35    /// An error occurred while waiting for a pending transaction.
36    #[error(transparent)]
37    PendingTransactionError(#[from] PendingTransactionError),
38}
39
40impl From<alloy_sol_types::Error> for Error {
41    #[inline]
42    fn from(e: alloy_sol_types::Error) -> Self {
43        Self::AbiError(e.into())
44    }
45}
46
47impl Error {
48    #[cold]
49    pub(crate) fn decode(name: &str, data: &[u8], error: AbiError) -> Self {
50        if data.is_empty() {
51            let name = name.split('(').next().unwrap_or(name);
52            return Self::ZeroData(name.to_string(), error);
53        }
54        Self::AbiError(error)
55    }
56
57    /// Return the revert data in case the call reverted.
58    pub fn as_revert_data(&self) -> Option<Bytes> {
59        if let Self::TransportError(e) = self {
60            return e.as_error_resp().and_then(|e| e.as_revert_data());
61        }
62
63        None
64    }
65
66    /// Attempts to decode the revert data into one of the custom errors in [`SolInterface`].
67    ///
68    /// Returns an enum container type consisting of the custom errors defined in the interface.
69    ///
70    /// None is returned if the revert data is empty or if the data could not be decoded into one of
71    /// the custom errors defined in the interface.
72    ///
73    /// ## Example
74    ///
75    /// ```no_run
76    /// use alloy_provider::ProviderBuilder;
77    /// use alloy_sol_types::sol;
78    ///
79    /// sol! {
80    ///     #[derive(Debug, PartialEq, Eq)]
81    ///     #[sol(rpc, bytecode = "694207")]
82    ///     contract ThrowsError {
83    ///         error SomeCustomError(uint64 a);
84    ///         error AnotherError(uint64 b);
85    ///
86    ///         function error(uint64 a) external {
87    ///             revert SomeCustomError(a);
88    ///         }
89    ///     }
90    /// }
91    ///
92    /// #[tokio::main]
93    /// async fn main() {
94    ///     let provider = ProviderBuilder::new().connect_anvil_with_wallet();
95    ///
96    ///     let throws_err = ThrowsError::deploy(provider).await.unwrap();
97    ///
98    ///     let err = throws_err.error(42).call().await.unwrap_err();
99    ///
100    ///     let custom_err =
101    ///         err.as_decoded_interface_error::<ThrowsError::ThrowsErrorErrors>().unwrap();
102    ///
103    ///     // Handle the custom error enum
104    ///     match custom_err {
105    ///         ThrowsError::ThrowsErrorErrors::SomeCustomError(a) => { /* handle error */ }
106    ///         ThrowsError::ThrowsErrorErrors::AnotherError(b) => { /* handle error */ }
107    ///     }
108    /// }
109    /// ```
110    pub fn as_decoded_interface_error<E: SolInterface>(&self) -> Option<E> {
111        self.as_revert_data().and_then(|data| E::abi_decode(&data).ok())
112    }
113
114    /// Decode the revert data into a custom [`SolError`] type.
115    ///
116    /// Returns an instance of the custom error type if decoding was successful, otherwise None.
117    ///
118    /// ## Example
119    ///
120    /// ```no_run
121    /// use alloy_provider::ProviderBuilder;
122    /// use alloy_sol_types::sol;
123    /// use ThrowsError::SomeCustomError;
124    /// sol! {
125    ///     #[derive(Debug, PartialEq, Eq)]
126    ///     #[sol(rpc, bytecode = "694207")]
127    ///     contract ThrowsError {
128    ///         error SomeCustomError(uint64 a);
129    ///         error AnotherError(uint64 b);
130    ///
131    ///         function error(uint64 a) external {
132    ///             revert SomeCustomError(a);
133    ///         }
134    ///     }
135    /// }
136    ///
137    /// #[tokio::main]
138    /// async fn main() {
139    ///     let provider = ProviderBuilder::new().connect_anvil_with_wallet();
140    ///
141    ///     let throws_err = ThrowsError::deploy(provider).await.unwrap();
142    ///
143    ///     let err = throws_err.error(42).call().await.unwrap_err();
144    ///
145    ///     let custom_err = err.as_decoded_error::<SomeCustomError>().unwrap();
146    ///
147    ///     assert_eq!(custom_err, SomeCustomError { a: 42 });
148    /// }
149    /// ```
150    pub fn as_decoded_error<E: SolError>(&self) -> Option<E> {
151        self.as_revert_data().and_then(|data| E::abi_decode(&data).ok())
152    }
153}