Skip to content

Commit

Permalink
feat(blockifier): add address and selector for intermediate cairo1 re…
Browse files Browse the repository at this point in the history
…vert errors
  • Loading branch information
dorimedini-starkware committed Oct 20, 2024
1 parent 9863885 commit 68e71d9
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 45 deletions.
2 changes: 1 addition & 1 deletion crates/blockifier/src/execution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl From<RunnerError> for PostExecutionError {
pub enum EntryPointExecutionError {
#[error(transparent)]
CairoRunError(#[from] CairoRunError),
#[error("Execution failed. Failure reason: {error_trace}.")]
#[error("Execution failed. Failure reason:\n{error_trace}.")]
ExecutionFailed { error_trace: String },
#[error("Internal error: {0}")]
InternalError(String),
Expand Down
44 changes: 41 additions & 3 deletions crates/blockifier/src/execution/stack_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ use cairo_vm::vm::errors::vm_errors::VirtualMachineError;
use itertools::Itertools;
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;
use super::syscalls::hint_processor::{SyscallExecutionError, ENTRYPOINT_FAILED_ERROR};
use crate::execution::call_info::CallInfo;
use crate::execution::errors::{ConstructorEntryPointExecutionError, EntryPointExecutionError};
use crate::transaction::errors::TransactionExecutionError;
Expand Down Expand Up @@ -154,8 +155,45 @@ impl ErrorStack {
}
}

pub fn extract_trailing_cairo1_revert_trace(root_callinfo: &CallInfo) -> String {
format_panic_data(&root_callinfo.execution.retdata.0)
pub fn extract_trailing_cairo1_revert_trace(root_call: &CallInfo) -> String {
let fallback_value = format_panic_data(&root_call.execution.retdata.0);
let entrypoint_failed_felt = Felt::from_hex(ENTRYPOINT_FAILED_ERROR)
.unwrap_or_else(|_| panic!("{ENTRYPOINT_FAILED_ERROR} does not fit in a felt."));

// Compute the failing call chain.
let mut error_calls: Vec<&CallInfo> = vec![];
let mut next_call = Some(root_call);
while let Some(call) = next_call {
// It is possible that a failing contract managed to call another (non-failing) contract
// before hitting an error; stop iteration if the current call was successful.
if !call.execution.failed {
break;
}
error_calls.push(call);
// If the last felt in the retdata is not the failure felt, stop iteration.
// Even if the next inner call is also in failed state, assume a scenario where the current
// call panicked after ignoring the error result of the inner call.
if call.execution.retdata.0.last() != Some(&entrypoint_failed_felt) {
break;
}
// For stack trace extraction, the last call chain is all that's relevant: sibling calls are
// not a source of error.
next_call = call.inner_calls.last();
}

// 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}, selector: {:#064x}):",
call_info.call.storage_address.0.key(),
call_info.call.entry_point_selector.0,
)
})
.chain([format_panic_data(&last_call.execution.retdata.0)])
.join("\n")
}

/// Extracts the error trace from a `TransactionExecutionError`. This is a top level function.
Expand Down
72 changes: 49 additions & 23 deletions crates/blockifier/src/execution/stack_trace_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,14 @@ An ASSERT_EQ instruction failed: 1 != 0.
"Transaction execution has failed:
0: Error in the called contract (contract address: {account_address_felt:#064x}, class hash: \
{account_contract_hash:#064x}, selector: {execute_selector_felt:#064x}):
Execution failed. Failure reason: (0x6661696c ('fail'), 0x454e545259504f494e545f4641494c4544 \
('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED')).
Execution failed. Failure reason:
Error in contract (contract address: {account_address_felt:#064x}, selector: \
{execute_selector_felt:#064x}):
Error in contract (contract address: {test_contract_address_felt:#064x}, selector: \
{external_entry_point_selector_felt:#064x}):
Error in contract (contract address: {test_contract_address_2_felt:#064x}, selector: \
{inner_entry_point_selector_felt:#064x}):
0x6661696c ('fail').
"
);

Expand Down Expand Up @@ -331,9 +337,14 @@ Unknown location (pc=0:{expected_pc1})
"Transaction execution has failed:
0: Error in the called contract (contract address: {account_address_felt:#064x}, class hash: \
{account_contract_hash:#064x}, selector: {execute_selector_felt:#064x}):
Execution failed. Failure reason: ({expected_error}, 0x454e545259504f494e545f4641494c4544 \
('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 \
('ENTRYPOINT_FAILED')).
Execution failed. Failure reason:
Error in contract (contract address: {account_address_felt:#064x}, selector: \
{execute_selector_felt:#064x}):
Error in contract (contract address: {contract_address_felt:#064x}, selector: \
{invoke_call_chain_selector_felt:#064x}):
Error in contract (contract address: {contract_address_felt:#064x}, selector: \
{invoke_call_chain_selector_felt:#064x}):
{expected_error}.
"
)
}
Expand Down Expand Up @@ -479,10 +490,16 @@ Unknown location (pc=0:{expected_pc3})
"Transaction execution has failed:
0: Error in the called contract (contract address: {account_address_felt:#064x}, class hash: \
{account_contract_hash:#064x}, selector: {execute_selector_felt:#064x}):
Execution failed. Failure reason: ({expected_error}, 0x454e545259504f494e545f4641494c4544 \
('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 \
('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 \
('ENTRYPOINT_FAILED')).
Execution failed. Failure reason:
Error in contract (contract address: {account_address_felt:#064x}, selector: \
{execute_selector_felt:#064x}):
Error in contract (contract address: {address_felt:#064x}, selector: \
{invoke_call_chain_selector_felt:#064x}):
Error in contract (contract address: {address_felt:#064x}, selector: \
{invoke_call_chain_selector_felt:#064x}):
Error in contract (contract address: {address_felt:#064x}, selector: \
{last_func_selector_felt:#064x}):
{expected_error}.
"
)
}
Expand Down Expand Up @@ -579,9 +596,11 @@ An ASSERT_EQ instruction failed: 1 != 0.
",
class_hash.0
),
CairoVersion::Cairo1 => "The `validate` entry point panicked with \
0x496e76616c6964207363656e6172696f ('Invalid scenario')."
.into(),
CairoVersion::Cairo1 => format!(
"The `validate` entry point panicked with:
Error in contract (contract address: {contract_address:#064x}, selector: {selector:#064x}):
0x496e76616c6964207363656e6172696f ('Invalid scenario')."
),
};

// Clean pc locations from the trace.
Expand Down Expand Up @@ -634,22 +653,26 @@ fn test_account_ctor_frame_stack_trace(
hash: {:#064x}, selector: {expected_selector:#064x}):
",
class_hash.0
) + match cairo_version {
CairoVersion::Cairo0 => {
"Error at pc=0:223:
)
.to_string()
+ &match cairo_version {
CairoVersion::Cairo0 => "Error at pc=0:223:
Cairo traceback (most recent call last):
Unknown location (pc=0:195)
Unknown location (pc=0:179)
An ASSERT_EQ instruction failed: 1 != 0.
"
}
CairoVersion::Cairo1 => {
"Execution failed. Failure reason: 0x496e76616c6964207363656e6172696f ('Invalid \
scenario').
.to_string(),
CairoVersion::Cairo1 => format!(
"Execution failed. Failure reason:
Error in contract (contract address: {expected_address:#064x}, selector: \
{expected_selector:#064x}):
0x496e76616c6964207363656e6172696f ('Invalid scenario').
"
}
};
)
.to_string(),
};

// Compare expected and actual error.
let error = deploy_account_tx.execute(state, &block_context, true, true).unwrap_err();
Expand Down Expand Up @@ -775,10 +798,13 @@ Error at pc=0:{}:
{frame_1}
Error at pc=0:{}:
{frame_2}
Execution failed. Failure reason: 0x496e76616c6964207363656e6172696f ('Invalid scenario').
Execution failed. Failure reason:
Error in contract (contract address: {expected_address:#064x}, selector: {:#064x}):
0x496e76616c6964207363656e6172696f ('Invalid scenario').
",
execute_offset + 205,
deploy_offset + 194
deploy_offset + 194,
ctor_selector.0
)
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use cairo_lang_utils::byte_array::BYTE_ARRAY_MAGIC;
use starknet_api::core::{ContractAddress, EntryPointSelector};
use starknet_types_core::felt::Felt;

use crate::execution::call_info::{CallExecution, CallInfo, Retdata};
Expand All @@ -7,27 +7,23 @@ use crate::execution::stack_trace::extract_trailing_cairo1_revert_trace;

#[test]
fn test_syscall_failure_format() {
let error_data = Retdata(
vec![
// Magic to indicate that this is a byte array.
BYTE_ARRAY_MAGIC,
// The number of full words in the byte array.
"0x00",
// The pending word of the byte array: "Execution failure"
"0x457865637574696f6e206661696c757265",
// The length of the pending word.
"0x11",
]
.into_iter()
.map(|x| Felt::from_hex(x).unwrap())
.collect(),
);
let execution_failure = "0x457865637574696f6e206661696c757265";
let error_data = Retdata(vec![Felt::from_hex(execution_failure).unwrap()]);
let callinfo = CallInfo {
execution: CallExecution { retdata: error_data, failed: true, ..Default::default() },
..Default::default()
};
let error = EntryPointExecutionError::ExecutionFailed {
error_trace: extract_trailing_cairo1_revert_trace(&callinfo),
};
assert_eq!(error.to_string(), "Execution failed. Failure reason: \"Execution failure\".");
assert_eq!(
error.to_string(),
format!(
"Execution failed. Failure reason:
Error in contract (contract address: {:#064x}, selector: {:#064x}):
{execution_failure} ('Execution failure').",
ContractAddress::default().0.key(),
EntryPointSelector::default().0
)
);
}
2 changes: 1 addition & 1 deletion crates/blockifier/src/transaction/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub enum TransactionExecutionError {
FeeCheckError(#[from] FeeCheckError),
#[error(transparent)]
FromStr(#[from] FromStrError),
#[error("The `validate` entry point panicked with {panic_reason}.")]
#[error("The `validate` entry point panicked with:\n{panic_reason}.")]
PanicInValidate { panic_reason: String },
#[error("The `validate` entry point should return `VALID`. Got {actual:?}.")]
InvalidValidateReturnData { actual: Retdata },
Expand Down

0 comments on commit 68e71d9

Please sign in to comment.