1use blockifier::execution::stack_trace::{
2 ErrorStack, ErrorStackHeader, ErrorStackSegment, PreambleType, gen_tx_execution_error_trace,
3};
4use blockifier::fee::fee_checks::FeeCheckError;
5use blockifier::transaction::errors::{
6 TransactionExecutionError, TransactionFeeError, TransactionPreValidationError,
7};
8use starknet_api::core::Nonce;
9use starknet_rs_core::types::Felt;
10use starknet_types;
11use starknet_types::contract_address::ContractAddress;
12use starknet_types::contract_storage_key::ContractStorageKey;
13use starknet_types::rpc::block::BlockId;
14use thiserror::Error;
15
16#[derive(Error, Debug)]
17pub enum Error {
18 #[error(transparent)]
19 StarknetApiError(#[from] starknet_api::StarknetApiError),
20 #[error(transparent)]
21 StateError(#[from] StateError),
22 #[error(transparent)]
23 BlockifierStateError(#[from] blockifier::state::errors::StateError),
24 #[error("{0:?}")]
25 ContractExecutionError(ContractExecutionError),
26 #[error("Execution error in simulating transaction no. {failure_index}: {execution_error:?}")]
27 ContractExecutionErrorInSimulation {
28 failure_index: usize,
29 execution_error: ContractExecutionError,
30 },
31 #[error("Types error: {0}")]
32 TypesError(#[from] starknet_types::error::Error),
33 #[error("I/O error: {0}")]
34 IoError(#[from] std::io::Error),
35 #[error("Error when reading file {path}")]
36 ReadFileError { source: std::io::Error, path: String },
37 #[error("The file does not exist")]
38 FileNotFound,
39 #[error("Contract not found")]
40 ContractNotFound,
41 #[error(transparent)]
42 SignError(#[from] starknet_rs_signers::local_wallet::SignError),
43 #[error("No block found")]
44 NoBlock,
45 #[error("No state at block {block_id:?}; consider running with --state-archive-capacity full")]
46 NoStateAtBlock { block_id: BlockId },
47 #[error("Format error")]
48 FormatError,
49 #[error("No transaction found")]
50 NoTransaction,
51 #[error("Invalid transaction index in a block")]
52 InvalidTransactionIndexInBlock,
53 #[error("Unsupported transaction type")]
54 UnsupportedTransactionType,
55 #[error("{msg}")]
56 UnsupportedAction { msg: String },
57 #[error("Unexpected internal error: {msg}")]
58 UnexpectedInternalError { msg: String },
59 #[error("Failed to load ContractClass: {0}")]
60 ContractClassLoadError(String),
61 #[error("Deserialization error: {origin}")]
62 DeserializationError { origin: String },
63 #[error("Serialization error: {origin}")]
64 SerializationError { origin: String },
65 #[error("Serialization not supported: {obj_name}")]
66 SerializationNotSupported { obj_name: String },
67 #[error(transparent)]
68 TransactionValidationError(#[from] TransactionValidationError),
69 #[error(transparent)]
70 TransactionFeeError(blockifier::transaction::errors::TransactionFeeError),
71 #[error(transparent)]
72 MessagingError(#[from] MessagingError),
73 #[error("Transaction has no trace")]
74 NoTransactionTrace,
75 #[error("the compiled class hash did not match the one supplied in the transaction")]
76 CompiledClassHashMismatch,
77 #[error("{msg}")]
78 ClassAlreadyDeclared { msg: String },
79 #[error("Requested entrypoint does not exist in the contract")]
80 EntrypointNotFound,
81 #[error("Contract class size is too large")]
82 ContractClassSizeIsTooLarge,
83}
84
85impl From<starknet_types_core::felt::FromStrError> for Error {
86 fn from(value: starknet_types_core::felt::FromStrError) -> Self {
87 Self::UnexpectedInternalError { msg: value.to_string() }
88 }
89}
90
91#[derive(Debug, Error)]
92pub enum StateError {
93 #[error("No class hash {0:x} found")]
94 NoneClassHash(Felt),
95 #[error("No compiled class hash found for class_hash {0:x}")]
96 NoneCompiledHash(Felt),
97 #[error("No casm class found for hash {0:x}")]
98 NoneCasmClass(Felt),
99 #[error("No contract state assigned for contact address: {0:x}")]
100 NoneContractState(ContractAddress),
101 #[error("No storage value assigned for: {0}")]
102 NoneStorage(ContractStorageKey),
103}
104
105#[derive(Debug, Error)]
106pub enum TransactionValidationError {
107 #[error("The transaction's resources don't cover validation or the minimal transaction fee.")]
108 InsufficientResourcesForValidate,
109 #[error("Account transaction nonce is invalid.")]
110 InvalidTransactionNonce {
111 address: ContractAddress,
112 account_nonce: Nonce,
113 incoming_tx_nonce: Nonce,
114 },
115 #[error("Account balance is not enough to cover the transaction cost.")]
116 InsufficientAccountBalance,
117 #[error("Account validation failed: {reason}")]
118 ValidationFailure { reason: String },
119}
120
121impl From<TransactionExecutionError> for Error {
122 fn from(value: TransactionExecutionError) -> Self {
123 match value {
124 TransactionExecutionError::TransactionPreValidationError(err) => match *err {
125 TransactionPreValidationError::InvalidNonce {
126 address,
127 account_nonce,
128 incoming_tx_nonce,
129 } => Self::TransactionValidationError(
130 TransactionValidationError::InvalidTransactionNonce {
131 address: address.into(),
132 account_nonce,
133 incoming_tx_nonce,
134 },
135 ),
136 TransactionPreValidationError::StateError(err) => {
137 Self::ContractExecutionError(err.to_string().into())
138 }
139 TransactionPreValidationError::TransactionFeeError(tx_fee_err) => {
140 Self::from(*tx_fee_err)
141 }
142 },
143 TransactionExecutionError::FeeCheckError(err) => err.into(),
144 TransactionExecutionError::TransactionFeeError(err) => (*err).into(),
145 TransactionExecutionError::ValidateTransactionError { .. } => {
146 TransactionValidationError::ValidationFailure { reason: value.to_string() }.into()
147 }
148 err @ TransactionExecutionError::DeclareTransactionError { .. } => {
149 Error::ClassAlreadyDeclared { msg: err.to_string() }
150 }
151 TransactionExecutionError::PanicInValidate { panic_reason } => {
152 TransactionValidationError::ValidationFailure { reason: panic_reason.to_string() }
153 .into()
154 }
155 other => Self::ContractExecutionError(other.into()),
156 }
157 }
158}
159
160impl From<FeeCheckError> for Error {
161 fn from(value: FeeCheckError) -> Self {
162 match value {
163 FeeCheckError::MaxGasAmountExceeded { .. } | FeeCheckError::MaxFeeExceeded { .. } => {
164 TransactionValidationError::InsufficientResourcesForValidate.into()
165 }
166 FeeCheckError::InsufficientFeeTokenBalance { .. } => {
167 TransactionValidationError::InsufficientAccountBalance.into()
168 }
169 }
170 }
171}
172
173impl From<TransactionFeeError> for Error {
174 fn from(value: TransactionFeeError) -> Self {
175 #[warn(clippy::wildcard_enum_match_arm)]
176 match value {
177 TransactionFeeError::FeeTransferError { .. }
178 | TransactionFeeError::MaxFeeTooLow { .. }
179 | TransactionFeeError::InsufficientResourceBounds { .. } => {
180 TransactionValidationError::InsufficientResourcesForValidate.into()
181 }
182 TransactionFeeError::MaxFeeExceedsBalance { .. }
183 | TransactionFeeError::ResourcesBoundsExceedBalance { .. }
184 | TransactionFeeError::GasBoundsExceedBalance { .. } => {
185 TransactionValidationError::InsufficientAccountBalance.into()
186 }
187 err @ (TransactionFeeError::CairoResourcesNotContainedInFeeCosts
188 | TransactionFeeError::ExecuteFeeTransferError(_)
189 | TransactionFeeError::InsufficientFee { .. }
190 | TransactionFeeError::MissingL1GasBounds
191 | TransactionFeeError::StateError(_)) => Error::TransactionFeeError(err),
192 }
193 }
194}
195
196#[derive(Debug, Error)]
197pub enum MessagingError {
198 #[error(
199 "Message is not configured, ensure you've used `postman/load_l1_messaging_contract` \
200 endpoint first."
201 )]
202 NotConfigured,
203 #[error("An error has occurred during messages conversion: {0}.")]
204 ConversionError(String),
205 #[error("Alloy error: {0}.")]
206 AlloyError(String),
207 #[error("Message to L1 with hash {0} is not present (never received OR already consumed).")]
208 MessageToL1NotPresent(String),
209 #[error("L1 not compatible: {0}")]
210 IncompatibleL1(String),
211}
212
213pub type DevnetResult<T, E = Error> = Result<T, E>;
214
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
216pub struct InnerContractExecutionError {
217 pub contract_address: starknet_api::core::ContractAddress,
218 pub class_hash: Felt,
219 pub selector: Felt,
220 #[serde(skip)]
221 pub return_data: Retdata,
222 pub error: Box<ContractExecutionError>,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226#[serde(untagged)]
227pub enum ContractExecutionError {
228 Nested(InnerContractExecutionError),
230 Message(String),
232}
233
234impl From<String> for ContractExecutionError {
235 fn from(value: String) -> Self {
236 ContractExecutionError::Message(value)
237 }
238}
239
240use blockifier::execution::call_info::{CallInfo, Retdata};
241use serde::{Deserialize, Serialize};
242
243impl From<TransactionExecutionError> for ContractExecutionError {
244 fn from(value: TransactionExecutionError) -> Self {
245 let error_stack = gen_tx_execution_error_trace(&value);
246 error_stack.into()
247 }
248}
249
250fn preamble_type_to_error_msg(preamble_type: &PreambleType) -> &'static str {
251 match preamble_type {
252 PreambleType::CallContract => "Error in external contract",
253 PreambleType::LibraryCall => "Error in library call",
254 PreambleType::Constructor => "Error in constructor",
255 }
256}
257
258fn header_to_error_msg(header: &ErrorStackHeader) -> &'static str {
259 match header {
260 ErrorStackHeader::Constructor => "Constructor error",
261 ErrorStackHeader::Execution => "Execution error",
262 ErrorStackHeader::Validation => "Validation error",
263 ErrorStackHeader::None => "Unknown error",
264 }
265}
266
267impl From<ErrorStack> for ContractExecutionError {
269 fn from(error_stack: ErrorStack) -> Self {
270 let error_string = error_stack.to_string();
271 fn format_error(stringified_error: &str, error_cause: &str) -> String {
272 if stringified_error.is_empty() {
273 error_cause.to_string()
274 } else {
275 format!("{} {}", stringified_error, error_cause)
276 }
277 }
278
279 let mut recursive_error_option = Option::<ContractExecutionError>::None;
280 for frame in error_stack.stack.iter().rev() {
281 let stack_err = match frame {
282 ErrorStackSegment::Cairo1RevertSummary(revert_summary) => {
283 let mut recursive_error = ContractExecutionError::Message(format_error(
284 &error_string,
285 &serde_json::to_string(&revert_summary.last_retdata.0).unwrap_or_default(),
286 ));
287
288 for trace in revert_summary.stack.iter().rev() {
289 recursive_error =
290 ContractExecutionError::Nested(InnerContractExecutionError {
291 contract_address: trace.contract_address,
292 class_hash: trace.class_hash.unwrap_or_default().0,
293 selector: trace.selector.0,
294 return_data: revert_summary.last_retdata.clone(),
295 error: Box::new(recursive_error),
296 });
297 }
298
299 recursive_error
300 }
301
302 ErrorStackSegment::Vm(vm) => recursive_error_option.take().unwrap_or(
305 ContractExecutionError::Message(format_error(&error_string, &String::from(vm))),
306 ),
307 ErrorStackSegment::StringFrame(msg) => {
308 ContractExecutionError::Message(format_error("", msg.as_str()))
309 }
310 ErrorStackSegment::EntryPoint(entry_point_error_frame) => {
311 let error = recursive_error_option.take().unwrap_or_else(|| {
312 ContractExecutionError::Message(format_error(
313 &error_string,
314 preamble_type_to_error_msg(&entry_point_error_frame.preamble_type),
315 ))
316 });
317
318 ContractExecutionError::Nested(InnerContractExecutionError {
319 contract_address: entry_point_error_frame.storage_address,
320 class_hash: entry_point_error_frame.class_hash.0,
321 selector: entry_point_error_frame.selector.unwrap_or_default().0,
322 return_data: Retdata(Vec::new()),
323 error: Box::new(error),
324 })
325 }
326 };
327
328 recursive_error_option = Some(stack_err);
329 }
330
331 if let Some(recursive_error) = recursive_error_option {
332 recursive_error
333 } else {
334 let error_msg = header_to_error_msg(&error_stack.header);
335 ContractExecutionError::Message(format_error(&error_string, error_msg))
336 }
337 }
338}
339
340impl From<&CallInfo> for ContractExecutionError {
341 fn from(call_info: &CallInfo) -> Self {
342 fn collect_failed_calls(root_call: &CallInfo) -> Vec<&CallInfo> {
345 let mut calls = vec![];
346
347 for inner_call in root_call.inner_calls.iter() {
348 calls.extend(collect_failed_calls(inner_call));
349 }
350
351 if root_call.execution.failed {
352 calls.push(root_call);
353 }
354
355 calls
356 }
357
358 let failed_calls = collect_failed_calls(call_info);
359
360 let mut recursive_error = ContractExecutionError::Message(
364 serde_json::to_string(
365 &failed_calls
366 .iter()
367 .rev()
368 .flat_map(|f| f.execution.retdata.clone().0)
369 .collect::<Vec<Felt>>(),
370 )
371 .unwrap_or_default(),
372 );
373
374 for failed in failed_calls {
375 let current = ContractExecutionError::Nested(InnerContractExecutionError {
376 contract_address: failed.call.storage_address,
377 class_hash: failed.call.class_hash.unwrap_or_default().0,
378 selector: failed.call.entry_point_selector.0,
379 return_data: failed.execution.retdata.clone(),
380 error: Box::new(recursive_error),
381 });
382 recursive_error = current;
383 }
384
385 recursive_error
386 }
387}