Skip to main content

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::{RpcError, TransportError, TransportErrorKind};
6use serde_json::value::RawValue;
7use thiserror::Error;
8
9/// Dynamic contract result type.
10pub type Result<T, E = Error> = core::result::Result<T, E>;
11
12/// Error when interacting with contracts.
13#[derive(Debug, Error)]
14pub enum Error {
15    /// Unknown function referenced.
16    #[error("unknown function: function {0} does not exist")]
17    UnknownFunction(String),
18    /// Unknown function selector referenced.
19    #[error("unknown function: function with selector {0} does not exist")]
20    UnknownSelector(Selector),
21    /// Called `deploy` with a transaction that is not a deployment transaction.
22    #[error("transaction is not a deployment transaction")]
23    NotADeploymentTransaction,
24    /// Contract was not deployed: either the deployment transaction reverted or `contractAddress`
25    /// was not found in the receipt.
26    #[error("contract not deployed: deployment transaction failed")]
27    ContractNotDeployed,
28    /// The contract returned no data.
29    #[error("contract call to `{0}` returned no data (\"0x\"); the called address might not be a contract")]
30    ZeroData(String, #[source] AbiError),
31    /// An error occurred ABI encoding or decoding.
32    #[error(transparent)]
33    AbiError(#[from] AbiError),
34    /// An error occurred interacting with a contract over RPC.
35    #[error(transparent)]
36    TransportError(#[from] TransportError),
37    /// An error occurred while waiting for a pending transaction.
38    #[error(transparent)]
39    PendingTransactionError(#[from] PendingTransactionError),
40}
41
42impl From<alloy_sol_types::Error> for Error {
43    #[inline]
44    fn from(e: alloy_sol_types::Error) -> Self {
45        Self::AbiError(e.into())
46    }
47}
48
49impl Error {
50    #[cold]
51    pub(crate) fn decode(name: &str, data: &[u8], error: AbiError) -> Self {
52        if data.is_empty() {
53            let name = name.split('(').next().unwrap_or(name);
54            return Self::ZeroData(name.to_string(), error);
55        }
56        Self::AbiError(error)
57    }
58
59    /// Return the revert data in case the call reverted.
60    pub fn as_revert_data(&self) -> Option<Bytes> {
61        if let Self::TransportError(e) = self {
62            return e.as_error_resp().and_then(|e| e.as_revert_data());
63        }
64
65        None
66    }
67
68    /// Attempts to decode the revert data into one of the custom errors in [`SolInterface`].
69    ///
70    /// Returns an enum container type consisting of the custom errors defined in the interface.
71    ///
72    /// None is returned if the revert data is empty or if the data could not be decoded into one of
73    /// the custom errors defined in the interface.
74    ///
75    /// # Examples
76    ///
77    /// ```no_run
78    /// use alloy_provider::ProviderBuilder;
79    /// use alloy_sol_types::sol;
80    ///
81    /// sol! {
82    ///     #[derive(Debug, PartialEq, Eq)]
83    ///     #[sol(rpc, bytecode = "694207")]
84    ///     contract ThrowsError {
85    ///         error SomeCustomError(uint64 a);
86    ///         error AnotherError(uint64 b);
87    ///
88    ///         function error(uint64 a) external {
89    ///             revert SomeCustomError(a);
90    ///         }
91    ///     }
92    /// }
93    ///
94    /// #[tokio::main]
95    /// async fn main() {
96    ///     let provider = ProviderBuilder::new().connect_anvil_with_wallet();
97    ///
98    ///     let throws_err = ThrowsError::deploy(provider).await.unwrap();
99    ///
100    ///     let err = throws_err.error(42).call().await.unwrap_err();
101    ///
102    ///     let custom_err =
103    ///         err.as_decoded_interface_error::<ThrowsError::ThrowsErrorErrors>().unwrap();
104    ///
105    ///     // Handle the custom error enum
106    ///     match custom_err {
107    ///         ThrowsError::ThrowsErrorErrors::SomeCustomError(a) => { /* handle error */ }
108    ///         ThrowsError::ThrowsErrorErrors::AnotherError(b) => { /* handle error */ }
109    ///     }
110    /// }
111    /// ```
112    pub fn as_decoded_interface_error<E: SolInterface>(&self) -> Option<E> {
113        self.as_revert_data().and_then(|data| E::abi_decode(&data).ok())
114    }
115
116    /// Try to decode a contract error into a specific Solidity error interface.
117    /// If the error cannot be decoded or it is not a contract error, return the original error.
118    ///
119    /// Example usage:
120    ///
121    /// ```ignore
122    /// sol! {
123    ///    library ErrorLib {
124    ///       error SomeError(uint256 code);
125    ///    }
126    /// }
127    ///
128    /// // call a contract that may return an error with the SomeError interface
129    /// let returndata = match myContract.call().await {
130    ///    Ok(returndata) => returndata,
131    ///    Err(err) => {
132    ///         let decoded_error = err.try_decode_into_interface_error::<ErrorLib::ErrorLibError>()?;
133    ///        // handle the decoded error however you want; for example, return it
134    ///         return Err(decoded_error);
135    ///    },
136    /// }
137    /// ```
138    ///
139    /// See also [`Self::as_decoded_interface_error`] for more details.
140    pub fn try_decode_into_interface_error<I: SolInterface>(self) -> Result<I, Self> {
141        self.as_decoded_interface_error::<I>().ok_or(self)
142    }
143
144    /// Decode the revert data into a custom [`SolError`] type.
145    ///
146    /// Returns an instance of the custom error type if decoding was successful, otherwise None.
147    ///
148    /// # Examples
149    ///
150    /// ```no_run
151    /// use alloy_provider::ProviderBuilder;
152    /// use alloy_sol_types::sol;
153    /// use ThrowsError::SomeCustomError;
154    /// sol! {
155    ///     #[derive(Debug, PartialEq, Eq)]
156    ///     #[sol(rpc, bytecode = "694207")]
157    ///     contract ThrowsError {
158    ///         error SomeCustomError(uint64 a);
159    ///         error AnotherError(uint64 b);
160    ///
161    ///         function error(uint64 a) external {
162    ///             revert SomeCustomError(a);
163    ///         }
164    ///     }
165    /// }
166    ///
167    /// #[tokio::main]
168    /// async fn main() {
169    ///     let provider = ProviderBuilder::new().connect_anvil_with_wallet();
170    ///
171    ///     let throws_err = ThrowsError::deploy(provider).await.unwrap();
172    ///
173    ///     let err = throws_err.error(42).call().await.unwrap_err();
174    ///
175    ///     let custom_err = err.as_decoded_error::<SomeCustomError>().unwrap();
176    ///
177    ///     assert_eq!(custom_err, SomeCustomError { a: 42 });
178    /// }
179    /// ```
180    pub fn as_decoded_error<E: SolError>(&self) -> Option<E> {
181        self.as_revert_data().and_then(|data| E::abi_decode(&data).ok())
182    }
183}
184
185/// The result of trying to parse a transport error into a specific interface.
186#[derive(Debug)]
187pub enum TryParseTransportErrorResult<I: SolInterface> {
188    /// The error was successfully decoded into the specified interface.
189    Decoded(I),
190    /// The error was not decoded but the revert data was extracted.
191    UnknownSelector(Bytes),
192    /// The error was not decoded and the revert data was not extracted.
193    Original(RpcError<TransportErrorKind, Box<RawValue>>),
194}
195
196/// Extension trait for TransportError parsing capabilities
197pub trait TransportErrorExt {
198    /// Attempts to parse a transport error into a specific interface.
199    fn try_parse_transport_error<I: SolInterface>(self) -> TryParseTransportErrorResult<I>;
200}
201
202impl TransportErrorExt for TransportError {
203    fn try_parse_transport_error<I: SolInterface>(self) -> TryParseTransportErrorResult<I> {
204        let revert_data = self.as_error_resp().and_then(|e| e.as_revert_data());
205        if let Some(decoded) =
206            revert_data.as_ref().and_then(|data| I::abi_decode(data.as_ref()).ok())
207        {
208            return TryParseTransportErrorResult::Decoded(decoded);
209        }
210
211        if let Some(decoded) = revert_data {
212            return TryParseTransportErrorResult::UnknownSelector(decoded);
213        }
214        TryParseTransportErrorResult::Original(self)
215    }
216}