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}