Skip to content

Commit c15e034

Browse files
committed
Improve backtrace error for forked contracts
Closes #3385 commit-id:628a8ce9
1 parent a09a048 commit c15e034

File tree

7 files changed

+197
-27
lines changed

7 files changed

+197
-27
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ use bimap::BiMap;
44
use camino::Utf8PathBuf;
55
use conversions::IntoConv;
66
use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
7+
use runtime::starknet::constants::TEST_CONTRACT_CLASS_HASH;
78
use scarb_api::StarknetContractArtifacts;
89
use starknet::core::types::contract::{AbiEntry, SierraClass};
910
use starknet::core::utils::get_selector_from_name;
1011
use starknet_api::core::{ClassHash, EntryPointSelector};
12+
use starknet_types_core::felt::Felt;
1113
use std::collections::HashMap;
14+
use std::ops::Not;
1215

1316
type ContractName = String;
1417
type FunctionName = String;
@@ -105,6 +108,15 @@ impl ContractsData {
105108
) -> Option<&FunctionName> {
106109
self.selectors.get(entry_point_selector)
107110
}
111+
112+
#[must_use]
113+
pub fn is_fork_class_hash(&self, class_hash: &ClassHash) -> bool {
114+
if class_hash.0 == Felt::from_hex_unchecked(TEST_CONTRACT_CLASS_HASH) {
115+
false
116+
} else {
117+
self.class_hashes.contains_right(class_hash).not()
118+
}
119+
}
108120
}
109121

110122
fn build_name_selector_map(abi: Vec<AbiEntry>) -> HashMap<EntryPointSelector, FunctionName> {

crates/forge-runner/src/backtrace/data.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::backtrace::display::{Backtrace, BacktraceStack};
1+
use crate::backtrace::display::{Backtrace, render_fork_backtrace, render_local_backtrace};
22
use anyhow::Context;
33
use anyhow::Result;
44
use cairo_annotations::annotations::TryFromDebugInfo;
@@ -19,26 +19,50 @@ use rayon::iter::ParallelIterator;
1919
use starknet_api::core::ClassHash;
2020
use std::collections::{HashMap, HashSet};
2121

22-
pub struct ContractBacktraceDataMapping(HashMap<ClassHash, ContractBacktraceData>);
22+
pub struct ContractBacktraceDataMapping(HashMap<ClassHash, ContractOrigin>);
2323

2424
impl ContractBacktraceDataMapping {
2525
pub fn new(contracts_data: &ContractsData, class_hashes: HashSet<ClassHash>) -> Result<Self> {
2626
Ok(Self(
2727
class_hashes
2828
.into_par_iter()
2929
.map(|class_hash| {
30-
ContractBacktraceData::new(&class_hash, contracts_data)
30+
ContractOrigin::new(&class_hash, contracts_data)
3131
.map(|contract_data| (class_hash, contract_data))
3232
})
3333
.collect::<Result<_>>()?,
3434
))
3535
}
3636

37-
pub fn get_backtrace(&self, pc: &[usize], class_hash: &ClassHash) -> Result<BacktraceStack> {
37+
pub fn get_backtrace(&self, pcs: &[usize], class_hash: &ClassHash) -> Result<String> {
3838
self.0
3939
.get(class_hash)
4040
.expect("class hash should be present in the data mapping")
41-
.backtrace_stack_from(pc)
41+
.render_backtrace(pcs)
42+
}
43+
}
44+
45+
enum ContractOrigin {
46+
Fork(ClassHash),
47+
Local(ContractBacktraceData),
48+
}
49+
50+
impl ContractOrigin {
51+
fn new(class_hash: &ClassHash, contracts_data: &ContractsData) -> Result<Self> {
52+
if contracts_data.is_fork_class_hash(class_hash) {
53+
Ok(ContractOrigin::Fork(*class_hash))
54+
} else {
55+
Ok(ContractOrigin::Local(ContractBacktraceData::new(
56+
class_hash,
57+
contracts_data,
58+
)?))
59+
}
60+
}
61+
fn render_backtrace(&self, pcs: &[usize]) -> Result<String> {
62+
match self {
63+
ContractOrigin::Fork(class_hash) => Ok(render_fork_backtrace(class_hash)),
64+
ContractOrigin::Local(data) => data.render_backtrace(pcs),
65+
}
4266
}
4367
}
4468

@@ -156,7 +180,7 @@ impl ContractBacktraceData {
156180
Ok(stack)
157181
}
158182

159-
fn backtrace_stack_from(&self, pcs: &[usize]) -> Result<BacktraceStack> {
183+
fn render_backtrace(&self, pcs: &[usize]) -> Result<String> {
160184
let stack = pcs
161185
.iter()
162186
.map(|pc| self.backtrace_from(*pc))
@@ -165,9 +189,6 @@ impl ContractBacktraceData {
165189

166190
let contract_name = &self.contract_name;
167191

168-
Ok(BacktraceStack {
169-
contract_name,
170-
stack,
171-
})
192+
Ok(render_local_backtrace(contract_name, stack))
172193
}
173194
}

crates/forge-runner/src/backtrace/display.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use cairo_annotations::annotations::coverage::{CodeLocation, ColumnNumber, LineNumber};
22
use cairo_annotations::annotations::profiler::FunctionName;
3+
use starknet_api::core::ClassHash;
34
use std::fmt;
45
use std::fmt::Display;
56

@@ -9,7 +10,7 @@ pub struct Backtrace<'a> {
910
pub inlined: bool,
1011
}
1112

12-
pub struct BacktraceStack<'a> {
13+
struct BacktraceStack<'a> {
1314
pub contract_name: &'a str,
1415
pub stack: Vec<Backtrace<'a>>,
1516
}
@@ -31,11 +32,26 @@ impl Display for Backtrace<'_> {
3132

3233
impl Display for BacktraceStack<'_> {
3334
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34-
writeln!(f, "error occurred in contract '{}'", self.contract_name,)?;
35+
writeln!(f, "error occurred in contract '{}'", self.contract_name)?;
3536
writeln!(f, "stack backtrace:")?;
3637
for (i, backtrace) in self.stack.iter().enumerate() {
3738
writeln!(f, " {i}: {backtrace}")?;
3839
}
3940
Ok(())
4041
}
4142
}
43+
44+
pub fn render_local_backtrace(contract_name: &str, stack: Vec<Backtrace>) -> String {
45+
BacktraceStack {
46+
contract_name,
47+
stack,
48+
}
49+
.to_string()
50+
}
51+
52+
pub fn render_fork_backtrace(contract_class_hash: &ClassHash) -> String {
53+
format!(
54+
"error occurred in contract fork with class hash: {:#x}\n",
55+
contract_class_hash.0
56+
)
57+
}

crates/forge-runner/src/backtrace/mod.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,10 @@ pub fn add_backtrace_footer(
1818
return message;
1919
}
2020

21-
if !is_backtrace_enabled() {
22-
return format!(
23-
"{message}\nnote: run with `{BACKTRACE_ENV}=1` environment variable to display a backtrace",
24-
);
25-
}
21+
let backtrace = is_backtrace_enabled()
22+
.then(|| get_backtrace(contracts_data, encountered_errors))
23+
.unwrap_or_else(|| format!("{message}\nnote: run with `{BACKTRACE_ENV}=1` environment variable to display a backtrace"));
2624

27-
let backtrace = get_backtrace(contracts_data, encountered_errors);
2825
format!("{message}\n{backtrace}")
2926
}
3027

@@ -40,7 +37,6 @@ pub fn get_backtrace(
4037
encountered_errors
4138
.iter()
4239
.map(|(class_hash, pcs)| data_mapping.get_backtrace(pcs, class_hash))
43-
.map(|backtrace| backtrace.map(|backtrace| backtrace.to_string()))
4440
.collect::<Result<Vec<_>>>()
4541
.map(|backtrace| backtrace.join("\n"))
4642
})

crates/forge/tests/data/backtrace_panic/src/lib.cairo

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,21 @@ mod Test {
7272
let dispatcher = IOuterContractDispatcher { contract_address: contract_address_outer };
7373
dispatcher.outer(contract_address_inner);
7474
}
75+
76+
#[test]
77+
#[fork(url: "{{ NODE_RPC_URL }}", block_number: 806134)]
78+
fn test_fork_contract_panics() {
79+
// NOTE: This is not exactly the same as InnerContract here, but will return the same error
80+
// Class hash needs to be different otherwise it would be found locally in the state
81+
let contract_address_inner =
82+
0x066eda239a01152a912129fe6b5bf309c9b21e3f583df4e5b7ee8ede1fad820a
83+
.try_into()
84+
.unwrap();
85+
86+
let contract_outer = declare("OuterContract").unwrap().contract_class();
87+
let (contract_address_outer, _) = contract_outer.deploy(@array![]).unwrap();
88+
89+
let dispatcher = IOuterContractDispatcher { contract_address: contract_address_outer };
90+
dispatcher.outer(contract_address_inner);
91+
}
7592
}

crates/forge/tests/data/backtrace_vm_error/src/lib.cairo

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,21 @@ mod Test {
6666
let dispatcher = IOuterContractDispatcher { contract_address: contract_address_outer };
6767
dispatcher.outer(contract_address_inner);
6868
}
69+
70+
#[test]
71+
#[fork(url: "{{ NODE_RPC_URL }}", block_number: 806134)]
72+
fn test_fork_unwrapped_call_contract_syscall() {
73+
// NOTE: This is not exactly the same as InnerContract here, but will return the same error
74+
// Class hash needs to be different otherwise it would be found locally in the state
75+
let contract_address_inner =
76+
0x01506c04bdb56f2cc9ea1f67fcb086c99df7cec2598ce90e56f1d36fffda1cf4
77+
.try_into()
78+
.unwrap();
79+
80+
let contract_outer = declare("OuterContract").unwrap().contract_class();
81+
let (contract_address_outer, _) = contract_outer.deploy(@array![]).unwrap();
82+
83+
let dispatcher = IOuterContractDispatcher { contract_address: contract_address_outer };
84+
dispatcher.outer(contract_address_inner);
85+
}
6986
}

0 commit comments

Comments
 (0)