diff --git a/crates/blockifier/src/execution/errors.rs b/crates/blockifier/src/execution/errors.rs index 922508b83f..21a81f385f 100644 --- a/crates/blockifier/src/execution/errors.rs +++ b/crates/blockifier/src/execution/errors.rs @@ -14,6 +14,7 @@ use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector}; use thiserror::Error; use crate::execution::entry_point::ConstructorContext; +use crate::execution::stack_trace::Cairo1RevertStack; use crate::state::errors::StateError; // TODO(AlonH, 21/12/2022): Implement Display for all types that appear in errors. @@ -81,7 +82,7 @@ pub enum EntryPointExecutionError { #[error(transparent)] CairoRunError(#[from] CairoRunError), #[error("Execution failed. Failure reason:\n{error_trace}.")] - ExecutionFailed { error_trace: String }, + ExecutionFailed { error_trace: Cairo1RevertStack }, #[error("Internal error: {0}")] InternalError(String), #[error("Invalid input: {input_descriptor}; {info}")] diff --git a/crates/blockifier/src/execution/stack_trace.rs b/crates/blockifier/src/execution/stack_trace.rs index bce3b80862..88bdf72c7f 100644 --- a/crates/blockifier/src/execution/stack_trace.rs +++ b/crates/blockifier/src/execution/stack_trace.rs @@ -1,3 +1,5 @@ +use std::fmt::{Display, Formatter}; + use cairo_vm::types::relocatable::Relocatable; use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; use cairo_vm::vm::errors::hint_errors::HintError; @@ -7,10 +9,10 @@ use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector}; use starknet_api::execution_utils::format_panic_data; use starknet_types_core::felt::Felt; -use super::deprecated_syscalls::hint_processor::DeprecatedSyscallExecutionError; -use super::syscalls::hint_processor::{SyscallExecutionError, ENTRYPOINT_FAILED_ERROR}; -use crate::execution::call_info::CallInfo; +use crate::execution::call_info::{CallInfo, Retdata}; +use crate::execution::deprecated_syscalls::hint_processor::DeprecatedSyscallExecutionError; use crate::execution::errors::{ConstructorEntryPointExecutionError, EntryPointExecutionError}; +use crate::execution::syscalls::hint_processor::{SyscallExecutionError, ENTRYPOINT_FAILED_ERROR}; use crate::transaction::errors::TransactionExecutionError; #[cfg(test)] @@ -154,9 +156,61 @@ impl ErrorStack { self.stack.push(frame); } } +#[derive(Debug)] +pub struct Cairo1RevertFrame { + pub contract_address: ContractAddress, + pub class_hash: Option, + pub selector: EntryPointSelector, +} + +impl From<&&CallInfo> for Cairo1RevertFrame { + fn from(callinfo: &&CallInfo) -> Self { + Self { + contract_address: callinfo.call.storage_address, + class_hash: callinfo.call.class_hash, + selector: callinfo.call.entry_point_selector, + } + } +} -pub fn extract_trailing_cairo1_revert_trace(root_call: &CallInfo) -> String { - let fallback_value = format_panic_data(&root_call.execution.retdata.0); +impl Display for Cairo1RevertFrame { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Error in contract (contract address: {:#064x}, class hash: {}, selector: {:#064x}):", + self.contract_address.0.key(), + match self.class_hash { + Some(class_hash) => format!("{:#064x}", class_hash.0), + None => "_".to_string(), + }, + self.selector.0, + ) + } +} + +#[derive(Debug)] +pub struct Cairo1RevertStack { + pub stack: Vec, + pub last_retdata: Retdata, +} + +impl Display for Cairo1RevertStack { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + self.stack + .iter() + .map(|frame| frame.to_string()) + .chain([format_panic_data(&self.last_retdata.0)]) + .join("\n") + ) + } +} + +pub fn extract_trailing_cairo1_revert_trace(root_call: &CallInfo) -> Cairo1RevertStack { + let fallback_value = + Cairo1RevertStack { stack: vec![], last_retdata: root_call.execution.retdata.clone() }; let entrypoint_failed_felt = Felt::from_hex(ENTRYPOINT_FAILED_ERROR) .unwrap_or_else(|_| panic!("{ENTRYPOINT_FAILED_ERROR} does not fit in a felt.")); @@ -203,22 +257,10 @@ pub fn extract_trailing_cairo1_revert_trace(root_call: &CallInfo) -> String { // Add one line per call, and append the failure reason. // If error_calls is empty, that means the root call is non-failing; return the fallback value. let Some(last_call) = error_calls.last() else { return fallback_value }; - error_calls - .iter() - .map(|call_info| { - format!( - "Error in contract (contract address: {:#064x}, class hash: {}, selector: \ - {:#064x}):", - call_info.call.storage_address.0.key(), - match call_info.call.class_hash { - Some(class_hash) => format!("{:#064x}", class_hash.0), - None => "_".to_string(), - }, - call_info.call.entry_point_selector.0, - ) - }) - .chain([format_panic_data(&last_call.execution.retdata.0)]) - .join("\n") + Cairo1RevertStack { + stack: error_calls.iter().map(Cairo1RevertFrame::from).collect(), + last_retdata: last_call.execution.retdata.clone(), + } } /// Extracts the error trace from a `TransactionExecutionError`. This is a top level function. diff --git a/crates/blockifier/src/transaction/errors.rs b/crates/blockifier/src/transaction/errors.rs index adcf00744f..1905c78eac 100644 --- a/crates/blockifier/src/transaction/errors.rs +++ b/crates/blockifier/src/transaction/errors.rs @@ -11,7 +11,7 @@ use thiserror::Error; use crate::bouncer::BouncerWeights; use crate::execution::call_info::Retdata; use crate::execution::errors::{ConstructorEntryPointExecutionError, EntryPointExecutionError}; -use crate::execution::stack_trace::gen_tx_execution_error_trace; +use crate::execution::stack_trace::{gen_tx_execution_error_trace, Cairo1RevertStack}; use crate::fee::fee_checks::FeeCheckError; use crate::state::errors::StateError; @@ -104,7 +104,7 @@ pub enum TransactionExecutionError { #[error(transparent)] FromStr(#[from] FromStrError), #[error("The `validate` entry point panicked with:\n{panic_reason}.")] - PanicInValidate { panic_reason: String }, + PanicInValidate { panic_reason: Cairo1RevertStack }, #[error("The `validate` entry point should return `VALID`. Got {actual:?}.")] InvalidValidateReturnData { actual: Retdata }, #[error(