Skip to content

Commit 59c73c7

Browse files
Trace as tree (#1539)
Issues that arises: #1553 ## Introduced changes - trace as recursive struct (tree) - `get_call_trace` returning full call from test instead of `get_last_call_trace` (due to ambiguity with e.g. for deploy without constructor new trace isn't added) ## Checklist - [x] Linked relevant issue - [x] Updated relevant documentation - [x] Added relevant tests - [x] Performed self-review of the code - [x] Added changes to `CHANGELOG.md` --------- Co-authored-by: Maksymilian Demitraszek <[email protected]> Co-authored-by: Maksymilian Demitraszek <[email protected]>
1 parent 696e8b1 commit 59c73c7

File tree

11 files changed

+726
-443
lines changed

11 files changed

+726
-443
lines changed

crates/cheatnet/src/constants.rs

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ use blockifier::execution::contract_class::{ContractClassV1, ContractClassV1Inne
66
use blockifier::execution::contract_class::ContractClass;
77
use cairo_vm::types::program::Program;
88

9+
use crate::runtime_extensions::common::create_entry_point_selector;
10+
use blockifier::execution::entry_point::{CallEntryPoint, CallType};
11+
use conversions::IntoConv;
12+
use starknet::core::utils::get_selector_from_name;
913
use starknet_api::deprecated_contract_class::EntryPointType;
1014

1115
use runtime::starknet::context::ERC20_CONTRACT_ADDRESS;
1216
use runtime::starknet::state::DictStateReader;
1317
use starknet_api::{
14-
core::{ClassHash, ContractAddress, Nonce, PatriciaKey},
18+
core::{ClassHash, ContractAddress, PatriciaKey},
1519
hash::{StarkFelt, StarkHash},
1620
patricia_key, stark_felt,
17-
transaction::{Calldata, DeclareTransactionV2, InvokeTransactionV1},
21+
transaction::Calldata,
1822
};
1923

2024
pub const MAX_FEE: u128 = 1_000_000 * 100_000_000_000; // 1000000 * min_gas_price.
@@ -32,32 +36,6 @@ pub const TEST_CONTRACT_CLASS_HASH: &str = "0x117";
3236
// snforge_std/src/cheatcodes.cairo::test_address
3337
pub const TEST_ADDRESS: &str = "0x01724987234973219347210837402";
3438

35-
#[must_use]
36-
pub fn build_declare_transaction(
37-
nonce: Nonce,
38-
class_hash: ClassHash,
39-
sender_address: ContractAddress,
40-
) -> DeclareTransactionV2 {
41-
DeclareTransactionV2 {
42-
nonce,
43-
class_hash,
44-
sender_address,
45-
..Default::default()
46-
}
47-
}
48-
49-
#[must_use]
50-
pub fn build_invoke_transaction(
51-
calldata: Calldata,
52-
sender_address: ContractAddress,
53-
) -> InvokeTransactionV1 {
54-
InvokeTransactionV1 {
55-
sender_address,
56-
calldata,
57-
..Default::default()
58-
}
59-
}
60-
6139
fn contract_class_no_entrypoints() -> ContractClass {
6240
let inner = ContractClassV1Inner {
6341
program: Program::default(),
@@ -99,3 +77,22 @@ pub fn build_testing_state() -> DictStateReader {
9977
..Default::default()
10078
}
10179
}
80+
81+
#[must_use]
82+
pub fn build_test_entry_point() -> CallEntryPoint {
83+
let test_selector = get_selector_from_name("TEST_CONTRACT_SELECTOR")
84+
.unwrap()
85+
.into_();
86+
let entry_point_selector = create_entry_point_selector(&test_selector);
87+
CallEntryPoint {
88+
class_hash: None,
89+
code_address: Some(ContractAddress(patricia_key!(TEST_ADDRESS))),
90+
entry_point_type: EntryPointType::External,
91+
entry_point_selector,
92+
calldata: Calldata(Arc::new(vec![])),
93+
storage_address: ContractAddress(patricia_key!(TEST_ADDRESS)),
94+
caller_address: ContractAddress::default(),
95+
call_type: CallType::Call,
96+
initial_gas: u64::MAX,
97+
}
98+
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ pub fn execute_call_entry_point(
3434
) -> EntryPointExecutionResult<CallInfo> {
3535
// region: Modified blockifier code
3636
// We skip recursion depth validation here.
37-
cheatnet_state.trace_info.push(entry_point.clone());
37+
38+
cheatnet_state
39+
.trace_data
40+
.enter_nested_call(entry_point.clone());
41+
3842
if let Some(ret_data) = get_ret_data_by_call_entry_point(entry_point, cheatnet_state) {
3943
return Ok(mocked_call_info(entry_point.clone(), ret_data.clone()));
4044
}
@@ -86,6 +90,8 @@ pub fn execute_call_entry_point(
8690
),
8791
};
8892

93+
cheatnet_state.trace_data.exit_nested_call();
94+
8995
result.map_err(|error| {
9096
// endregion
9197
match error {

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

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,6 @@ impl<'a> ExtensionLogic for CallToBlockifierExtension<'a> {
5454
vm: &mut VirtualMachine,
5555
extended_runtime: &mut IORuntime<'a>,
5656
) -> Result<SyscallHandlingResult, HintError> {
57-
match selector {
58-
// We clear it here to ensure that on each new contract call
59-
// deploy syscall and library call made from test code
60-
// we start with an empty trace
61-
// Same case when handling l1_handler_execute and in `deploy_at`
62-
DeprecatedSyscallSelector::CallContract
63-
| DeprecatedSyscallSelector::LibraryCall
64-
| DeprecatedSyscallSelector::LibraryCallL1Handler
65-
| DeprecatedSyscallSelector::Deploy => extended_runtime
66-
.extended_runtime
67-
.extension
68-
.cheatnet_state
69-
.trace_info
70-
.clear(),
71-
_ => (),
72-
}
7357
match selector {
7458
// We execute contract calls and library calls with modified blockifier
7559
// This is redirected to drop ForgeRuntimeExtension

crates/cheatnet/src/runtime_extensions/forge_runtime_extension/cheatcodes/deploy.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ pub fn deploy_at(
4040
calldata: &[Felt252],
4141
contract_address: ContractAddress,
4242
) -> Result<DeployCallPayload, CheatcodeError> {
43-
cheatnet_state.trace_info.clear();
4443
let blockifier_state_raw: &mut dyn State = blockifier_state.blockifier_state;
4544

4645
if let Ok(class_hash) = blockifier_state_raw.get_class_hash_at(contract_address) {

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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::runtime_extensions::forge_runtime_extension::cheatcodes::deploy::{
77
deploy, deploy_at, DeployCallPayload,
88
};
99
use crate::runtime_extensions::forge_runtime_extension::cheatcodes::CheatcodeError;
10-
use crate::state::{BlockifierState, CheatTarget};
10+
use crate::state::{BlockifierState, CallTrace, CheatTarget};
1111
use anyhow::{Context, Result};
1212
use blockifier::execution::call_info::{CallExecution, CallInfo};
1313
use blockifier::execution::deprecated_syscalls::DeprecatedSyscallSelector;
@@ -393,7 +393,6 @@ impl<'a> ExtensionLogic for ForgeExtension<'a> {
393393
let mut blockifier_state =
394394
BlockifierState::from(cheatnet_runtime.extended_runtime.hint_handler.state);
395395

396-
cheatnet_runtime.extension.cheatnet_state.trace_info.clear();
397396
match blockifier_state
398397
.l1_handler_execute(
399398
cheatnet_runtime.extension.cheatnet_state,
@@ -592,18 +591,17 @@ impl<'a> ExtensionLogic for ForgeExtension<'a> {
592591
Felt252::from_bytes_be(&s_bytes[0..16]), // 16 high bytes of s
593592
]))
594593
}
595-
"get_last_call_trace" => {
596-
let trace_info = &extended_runtime
594+
"get_call_trace" => {
595+
let call_trace = &extended_runtime
597596
.extended_runtime
598597
.extended_runtime
599598
.extension
600599
.cheatnet_state
601-
.trace_info;
602-
let mut output = vec![Felt252::from(trace_info.len())];
600+
.trace_data
601+
.current_call_stack
602+
.borrow_full_trace();
603+
let output = serialize_call_trace(call_trace);
603604

604-
for call_entry_point in trace_info {
605-
output.append(&mut serialize_call_entry_point(call_entry_point));
606-
}
607605
Ok(CheatcodeHandlingResult::Handled(output))
608606
}
609607
"store" => {
@@ -684,6 +682,19 @@ fn handle_deploy_result(
684682
}
685683
}
686684

685+
fn serialize_call_trace(call_trace: &CallTrace) -> Vec<Felt252> {
686+
let mut output = vec![];
687+
output.append(&mut serialize_call_entry_point(&call_trace.entry_point));
688+
689+
output.push(Felt252::from(call_trace.nested_calls.len()));
690+
691+
for call_trace in &call_trace.nested_calls {
692+
output.append(&mut serialize_call_trace(&call_trace.borrow()));
693+
}
694+
695+
output
696+
}
697+
687698
fn serialize_call_entry_point(call_entry_point: &CallEntryPoint) -> Vec<Felt252> {
688699
let entry_point_type = match call_entry_point.entry_point_type {
689700
EntryPointType::Constructor => 0,

crates/cheatnet/src/state.rs

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@ use runtime::starknet::state::DictStateReader;
1919

2020
use starknet_api::core::EntryPointSelector;
2121

22+
use crate::constants::build_test_entry_point;
2223
use starknet_api::transaction::ContractAddressSalt;
2324
use starknet_api::{
2425
core::{ClassHash, CompiledClassHash, ContractAddress, Nonce},
2526
hash::StarkFelt,
2627
state::StorageKey,
2728
};
29+
use std::cell::{Ref, RefCell};
2830
use std::collections::HashMap;
2931
use std::hash::BuildHasher;
32+
use std::rc::Rc;
3033

3134
// Specifies which contracts to target
3235
// with a cheatcode function
@@ -136,7 +139,45 @@ pub enum CheatStatus<T> {
136139
Uncheated,
137140
}
138141

139-
#[derive(Default)]
142+
/// Tree structure representing trace of a call.
143+
pub struct CallTrace {
144+
pub entry_point: CallEntryPoint,
145+
pub nested_calls: Vec<Rc<RefCell<CallTrace>>>,
146+
}
147+
148+
pub struct NotEmptyCallStack(Vec<Rc<RefCell<CallTrace>>>);
149+
150+
impl NotEmptyCallStack {
151+
pub fn from(elem: Rc<RefCell<CallTrace>>) -> Self {
152+
NotEmptyCallStack(vec![elem])
153+
}
154+
155+
pub fn push(&mut self, elem: Rc<RefCell<CallTrace>>) {
156+
self.0.push(elem);
157+
}
158+
159+
pub fn top(&mut self) -> Rc<RefCell<CallTrace>> {
160+
let top_val = self.0.pop().unwrap();
161+
let borrowed_ref = top_val.clone();
162+
self.0.push(top_val);
163+
borrowed_ref
164+
}
165+
166+
pub fn pop(&mut self) -> Rc<RefCell<CallTrace>> {
167+
assert!(self.0.len() > 1, "You cannot make NotEmptyCallStack empty");
168+
self.0.pop().unwrap()
169+
}
170+
171+
#[must_use]
172+
pub fn borrow_full_trace(&self) -> Ref<'_, CallTrace> {
173+
self.0.first().unwrap().borrow()
174+
}
175+
}
176+
177+
pub struct TraceData {
178+
pub current_call_stack: NotEmptyCallStack,
179+
}
180+
140181
pub struct CheatnetState {
141182
pub rolled_contracts: HashMap<ContractAddress, CheatStatus<Felt252>>,
142183
pub global_roll: Option<Felt252>,
@@ -155,8 +196,38 @@ pub struct CheatnetState {
155196
pub block_info: BlockInfo,
156197
// execution resources used by all contract calls
157198
pub used_resources: UsedResources,
158-
// trace info of the last call
159-
pub trace_info: Vec<CallEntryPoint>,
199+
200+
pub trace_data: TraceData,
201+
}
202+
203+
impl Default for CheatnetState {
204+
fn default() -> Self {
205+
let test_call = Rc::new(RefCell::new(CallTrace {
206+
entry_point: build_test_entry_point(),
207+
nested_calls: vec![],
208+
}));
209+
Self {
210+
rolled_contracts: Default::default(),
211+
global_roll: None,
212+
pranked_contracts: Default::default(),
213+
global_prank: None,
214+
warped_contracts: Default::default(),
215+
global_warp: None,
216+
elected_contracts: Default::default(),
217+
global_elect: None,
218+
mocked_functions: Default::default(),
219+
spoofed_contracts: Default::default(),
220+
global_spoof: None,
221+
spies: vec![],
222+
detected_events: vec![],
223+
deploy_salt_base: 0,
224+
block_info: Default::default(),
225+
used_resources: Default::default(),
226+
trace_data: TraceData {
227+
current_call_stack: NotEmptyCallStack::from(test_call),
228+
},
229+
}
230+
}
160231
}
161232

162233
impl CheatnetState {
@@ -224,6 +295,27 @@ impl CheatnetState {
224295
}
225296
}
226297

298+
impl TraceData {
299+
pub fn enter_nested_call(&mut self, entry_point: CallEntryPoint) {
300+
let new_call = Rc::new(RefCell::new(CallTrace {
301+
entry_point,
302+
nested_calls: vec![],
303+
}));
304+
let current_call = self.current_call_stack.top();
305+
306+
current_call
307+
.borrow_mut()
308+
.nested_calls
309+
.push(new_call.clone());
310+
311+
self.current_call_stack.push(new_call);
312+
}
313+
314+
pub fn exit_nested_call(&mut self) {
315+
self.current_call_stack.pop();
316+
}
317+
}
318+
227319
fn match_node_response<T: Default>(result: StateResult<T>) -> StateResult<T> {
228320
match result {
229321
Ok(class_hash) => Ok(class_hash),

crates/forge-runner/src/running.rs

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ use crate::test_case_summary::{Single, TestCaseSummary};
1010
use crate::{RunnerConfig, RunnerParams, TestCaseRunnable, CACHE_DIR};
1111
use anyhow::{bail, ensure, Result};
1212
use blockifier::execution::common_hints::ExecutionMode;
13-
use blockifier::execution::entry_point::{
14-
CallEntryPoint, CallType, EntryPointExecutionContext, ExecutionResources,
15-
};
13+
use blockifier::execution::entry_point::{EntryPointExecutionContext, ExecutionResources};
1614
use blockifier::execution::execution_utils::ReadOnlySegments;
1715
use blockifier::execution::syscalls::hint_processor::SyscallHintProcessor;
1816
use blockifier::state::cached_state::CachedState;
@@ -27,6 +25,7 @@ use cairo_vm::types::relocatable::Relocatable;
2725
use cairo_vm::vm::vm_core::VirtualMachine;
2826
use camino::Utf8Path;
2927
use cheatnet::constants as cheatnet_constants;
28+
use cheatnet::constants::build_test_entry_point;
3029
use cheatnet::forking::state::ForkStateReader;
3130
use cheatnet::runtime_extensions::call_to_blockifier_runtime_extension::CallToBlockifierExtension;
3231
use cheatnet::runtime_extensions::cheatable_starknet_runtime_extension::CheatableStarknetRuntimeExtension;
@@ -39,13 +38,6 @@ use itertools::chain;
3938
use runtime::starknet::context;
4039
use runtime::starknet::context::BlockInfo;
4140
use runtime::{ExtendedRuntime, StarknetRuntime};
42-
use starknet::core::utils::get_selector_from_name;
43-
use starknet_api::core::PatriciaKey;
44-
use starknet_api::core::{ContractAddress, EntryPointSelector};
45-
use starknet_api::deprecated_contract_class::EntryPointType;
46-
use starknet_api::hash::StarkHash;
47-
use starknet_api::patricia_key;
48-
use starknet_api::transaction::Calldata;
4941
use tokio::sync::mpsc::Sender;
5042
use tokio::task::JoinHandle;
5143

@@ -152,22 +144,7 @@ fn build_syscall_handler<'a>(
152144
execution_resources: &'a mut ExecutionResources,
153145
context: &'a mut EntryPointExecutionContext,
154146
) -> SyscallHintProcessor<'a> {
155-
let test_selector = get_selector_from_name("TEST_CONTRACT_SELECTOR").unwrap();
156-
let entry_point_selector =
157-
EntryPointSelector(StarkHash::new(test_selector.to_bytes_be()).unwrap());
158-
let entry_point = CallEntryPoint {
159-
class_hash: None,
160-
code_address: Some(ContractAddress(patricia_key!(
161-
cheatnet_constants::TEST_ADDRESS
162-
))),
163-
entry_point_type: EntryPointType::External,
164-
entry_point_selector,
165-
calldata: Calldata(Arc::new(vec![])),
166-
storage_address: ContractAddress(patricia_key!(cheatnet_constants::TEST_ADDRESS)),
167-
caller_address: ContractAddress::default(),
168-
call_type: CallType::Call,
169-
initial_gas: u64::MAX,
170-
};
147+
let entry_point = build_test_entry_point();
171148

172149
SyscallHintProcessor::new(
173150
blockifier_state,

0 commit comments

Comments
 (0)