From 56714fe75f495e81c3d0e4e7a5a06b6078ce6bee Mon Sep 17 00:00:00 2001 From: Dori Medini Date: Sun, 20 Oct 2024 12:22:01 +0300 Subject: [PATCH] test(blockifier): test negative flows of cairo1 revert trace --- .../src/execution/stack_trace_test.rs | 122 +++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/crates/blockifier/src/execution/stack_trace_test.rs b/crates/blockifier/src/execution/stack_trace_test.rs index 864bb19b7cc..3ccf7400689 100644 --- a/crates/blockifier/src/execution/stack_trace_test.rs +++ b/crates/blockifier/src/execution/stack_trace_test.rs @@ -1,7 +1,8 @@ +use assert_matches::assert_matches; use pretty_assertions::assert_eq; use regex::Regex; use rstest::rstest; -use starknet_api::core::{calculate_contract_address, Nonce}; +use starknet_api::core::{calculate_contract_address, ContractAddress, EntryPointSelector, Nonce}; use starknet_api::transaction::{ ContractAddressSalt, Fee, @@ -10,12 +11,18 @@ use starknet_api::transaction::{ ValidResourceBounds, }; use starknet_api::{calldata, felt, invoke_tx_args}; +use starknet_types_core::felt::Felt; use crate::abi::abi_utils::selector_from_name; use crate::abi::constants::CONSTRUCTOR_ENTRY_POINT_NAME; use crate::context::{BlockContext, ChainInfo}; use crate::execution::call_info::{CallExecution, CallInfo, Retdata}; -use crate::execution::stack_trace::{extract_trailing_cairo1_revert_trace, TRACE_LENGTH_CAP}; +use crate::execution::errors::EntryPointExecutionError; +use crate::execution::stack_trace::{ + extract_trailing_cairo1_revert_trace, + Cairo1RevertStack, + TRACE_LENGTH_CAP, +}; use crate::execution::syscalls::hint_processor::ENTRYPOINT_FAILED_ERROR; use crate::test_utils::contracts::FeatureContract; use crate::test_utils::initial_test_state::{fund_account, test_state}; @@ -849,5 +856,116 @@ fn test_cairo1_revert_error_truncation(#[case] n_frames: usize, #[case] n_retdat assert!( format!("{}", extract_trailing_cairo1_revert_trace(&next_call_info)).len() <= TRACE_LENGTH_CAP + ) +} + +#[test] +fn test_cairo1_stack_extraction_inner_call_successful() { + let failure_reason_str = "0x1"; + let error_data = Retdata(vec![felt!(failure_reason_str)]); + let callinfo = CallInfo { + execution: CallExecution { retdata: error_data, failed: true, ..Default::default() }, + inner_calls: vec![CallInfo::default()], + ..Default::default() + }; + let error = EntryPointExecutionError::ExecutionFailed { + error_trace: extract_trailing_cairo1_revert_trace(&callinfo), + }; + assert_eq!( + error.to_string(), + format!( + "Execution failed. Failure reason: +Error in contract (contract address: {:#064x}, class hash: _, selector: {:#064x}): +{failure_reason_str}.", + ContractAddress::default().0.key(), + EntryPointSelector::default().0 + ) + ); +} + +#[test] +fn test_ambiguous_inner_cairo1_failure() { + let (failure_reason_0, failure_reason_1) = (Felt::ONE, Felt::TWO); + let outer_retdata = + Retdata(vec![failure_reason_0, failure_reason_1, felt!(ENTRYPOINT_FAILED_ERROR)]); + let inner_call_info = CallInfo { + execution: CallExecution { + retdata: Retdata(vec![failure_reason_0, failure_reason_1]), + failed: true, + ..Default::default() + }, + ..Default::default() + }; + let call_info = CallInfo { + execution: CallExecution { + retdata: outer_retdata.clone(), + failed: true, + ..Default::default() + }, + // Both of these inner calls can be the source of error; expect fallback value. + inner_calls: vec![inner_call_info.clone(), inner_call_info], + ..Default::default() + }; + assert_matches!( + extract_trailing_cairo1_revert_trace(&call_info), + Cairo1RevertStack { stack, last_retdata } + if stack.is_empty() && last_retdata == outer_retdata + ); +} + +#[rstest] +fn test_inner_cairo1_failure_not_last(#[values(true, false)] last_is_failed: bool) { + let (failure_reason_0, failure_reason_1) = (Felt::ONE, Felt::TWO); + let outer_retdata = + Retdata(vec![failure_reason_0, failure_reason_1, felt!(ENTRYPOINT_FAILED_ERROR)]); + let first_inner_retdata = Retdata(outer_retdata.0[..outer_retdata.0.len() - 1].into()); + let first_inner_call_info = CallInfo { + execution: CallExecution { + retdata: first_inner_retdata.clone(), + failed: true, + ..Default::default() + }, + ..Default::default() + }; + let last_inner_call_info = CallInfo { + execution: CallExecution { + retdata: Retdata( + // Not a prefix of the outer retdata. Should not be selected as inner failure. + vec![failure_reason_1, felt!(ENTRYPOINT_FAILED_ERROR)], + ), + failed: last_is_failed, + ..Default::default() + }, + ..Default::default() + }; + let call_info = CallInfo { + execution: CallExecution { + retdata: outer_retdata.clone(), + failed: true, + ..Default::default() + }, + inner_calls: vec![first_inner_call_info, last_inner_call_info], + ..Default::default() + }; + assert_matches!( + extract_trailing_cairo1_revert_trace(&call_info), + Cairo1RevertStack { stack, last_retdata } + if stack.len() == 2 && last_retdata == first_inner_retdata + ); +} + +/// If extraction function is called with a successful callinfo, it should return an empty stack and +/// the original retdata. +#[test] +fn test_cairo1_stack_extraction_not_failure_fallback() { + let expected_retdata = Retdata(vec![Felt::ONE, Felt::THREE]); + let successful_call = CallInfo { + execution: CallExecution { retdata: expected_retdata.clone(), ..Default::default() }, + ..Default::default() + }; + assert_matches!( + extract_trailing_cairo1_revert_trace(&successful_call), + Cairo1RevertStack { stack, last_retdata } + if stack.is_empty() && last_retdata == expected_retdata ); }