alloy_contract/error.rs
1use alloy_dyn_abi::Error as AbiError;
2use alloy_primitives::{Bytes, Selector};
3use alloy_provider::{MulticallError, 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 From<MulticallError> for Error {
50 fn from(error: MulticallError) -> Self {
51 match error {
52 MulticallError::TransportError(error) => Self::TransportError(error),
53 MulticallError::DecodeError(error) => error.into(),
54 error => Self::TransportError(TransportErrorKind::custom_str(&error.to_string())),
55 }
56 }
57}
58
59impl Error {
60 #[cold]
61 pub(crate) fn decode(name: &str, data: &[u8], error: AbiError) -> Self {
62 if data.is_empty() {
63 let name = name.split('(').next().unwrap_or(name);
64 return Self::ZeroData(name.to_string(), error);
65 }
66 Self::AbiError(error)
67 }
68
69 /// Return the revert data in case the call reverted.
70 pub fn as_revert_data(&self) -> Option<Bytes> {
71 if let Self::TransportError(e) = self {
72 return e.as_error_resp().and_then(|e| e.as_revert_data());
73 }
74
75 None
76 }
77
78 /// Attempts to decode the revert data into one of the custom errors in [`SolInterface`].
79 ///
80 /// Returns an enum container type consisting of the custom errors defined in the interface.
81 ///
82 /// None is returned if the revert data is empty or if the data could not be decoded into one of
83 /// the custom errors defined in the interface.
84 ///
85 /// # Examples
86 ///
87 /// ```no_run
88 /// use alloy_provider::ProviderBuilder;
89 /// use alloy_sol_types::sol;
90 ///
91 /// sol! {
92 /// #[derive(Debug, PartialEq, Eq)]
93 /// #[sol(rpc, bytecode = "694207")]
94 /// contract ThrowsError {
95 /// error SomeCustomError(uint64 a);
96 /// error AnotherError(uint64 b);
97 ///
98 /// function error(uint64 a) external {
99 /// revert SomeCustomError(a);
100 /// }
101 /// }
102 /// }
103 ///
104 /// #[tokio::main]
105 /// async fn main() {
106 /// let provider = ProviderBuilder::new().connect_anvil_with_wallet();
107 ///
108 /// let throws_err = ThrowsError::deploy(provider).await.unwrap();
109 ///
110 /// let err = throws_err.error(42).call().await.unwrap_err();
111 ///
112 /// let custom_err =
113 /// err.as_decoded_interface_error::<ThrowsError::ThrowsErrorErrors>().unwrap();
114 ///
115 /// // Handle the custom error enum
116 /// match custom_err {
117 /// ThrowsError::ThrowsErrorErrors::SomeCustomError(a) => { /* handle error */ }
118 /// ThrowsError::ThrowsErrorErrors::AnotherError(b) => { /* handle error */ }
119 /// }
120 /// }
121 /// ```
122 pub fn as_decoded_interface_error<E: SolInterface>(&self) -> Option<E> {
123 self.as_revert_data().and_then(|data| E::abi_decode(&data).ok())
124 }
125
126 /// Try to decode a contract error into a specific Solidity error interface.
127 /// If the error cannot be decoded or it is not a contract error, return the original error.
128 ///
129 /// Example usage:
130 ///
131 /// ```ignore
132 /// sol! {
133 /// library ErrorLib {
134 /// error SomeError(uint256 code);
135 /// }
136 /// }
137 ///
138 /// // call a contract that may return an error with the SomeError interface
139 /// let returndata = match myContract.call().await {
140 /// Ok(returndata) => returndata,
141 /// Err(err) => {
142 /// let decoded_error = err.try_decode_into_interface_error::<ErrorLib::ErrorLibError>()?;
143 /// // handle the decoded error however you want; for example, return it
144 /// return Err(decoded_error);
145 /// },
146 /// }
147 /// ```
148 ///
149 /// See also [`Self::as_decoded_interface_error`] for more details.
150 pub fn try_decode_into_interface_error<I: SolInterface>(self) -> Result<I, Self> {
151 self.as_decoded_interface_error::<I>().ok_or(self)
152 }
153
154 /// Decode the revert data into a custom [`SolError`] type.
155 ///
156 /// Returns an instance of the custom error type if decoding was successful, otherwise None.
157 ///
158 /// # Examples
159 ///
160 /// ```no_run
161 /// use alloy_provider::ProviderBuilder;
162 /// use alloy_sol_types::sol;
163 /// use ThrowsError::SomeCustomError;
164 /// sol! {
165 /// #[derive(Debug, PartialEq, Eq)]
166 /// #[sol(rpc, bytecode = "694207")]
167 /// contract ThrowsError {
168 /// error SomeCustomError(uint64 a);
169 /// error AnotherError(uint64 b);
170 ///
171 /// function error(uint64 a) external {
172 /// revert SomeCustomError(a);
173 /// }
174 /// }
175 /// }
176 ///
177 /// #[tokio::main]
178 /// async fn main() {
179 /// let provider = ProviderBuilder::new().connect_anvil_with_wallet();
180 ///
181 /// let throws_err = ThrowsError::deploy(provider).await.unwrap();
182 ///
183 /// let err = throws_err.error(42).call().await.unwrap_err();
184 ///
185 /// let custom_err = err.as_decoded_error::<SomeCustomError>().unwrap();
186 ///
187 /// assert_eq!(custom_err, SomeCustomError { a: 42 });
188 /// }
189 /// ```
190 pub fn as_decoded_error<E: SolError>(&self) -> Option<E> {
191 self.as_revert_data().and_then(|data| E::abi_decode(&data).ok())
192 }
193}
194
195/// The result of trying to parse a transport error into a specific interface.
196#[derive(Debug)]
197pub enum TryParseTransportErrorResult<I: SolInterface> {
198 /// The error was successfully decoded into the specified interface.
199 Decoded(I),
200 /// The error was not decoded but the revert data was extracted.
201 UnknownSelector(Bytes),
202 /// The error was not decoded and the revert data was not extracted.
203 Original(RpcError<TransportErrorKind, Box<RawValue>>),
204}
205
206/// Extension trait for TransportError parsing capabilities
207pub trait TransportErrorExt {
208 /// Attempts to parse a transport error into a specific interface.
209 fn try_parse_transport_error<I: SolInterface>(self) -> TryParseTransportErrorResult<I>;
210}
211
212impl TransportErrorExt for TransportError {
213 fn try_parse_transport_error<I: SolInterface>(self) -> TryParseTransportErrorResult<I> {
214 let revert_data = self.as_error_resp().and_then(|e| e.as_revert_data());
215 if let Some(decoded) =
216 revert_data.as_ref().and_then(|data| I::abi_decode(data.as_ref()).ok())
217 {
218 return TryParseTransportErrorResult::Decoded(decoded);
219 }
220
221 if let Some(decoded) = revert_data {
222 return TryParseTransportErrorResult::UnknownSelector(decoded);
223 }
224 TryParseTransportErrorResult::Original(self)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn converts_multicall_transport_error() {
234 let error = MulticallError::TransportError(TransportErrorKind::backend_gone());
235
236 assert!(matches!(Error::from(error), Error::TransportError(_)));
237 }
238
239 #[test]
240 fn converts_multicall_decode_error() {
241 let error = MulticallError::DecodeError(alloy_sol_types::Error::Overrun);
242
243 assert!(matches!(Error::from(error), Error::AbiError(_)));
244 }
245
246 #[test]
247 fn converts_other_multicall_errors_to_transport_errors() {
248 let error = Error::from(MulticallError::NoReturnData);
249
250 assert!(matches!(error, Error::TransportError(_)));
251 assert_eq!(error.to_string(), "no return data");
252 }
253}