Skip to content

Commit

Permalink
feat(blockifier): structured cairo1 stack trace
Browse files Browse the repository at this point in the history
  • Loading branch information
dorimedini-starkware committed Oct 21, 2024
1 parent a07521d commit da3be39
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 24 deletions.
3 changes: 2 additions & 1 deletion crates/blockifier/src/execution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,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.
Expand Down Expand Up @@ -80,7 +81,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}")]
Expand Down
84 changes: 63 additions & 21 deletions crates/blockifier/src/execution/stack_trace.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)]
Expand Down Expand Up @@ -154,9 +156,61 @@ impl ErrorStack {
self.stack.push(frame);
}
}
#[derive(Debug)]
pub struct Cairo1RevertFrame {
pub contract_address: ContractAddress,
pub class_hash: Option<ClassHash>,
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<Cairo1RevertFrame>,
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."));

Expand All @@ -183,22 +237,10 @@ pub fn extract_trailing_cairo1_revert_trace(root_call: &CallInfo) -> String {

// Add one line per call, and append the failure reason.
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.
Expand Down
4 changes: 2 additions & 2 deletions crates/blockifier/src/transaction/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit da3be39

Please sign in to comment.