Skip to content

Commit d5c64c6

Browse files
authored
Add Display for trace info (#1611)
<!-- Reference any GitHub issues resolved by this PR --> Closes #1464 ## Introduced changes <!-- A brief description of the changes --> - Adds nice indented output for formatting trace info ## Checklist <!-- Make sure all of these are complete --> - [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`
1 parent ad3ff13 commit d5c64c6

File tree

6 files changed

+311
-0
lines changed

6 files changed

+311
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "trace_info"
3+
version = "0.1.0"
4+
5+
[dependencies]
6+
starknet = "2.4.0"
7+
snforge_std = { path = "../../../../../snforge_std" }
8+
9+
[[target.starknet-contract]]
10+
sierra = true
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use starknet::ContractAddress;
2+
3+
#[derive(Drop, Serde, Clone)]
4+
struct RecursiveCall {
5+
contract_address: ContractAddress,
6+
payload: Array<RecursiveCall>
7+
}
8+
9+
#[starknet::interface]
10+
trait RecursiveCaller<T> {
11+
fn execute_calls(self: @T, calls: Array<RecursiveCall>);
12+
}
13+
14+
#[starknet::contract]
15+
mod SimpleContract {
16+
use core::array::ArrayTrait;
17+
use core::traits::Into;
18+
use starknet::ContractAddress;
19+
use starknet::get_contract_address;
20+
use super::{
21+
RecursiveCaller, RecursiveCallerDispatcher, RecursiveCallerDispatcherTrait, RecursiveCall
22+
};
23+
24+
25+
#[storage]
26+
struct Storage {}
27+
28+
29+
#[abi(embed_v0)]
30+
impl RecursiveCallerImpl of RecursiveCaller<ContractState> {
31+
fn execute_calls(self: @ContractState, calls: Array<RecursiveCall>) {
32+
let mut i = 0;
33+
while i < calls
34+
.len() {
35+
let serviced_call = calls.at(i);
36+
RecursiveCallerDispatcher {
37+
contract_address: serviced_call.contract_address.clone()
38+
}
39+
.execute_calls(serviced_call.payload.clone());
40+
i = i + 1;
41+
}
42+
}
43+
}
44+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use snforge_std::{declare, ContractClassTrait, PrintTrait};
2+
use snforge_std::trace::{get_call_trace};
3+
4+
use trace_info::{RecursiveCallerDispatcher, RecursiveCallerDispatcherTrait, RecursiveCall};
5+
6+
#[test]
7+
fn test_trace_print() {
8+
let sc = declare('SimpleContract');
9+
10+
let contract_address_A = sc.deploy(@array![]).unwrap();
11+
let contract_address_B = sc.deploy(@array![]).unwrap();
12+
let contract_address_C = sc.deploy(@array![]).unwrap();
13+
14+
let calls = array![
15+
RecursiveCall {
16+
contract_address: contract_address_B,
17+
payload: array![
18+
RecursiveCall { contract_address: contract_address_C, payload: array![], },
19+
RecursiveCall { contract_address: contract_address_C, payload: array![], }
20+
]
21+
},
22+
RecursiveCall { contract_address: contract_address_C, payload: array![], }
23+
];
24+
25+
RecursiveCallerDispatcher { contract_address: contract_address_A }.execute_calls(calls);
26+
27+
println!("{}", get_call_trace());
28+
}

crates/forge/tests/e2e/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ mod forking;
99
mod fuzzing;
1010
mod io_operations;
1111
mod running;
12+
mod trace;
1213
mod trace_data;
1314
mod workspaces;

crates/forge/tests/e2e/trace.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use crate::assert_stdout_contains;
2+
use crate::e2e::common::runner::{setup_package, test_runner};
3+
use indoc::indoc;
4+
5+
#[test]
6+
fn trace_info_print() {
7+
let temp = setup_package("trace");
8+
let snapbox = test_runner();
9+
10+
let output = snapbox.current_dir(&temp).assert().success();
11+
12+
assert_stdout_contains!(
13+
output,
14+
indoc! {r"
15+
[..]Compiling[..]
16+
[..]Finished[..]
17+
18+
Collected 1 test(s) from trace_info package
19+
Running 0 test(s) from src/
20+
Running 1 test(s) from tests/
21+
Entry point type: External
22+
Selector: [..]
23+
Calldata: []
24+
Storage address: [..]
25+
Caller address: 0
26+
Call type: Call
27+
Nested Calls: [
28+
(
29+
Entry point type: External
30+
Selector: [..]
31+
Calldata: [..]
32+
Storage address: [..]
33+
Caller address: [..]
34+
Call type: Call
35+
Nested Calls: [
36+
(
37+
Entry point type: External
38+
Selector: [..]
39+
Calldata: [..]
40+
Storage address: [..]
41+
Caller address: [..]
42+
Call type: Call
43+
Nested Calls: [
44+
(
45+
Entry point type: External
46+
Selector: [..]
47+
Calldata: [0]
48+
Storage address: [..]
49+
Caller address: [..]
50+
Call type: Call
51+
Nested Calls: []
52+
),
53+
(
54+
Entry point type: External
55+
Selector: [..]
56+
Calldata: [0]
57+
Storage address: [..]
58+
Caller address: [..]
59+
Call type: Call
60+
Nested Calls: []
61+
)
62+
]
63+
),
64+
(
65+
Entry point type: External
66+
Selector: [..]
67+
Calldata: [0]
68+
Storage address: [..]
69+
Caller address: [..]
70+
Call type: Call
71+
Nested Calls: []
72+
)
73+
]
74+
)
75+
]
76+
77+
[PASS] tests::test_trace::test_trace_print (gas: [..]
78+
Tests: 1 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out
79+
"}
80+
);
81+
}

snforge_std/src/trace.cairo

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,150 @@ fn get_call_trace() -> CallTrace {
3434
let mut output = cheatcode::<'get_call_trace'>(array![].span());
3535
Serde::deserialize(ref output).unwrap()
3636
}
37+
38+
use core::fmt::{Display, Formatter, Error, Debug};
39+
40+
impl DisplayEntryPointType of Display<EntryPointType> {
41+
fn fmt(self: @EntryPointType, ref f: Formatter) -> Result<(), Error> {
42+
let str: ByteArray = match self {
43+
EntryPointType::Constructor => "Constructor",
44+
EntryPointType::External => "External",
45+
EntryPointType::L1Handler => "L1 Handler",
46+
};
47+
f.buffer.append(@str);
48+
Result::Ok(())
49+
}
50+
}
51+
52+
impl DisplayCallType of Display<CallType> {
53+
fn fmt(self: @CallType, ref f: Formatter) -> Result<(), Error> {
54+
let str: ByteArray = match self {
55+
CallType::Call => "Call",
56+
CallType::Delegate => "Delegate",
57+
};
58+
f.buffer.append(@str);
59+
Result::Ok(())
60+
}
61+
}
62+
63+
impl DisplayCallTrace of Display<CallTrace> {
64+
fn fmt(self: @CallTrace, ref f: Formatter) -> Result<(), Error> {
65+
Display::fmt(@IndentedCallTrace { struct_ref: self, base_indents: 0 }, ref f).unwrap();
66+
Result::Ok(())
67+
}
68+
}
69+
70+
#[derive(Drop)]
71+
struct Indented<T> {
72+
struct_ref: @T,
73+
base_indents: u8,
74+
}
75+
76+
type IndentedEntryPoint = Indented<CallEntryPoint>;
77+
type IndentedCallTraceArray = Indented<Array<CallTrace>>;
78+
type IndentedCallTrace = Indented<CallTrace>;
79+
80+
81+
impl DisplayIndentedCallTrace of Display<Indented<CallTrace>> {
82+
fn fmt(self: @Indented<CallTrace>, ref f: Formatter) -> Result<(), Error> {
83+
Display::fmt(
84+
@IndentedEntryPoint {
85+
base_indents: *self.base_indents, struct_ref: *self.struct_ref.entry_point
86+
},
87+
ref f
88+
)
89+
.unwrap();
90+
write!(f, "\n").unwrap();
91+
write_indents_to_formatter(*self.base_indents, ref f);
92+
write!(f, "Nested Calls: [").unwrap();
93+
if (*self.struct_ref.nested_calls).len() > 0 {
94+
write!(f, "\n").unwrap();
95+
Display::fmt(
96+
@IndentedCallTraceArray {
97+
base_indents: (*self.base_indents) + 1,
98+
struct_ref: *self.struct_ref.nested_calls
99+
},
100+
ref f
101+
)
102+
.unwrap();
103+
write!(f, "\n").unwrap();
104+
write_indents_to_formatter(*self.base_indents, ref f);
105+
}
106+
107+
write!(f, "]").unwrap();
108+
Result::Ok(())
109+
}
110+
}
111+
112+
impl DisplayIndentedCallTraceArray of Display<Indented<Array<CallTrace>>> {
113+
fn fmt(self: @Indented<Array<CallTrace>>, ref f: Formatter) -> Result<(), Error> {
114+
let mut i: u32 = 0;
115+
let trace_len = (*self.struct_ref).len();
116+
while i < trace_len {
117+
write_indents_to_formatter(*self.base_indents, ref f);
118+
write!(f, "(\n").unwrap();
119+
120+
Display::fmt(
121+
@IndentedCallTrace {
122+
base_indents: *self.base_indents + 1, struct_ref: (*self.struct_ref)[i]
123+
},
124+
ref f
125+
)
126+
.unwrap();
127+
write!(f, "\n").unwrap();
128+
write_indents_to_formatter(*self.base_indents, ref f);
129+
write!(f, ")").unwrap();
130+
131+
i = i + 1;
132+
if i != trace_len {
133+
write!(f, ",\n").unwrap();
134+
}
135+
};
136+
137+
Result::Ok(())
138+
}
139+
}
140+
141+
impl DisplayIndentedEntryPoint of Display<Indented<CallEntryPoint>> {
142+
fn fmt(self: @Indented<CallEntryPoint>, ref f: Formatter) -> Result<(), Error> {
143+
write_indents_to_formatter(*self.base_indents, ref f);
144+
write!(f, "Entry point type: ")?;
145+
Display::fmt(*self.struct_ref.entry_point_type, ref f)?;
146+
147+
write!(f, "\n")?;
148+
write_indents_to_formatter(*self.base_indents, ref f);
149+
write!(f, "Selector: ")?;
150+
Display::fmt(*self.struct_ref.entry_point_selector, ref f)?;
151+
152+
write!(f, "\n")?;
153+
write_indents_to_formatter(*self.base_indents, ref f);
154+
write!(f, "Calldata: ")?;
155+
Debug::fmt(*self.struct_ref.calldata, ref f)?;
156+
157+
write!(f, "\n")?;
158+
write_indents_to_formatter(*self.base_indents, ref f);
159+
write!(f, "Storage address: ")?;
160+
Debug::fmt(*self.struct_ref.contract_address, ref f)?;
161+
162+
write!(f, "\n")?;
163+
write_indents_to_formatter(*self.base_indents, ref f);
164+
write!(f, "Caller address: ")?;
165+
Debug::fmt(*self.struct_ref.caller_address, ref f)?;
166+
167+
write!(f, "\n")?;
168+
write_indents_to_formatter(*self.base_indents, ref f);
169+
write!(f, "Call type: ")?;
170+
Display::fmt(*self.struct_ref.call_type, ref f)?;
171+
172+
Result::Ok(())
173+
}
174+
}
175+
176+
177+
fn write_indents_to_formatter(indents: u8, ref f: Formatter) {
178+
let mut i: u8 = 0;
179+
while i < indents {
180+
write!(f, " ").unwrap();
181+
i = i + 1;
182+
}
183+
}

0 commit comments

Comments
 (0)