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