Skip to content

Commit 16c6ae1

Browse files
authored
Produce trace on panic (#2788)
**Stack**: - #2788 ⬅ - #2787 ⚠️ *Part of a stack created by [spr](https://github.com/ejoffe/spr). Do not merge manually using the UI - doing so may have unexpected results.*
1 parent bc6b537 commit 16c6ae1

File tree

7 files changed

+70
-55
lines changed

7 files changed

+70
-55
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Forge
11+
12+
#### Fixed
13+
14+
- `snforge` produces trace for contracts even if they fail or panic (assuming test passed)
15+
1016
## [0.35.0] - 2024-12-13
1117

1218
### Forge

crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cairo1_execution.rs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::runtime_extensions::call_to_blockifier_runtime_extension::execution::entry_point::{
2-
ContractClassEntryPointExecutionResult, EntryPointExecutionErrorWithLastPc, GetLastPc,
3-
OnErrorLastPc,
2+
ContractClassEntryPointExecutionResult, EntryPointExecutionErrorWithTrace, OnErrorLastPc,
43
};
54
use crate::runtime_extensions::call_to_blockifier_runtime_extension::CheatnetState;
65
use crate::runtime_extensions::cheatable_starknet_runtime_extension::CheatableStarknetRuntimeExtension;
@@ -72,23 +71,14 @@ pub fn execute_entry_point_call_cairo1(
7271
)
7372
.on_error_get_last_pc(&mut runner)?;
7473

75-
let vm_trace = if cheatable_runtime
76-
.extension
77-
.cheatnet_state
78-
.trace_data
79-
.is_vm_trace_needed
80-
{
81-
Some(get_relocated_vm_trace(&runner))
82-
} else {
83-
None
84-
};
74+
let trace = get_relocated_vm_trace(&mut runner);
75+
8576
let syscall_counter = cheatable_runtime
8677
.extended_runtime
8778
.hint_handler
8879
.syscall_counter
8980
.clone();
9081

91-
let last_pc = runner.get_last_pc();
9282
let call_info = finalize_execution(
9383
runner,
9484
cheatable_runtime.extended_runtime.hint_handler,
@@ -97,15 +87,15 @@ pub fn execute_entry_point_call_cairo1(
9787
program_extra_data_length,
9888
)?;
9989
if call_info.execution.failed {
100-
return Err(EntryPointExecutionErrorWithLastPc {
90+
return Err(EntryPointExecutionErrorWithTrace {
10191
source: EntryPointExecutionError::ExecutionFailed {
10292
error_data: call_info.execution.retdata.0,
10393
},
104-
last_pc,
94+
trace,
10595
});
10696
}
10797

108-
Ok((call_info, syscall_counter, vm_trace))
98+
Ok((call_info, syscall_counter, trace))
10999
// endregion
110100
}
111101

crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/entry_point.rs

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ use cairo_vm::vm::trace::trace_entry::RelocatedTraceEntry;
3131
use thiserror::Error;
3232
use conversions::FromConv;
3333
use crate::runtime_extensions::call_to_blockifier_runtime_extension::rpc::{AddressOrClassHash, CallResult};
34-
use crate::runtime_extensions::common::sum_syscall_counters;
34+
use crate::runtime_extensions::common::{get_relocated_vm_trace, sum_syscall_counters};
3535
use conversions::string::TryFromHexStr;
3636

3737
pub(crate) type ContractClassEntryPointExecutionResult = Result<
3838
(CallInfo, SyscallCounter, Option<Vec<RelocatedTraceEntry>>),
39-
EntryPointExecutionErrorWithLastPc,
39+
EntryPointExecutionErrorWithTrace,
4040
>;
4141

4242
// blockifier/src/execution/entry_point.rs:180 (CallEntryPoint::execute)
@@ -157,16 +157,17 @@ pub fn execute_call_entry_point(
157157
);
158158
Ok(call_info)
159159
}
160-
Err(EntryPointExecutionErrorWithLastPc {
161-
source: err,
162-
last_pc: pc,
163-
}) => {
164-
if let Some(pc) = pc {
160+
Err(EntryPointExecutionErrorWithTrace { source: err, trace }) => {
161+
if let Some(pc) = trace
162+
.as_ref()
163+
.and_then(|trace| trace.last())
164+
.map(|entry| entry.pc)
165+
{
165166
cheatnet_state
166167
.encountered_errors
167168
.push(EncounteredError { pc, class_hash });
168169
}
169-
exit_error_call(&err, cheatnet_state, resources, entry_point);
170+
exit_error_call(&err, cheatnet_state, resources, entry_point, trace);
170171
Err(err)
171172
}
172173
}
@@ -203,6 +204,7 @@ fn exit_error_call(
203204
cheatnet_state: &mut CheatnetState,
204205
resources: &mut ExecutionResources,
205206
entry_point: &CallEntryPoint,
207+
vm_trace: Option<Vec<RelocatedTraceEntry>>,
206208
) {
207209
let identifier = match entry_point.call_type {
208210
CallType::Call => AddressOrClassHash::ContractAddress(entry_point.storage_address),
@@ -213,7 +215,7 @@ fn exit_error_call(
213215
Default::default(),
214216
CallResult::from_err(error, &identifier),
215217
&[],
216-
None,
218+
vm_trace,
217219
);
218220
}
219221

@@ -314,19 +316,19 @@ fn aggregate_syscall_counters(trace: &Rc<RefCell<CallTrace>>) -> SyscallCounter
314316

315317
#[derive(Debug, Error)]
316318
#[error("{}", source)]
317-
pub struct EntryPointExecutionErrorWithLastPc {
319+
pub struct EntryPointExecutionErrorWithTrace {
318320
pub source: EntryPointExecutionError,
319-
pub last_pc: Option<usize>,
321+
pub trace: Option<Vec<RelocatedTraceEntry>>,
320322
}
321323

322-
impl<T> From<T> for EntryPointExecutionErrorWithLastPc
324+
impl<T> From<T> for EntryPointExecutionErrorWithTrace
323325
where
324326
T: Into<EntryPointExecutionError>,
325327
{
326328
fn from(value: T) -> Self {
327329
Self {
328330
source: value.into(),
329-
last_pc: None,
331+
trace: None,
330332
}
331333
}
332334
}
@@ -335,37 +337,21 @@ pub(crate) trait OnErrorLastPc<T>: Sized {
335337
fn on_error_get_last_pc(
336338
self,
337339
runner: &mut CairoRunner,
338-
) -> Result<T, EntryPointExecutionErrorWithLastPc>;
340+
) -> Result<T, EntryPointExecutionErrorWithTrace>;
339341
}
340342

341343
impl<T> OnErrorLastPc<T> for Result<T, EntryPointExecutionError> {
342344
fn on_error_get_last_pc(
343345
self,
344346
runner: &mut CairoRunner,
345-
) -> Result<T, EntryPointExecutionErrorWithLastPc> {
347+
) -> Result<T, EntryPointExecutionErrorWithTrace> {
346348
match self {
347349
Err(source) => {
348-
let last_pc = runner.get_last_pc();
350+
let trace = get_relocated_vm_trace(runner);
349351

350-
Err(EntryPointExecutionErrorWithLastPc { source, last_pc })
352+
Err(EntryPointExecutionErrorWithTrace { source, trace })
351353
}
352354
Ok(value) => Ok(value),
353355
}
354356
}
355357
}
356-
357-
pub trait GetLastPc {
358-
fn get_last_pc(&mut self) -> Option<usize>;
359-
}
360-
361-
impl GetLastPc for CairoRunner {
362-
fn get_last_pc(&mut self) -> Option<usize> {
363-
if self.relocated_trace.is_none() {
364-
self.relocate(true).ok()?;
365-
}
366-
self.relocated_trace
367-
.as_ref()
368-
.and_then(|trace| trace.last())
369-
.map(|entry| entry.pc)
370-
}
371-
}

crates/cheatnet/src/runtime_extensions/common.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ pub fn sum_syscall_counters(mut a: SyscallCounter, b: &SyscallCounter) -> Syscal
1818
}
1919

2020
#[must_use]
21-
pub fn get_relocated_vm_trace(cairo_runner: &CairoRunner) -> Vec<RelocatedTraceEntry> {
22-
cairo_runner.relocated_trace.clone().unwrap()
21+
pub fn get_relocated_vm_trace(cairo_runner: &mut CairoRunner) -> Option<Vec<RelocatedTraceEntry>> {
22+
// if vm execution failed, the trace is not relocated so we need to relocate it
23+
if cairo_runner.relocated_trace.is_none() {
24+
cairo_runner.relocate(true).ok()?;
25+
}
26+
cairo_runner.relocated_trace.clone()
2327
}

crates/cheatnet/src/runtime_extensions/forge_runtime_extension/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ pub fn update_top_call_l1_resources(runtime: &mut ForgeRuntime) {
591591
top_call.borrow_mut().used_l1_resources.l2_l1_message_sizes = all_l2_l1_message_sizes;
592592
}
593593

594-
pub fn update_top_call_vm_trace(runtime: &mut ForgeRuntime, cairo_runner: &CairoRunner) {
594+
pub fn update_top_call_vm_trace(runtime: &mut ForgeRuntime, cairo_runner: &mut CairoRunner) {
595595
let trace_data = &mut runtime
596596
.extended_runtime
597597
.extended_runtime
@@ -601,7 +601,7 @@ pub fn update_top_call_vm_trace(runtime: &mut ForgeRuntime, cairo_runner: &Cairo
601601

602602
if trace_data.is_vm_trace_needed {
603603
trace_data.current_call_stack.top().borrow_mut().vm_trace =
604-
Some(get_relocated_vm_trace(cairo_runner));
604+
get_relocated_vm_trace(cairo_runner);
605605
}
606606
}
607607
fn add_syscall_resources(

crates/forge-runner/src/running.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ pub fn run_test_case(
211211

212212
let run_result =
213213
match run_assembled_program(&assembled_program, builtins, hints_dict, &mut forge_runtime) {
214-
Ok(runner) => {
214+
Ok(mut runner) => {
215215
let vm_resources_without_inner_calls = runner
216216
.get_execution_resources()
217217
.unwrap()
@@ -242,7 +242,7 @@ pub fn run_test_case(
242242
&runner.relocated_memory,
243243
);
244244

245-
update_top_call_vm_trace(&mut forge_runtime, &runner);
245+
update_top_call_vm_trace(&mut forge_runtime, &mut runner);
246246

247247
Ok((gas_counter, runner.relocated_memory, value))
248248
}

crates/forge/tests/e2e/build_trace_data.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,32 @@ fn trace_has_deploy_with_no_constructor_phantom_nodes() {
166166
cairo_annotations::trace_data::CallTraceNode::DeployWithoutConstructor
167167
);
168168
}
169+
170+
#[test]
171+
fn trace_is_produced_even_if_contract_panics() {
172+
let temp = setup_package("backtrace_panic");
173+
test_runner(&temp)
174+
.arg("--save-trace-data")
175+
.assert()
176+
.success();
177+
178+
let trace_data = fs::read_to_string(
179+
temp.join(TRACE_DIR)
180+
.join("backtrace_panic::Test::test_contract_panics.json"),
181+
)
182+
.unwrap();
183+
184+
let call_trace: ProfilerCallTrace = serde_json::from_str(&trace_data).unwrap();
185+
186+
assert_all_execution_info_exists(&call_trace);
187+
}
188+
189+
fn assert_all_execution_info_exists(trace: &ProfilerCallTrace) {
190+
assert!(trace.cairo_execution_info.is_some());
191+
192+
for trace_node in &trace.nested_calls {
193+
if let ProfilerCallTraceNode::EntryPointCall(trace) = trace_node {
194+
assert_all_execution_info_exists(trace);
195+
}
196+
}
197+
}

0 commit comments

Comments
 (0)