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}