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::TransportError;
6use thiserror::Error;
7
8/// Dynamic contract result type.
9pub type Result<T, E = Error> = core::result::Result<T, E>;
10
11/// Error when interacting with contracts.
12#[derive(Debug, Error)]
13pub enum Error {
14 /// Unknown function referenced.
15 #[error("unknown function: function {0} does not exist")]
16 UnknownFunction(String),
17 /// Unknown function selector referenced.
18 #[error("unknown function: function with selector {0} does not exist")]
19 UnknownSelector(Selector),
20 /// Called `deploy` with a transaction that is not a deployment transaction.
21 #[error("transaction is not a deployment transaction")]
22 NotADeploymentTransaction,
23 /// `contractAddress` was not found in the deployment transaction’s receipt.
24 #[error("missing `contractAddress` from deployment transaction receipt")]
25 ContractNotDeployed,
26 /// The contract returned no data.
27 #[error("contract call to `{0}` returned no data (\"0x\"); the called address might not be a contract")]
28 ZeroData(String, #[source] AbiError),
29 /// An error occurred ABI encoding or decoding.
30 #[error(transparent)]
31 AbiError(#[from] AbiError),
32 /// An error occurred interacting with a contract over RPC.
33 #[error(transparent)]
34 TransportError(#[from] TransportError),
35 /// An error occurred while waiting for a pending transaction.
36 #[error(transparent)]
37 PendingTransactionError(#[from] PendingTransactionError),
38}
39
40impl From<alloy_sol_types::Error> for Error {
41 #[inline]
42 fn from(e: alloy_sol_types::Error) -> Self {
43 Self::AbiError(e.into())
44 }
45}
46
47impl Error {
48 #[cold]
49 pub(crate) fn decode(name: &str, data: &[u8], error: AbiError) -> Self {
50 if data.is_empty() {
51 let name = name.split('(').next().unwrap_or(name);
52 return Self::ZeroData(name.to_string(), error);
53 }
54 Self::AbiError(error)
55 }
56
57 /// Return the revert data in case the call reverted.
58 pub fn as_revert_data(&self) -> Option<Bytes> {
59 if let Self::TransportError(e) = self {
60 return e.as_error_resp().and_then(|e| e.as_revert_data());
61 }
62
63 None
64 }
65
66 /// Attempts to decode the revert data into one of the custom errors in [`SolInterface`].
67 ///
68 /// Returns an enum container type consisting of the custom errors defined in the interface.
69 ///
70 /// None is returned if the revert data is empty or if the data could not be decoded into one of
71 /// the custom errors defined in the interface.
72 ///
73 /// ## Example
74 ///
75 /// ```no_run
76 /// use alloy_provider::ProviderBuilder;
77 /// use alloy_sol_types::sol;
78 ///
79 /// sol! {
80 /// #[derive(Debug, PartialEq, Eq)]
81 /// #[sol(rpc, bytecode = "694207")]
82 /// contract ThrowsError {
83 /// error SomeCustomError(uint64 a);
84 /// error AnotherError(uint64 b);
85 ///
86 /// function error(uint64 a) external {
87 /// revert SomeCustomError(a);
88 /// }
89 /// }
90 /// }
91 ///
92 /// #[tokio::main]
93 /// async fn main() {
94 /// let provider = ProviderBuilder::new().connect_anvil_with_wallet();
95 ///
96 /// let throws_err = ThrowsError::deploy(provider).await.unwrap();
97 ///
98 /// let err = throws_err.error(42).call().await.unwrap_err();
99 ///
100 /// let custom_err =
101 /// err.as_decoded_interface_error::<ThrowsError::ThrowsErrorErrors>().unwrap();
102 ///
103 /// // Handle the custom error enum
104 /// match custom_err {
105 /// ThrowsError::ThrowsErrorErrors::SomeCustomError(a) => { /* handle error */ }
106 /// ThrowsError::ThrowsErrorErrors::AnotherError(b) => { /* handle error */ }
107 /// }
108 /// }
109 /// ```
110 pub fn as_decoded_interface_error<E: SolInterface>(&self) -> Option<E> {
111 self.as_revert_data().and_then(|data| E::abi_decode(&data).ok())
112 }
113
114 /// Decode the revert data into a custom [`SolError`] type.
115 ///
116 /// Returns an instance of the custom error type if decoding was successful, otherwise None.
117 ///
118 /// ## Example
119 ///
120 /// ```no_run
121 /// use alloy_provider::ProviderBuilder;
122 /// use alloy_sol_types::sol;
123 /// use ThrowsError::SomeCustomError;
124 /// sol! {
125 /// #[derive(Debug, PartialEq, Eq)]
126 /// #[sol(rpc, bytecode = "694207")]
127 /// contract ThrowsError {
128 /// error SomeCustomError(uint64 a);
129 /// error AnotherError(uint64 b);
130 ///
131 /// function error(uint64 a) external {
132 /// revert SomeCustomError(a);
133 /// }
134 /// }
135 /// }
136 ///
137 /// #[tokio::main]
138 /// async fn main() {
139 /// let provider = ProviderBuilder::new().connect_anvil_with_wallet();
140 ///
141 /// let throws_err = ThrowsError::deploy(provider).await.unwrap();
142 ///
143 /// let err = throws_err.error(42).call().await.unwrap_err();
144 ///
145 /// let custom_err = err.as_decoded_error::<SomeCustomError>().unwrap();
146 ///
147 /// assert_eq!(custom_err, SomeCustomError { a: 42 });
148 /// }
149 /// ```
150 pub fn as_decoded_error<E: SolError>(&self) -> Option<E> {
151 self.as_revert_data().and_then(|data| E::abi_decode(&data).ok())
152 }
153}