Skip to content

Commit 8a9f925

Browse files
committed
Add gas report logic
1 parent 84c65d9 commit 8a9f925

File tree

16 files changed

+178
-12
lines changed

16 files changed

+178
-12
lines changed

crates/cheatnet/src/trace_data.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ impl GasReportData {
240240
}
241241
}
242242

243-
pub fn get_gas(&self) -> &GasVector {
244-
self.partial_gas_usage.get_or_init(|| {
243+
pub fn get_gas(&self) -> GasVector {
244+
*self.partial_gas_usage.get_or_init(|| {
245245
self.execution_summary.clone().to_partial_gas_vector(
246246
VersionedConstants::latest_constants(),
247247
&GasVectorComputationMode::All,

crates/debugging/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ mod contracts_data_store;
77
mod trace;
88
mod tree;
99

10+
pub use contracts_data_store::ContractsDataStore;
1011
pub use trace::components::{Component, Components};
11-
pub use trace::{context::Context, types::Trace};
12+
pub use trace::{context::Context, types::ContractName, types::Trace};

crates/forge-runner/src/forge_config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct OutputConfig {
3434
pub trace_args: TraceArgs,
3535
pub detailed_resources: bool,
3636
pub execution_data_to_save: ExecutionDataToSave,
37+
pub gas_report: bool,
3738
}
3839

3940
#[derive(Debug, PartialEq, Clone, Default)]

crates/forge-runner/src/gas.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use cheatnet::state::ExtendedStateReader;
1616
use starknet_api::execution_resources::{GasAmount, GasVector};
1717
use starknet_api::transaction::fields::GasVectorComputationMode;
1818

19+
pub mod report;
1920
pub mod stats;
2021

2122
#[tracing::instrument(skip_all, level = "debug")]
@@ -149,16 +150,18 @@ pub fn check_available_gas(
149150
..
150151
} if available_gas.is_some_and(|available_gas| {
151152
let av_gas = available_gas.to_gas_vector();
152-
gas_info.l1_gas > av_gas.l1_gas
153-
|| gas_info.l1_data_gas > av_gas.l1_data_gas
154-
|| gas_info.l2_gas > av_gas.l2_gas
153+
gas_info.gas_used.l1_gas > av_gas.l1_gas
154+
|| gas_info.gas_used.l1_data_gas > av_gas.l1_data_gas
155+
|| gas_info.gas_used.l2_gas > av_gas.l2_gas
155156
}) =>
156157
{
157158
TestCaseSummary::Failed {
158159
name,
159160
msg: Some(format!(
160161
"\n\tTest cost exceeded the available gas. Consumed l1_gas: ~{}, l1_data_gas: ~{}, l2_gas: ~{}",
161-
gas_info.l1_gas, gas_info.l1_data_gas, gas_info.l2_gas
162+
gas_info.gas_used.l1_gas,
163+
gas_info.gas_used.l1_data_gas,
164+
gas_info.gas_used.l2_gas
162165
)),
163166
fuzzer_args: Vec::default(),
164167
test_statistics: (),
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use crate::gas::stats::GasStats;
2+
use cheatnet::trace_data::{CallTrace, CallTraceNode};
3+
use debugging::ContractName as DebuggingContractName;
4+
use debugging::ContractsDataStore;
5+
use starknet_api::core::{ClassHash, EntryPointSelector};
6+
use starknet_api::execution_resources::GasVector;
7+
use std::collections::BTreeMap;
8+
9+
type ContractName = String;
10+
type Selector = String;
11+
12+
#[derive(Debug, Clone)]
13+
pub struct GasSingleTestInfo {
14+
pub gas_used: GasVector,
15+
pub report_data: BTreeMap<ContractName, ContractInfo>,
16+
}
17+
18+
#[derive(Debug, Clone, Default)]
19+
pub struct ContractInfo {
20+
pub gas_used: GasVector,
21+
pub functions: BTreeMap<Selector, SelectorReportData>,
22+
}
23+
24+
#[derive(Debug, Clone, Default)]
25+
pub struct SelectorReportData {
26+
pub gas_stats: GasStats,
27+
pub n_calls: u64,
28+
pub records: Vec<u64>,
29+
}
30+
31+
impl GasSingleTestInfo {
32+
#[must_use]
33+
pub fn new(gas_used: GasVector) -> Self {
34+
Self {
35+
gas_used,
36+
report_data: BTreeMap::new(),
37+
}
38+
}
39+
40+
#[must_use]
41+
pub fn new_with_report(
42+
gas_used: GasVector,
43+
call_trace: &CallTrace,
44+
contracts_data: &ContractsDataStore,
45+
) -> Self {
46+
Self {
47+
gas_used,
48+
report_data: BTreeMap::new(),
49+
}
50+
.collect_gas_data(call_trace, contracts_data)
51+
}
52+
53+
fn collect_gas_data(mut self, trace: &CallTrace, contracts_data: &ContractsDataStore) -> Self {
54+
let mut stack = trace.nested_calls.clone();
55+
56+
while let Some(call_trace_node) = stack.pop() {
57+
if let CallTraceNode::EntryPointCall(call) = call_trace_node {
58+
let call = call.borrow();
59+
let class_hash = call.entry_point.class_hash.expect(
60+
"class_hash should be set in `fn execute_call_entry_point` in cheatnet",
61+
);
62+
63+
let contract_name = get_contract_name(contracts_data, class_hash);
64+
let selector = get_selector(contracts_data, call.entry_point.entry_point_selector);
65+
let gas = call
66+
.gas_report_data
67+
.as_ref()
68+
.expect("Gas report data must be updated after test execution")
69+
.get_gas();
70+
71+
self.update_entry(contract_name, selector, gas);
72+
stack.extend(call.nested_calls.clone());
73+
}
74+
}
75+
self.finalize();
76+
self
77+
}
78+
79+
fn update_entry(
80+
&mut self,
81+
contract_name: ContractName,
82+
selector: Selector,
83+
gas_used: GasVector,
84+
) {
85+
let contract_info = self.report_data.entry(contract_name).or_default();
86+
87+
if let Some(gas) = contract_info.gas_used.checked_add(gas_used) {
88+
contract_info.gas_used = gas;
89+
}
90+
91+
let entry = contract_info.functions.entry(selector).or_default();
92+
entry.records.push(gas_used.l2_gas.0);
93+
entry.n_calls += 1;
94+
}
95+
96+
fn finalize(&mut self) {
97+
for contract_info in self.report_data.values_mut() {
98+
for gas_info in contract_info.functions.values_mut() {
99+
gas_info.gas_stats = GasStats::new(&gas_info.records);
100+
}
101+
}
102+
}
103+
}
104+
105+
fn get_contract_name(contracts_data: &ContractsDataStore, class_hash: ClassHash) -> ContractName {
106+
contracts_data
107+
.get_contract_name(&class_hash)
108+
.cloned()
109+
.unwrap_or_else(|| DebuggingContractName("forked contract".to_string()))
110+
.0
111+
.clone()
112+
}
113+
114+
fn get_selector(contracts_data: &ContractsDataStore, selector: EntryPointSelector) -> Selector {
115+
contracts_data
116+
.get_selector(&selector)
117+
.expect("`Selector` should be present")
118+
.0
119+
.clone()
120+
}

crates/forge-runner/src/messages.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ impl TestResultMessage {
7979
AnyTestCaseSummary::Single(TestCaseSummary::Passed { gas_info, .. }) => {
8080
format!(
8181
" (l1_gas: ~{}, l1_data_gas: ~{}, l2_gas: ~{})",
82-
gas_info.l1_gas, gas_info.l1_data_gas, gas_info.l2_gas
82+
gas_info.gas_used.l1_gas,
83+
gas_info.gas_used.l1_data_gas,
84+
gas_info.gas_used.l2_gas
8385
)
8486
}
8587
_ => String::new(),

crates/forge-runner/src/running.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ fn extract_test_case_summary(
409409
contracts_data,
410410
versioned_program_path,
411411
trace_args,
412+
forge_config.output_config.gas_report,
412413
),
413414
RunResult::Error(run_error) => {
414415
let mut message = format!(

crates/forge-runner/src/test_case_summary.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::build_trace_data::build_profiler_call_trace;
33
use crate::debugging::{TraceArgs, build_debugging_trace};
44
use crate::expected_result::{ExpectedPanicValue, ExpectedTestResult};
55
use crate::gas::check_available_gas;
6+
use crate::gas::report::GasSingleTestInfo;
67
use crate::gas::stats::GasStats;
78
use crate::package_tests::with_config_resolved::TestCaseWithResolvedConfig;
89
use crate::running::{RunCompleted, RunStatus};
@@ -12,6 +13,7 @@ use cheatnet::runtime_extensions::call_to_blockifier_runtime_extension::rpc::Use
1213
use cheatnet::runtime_extensions::forge_runtime_extension::contracts_data::ContractsData;
1314
use conversions::byte_array::ByteArray;
1415
use conversions::felt::ToShortString;
16+
use debugging::ContractsDataStore;
1517
use shared::utils::build_readable_text;
1618
use starknet_api::execution_resources::GasVector;
1719
use starknet_types_core::felt::Felt;
@@ -82,7 +84,7 @@ impl TestType for Fuzzing {
8284
#[derive(Debug, PartialEq, Clone)]
8385
pub struct Single;
8486
impl TestType for Single {
85-
type GasInfo = GasVector;
87+
type GasInfo = GasSingleTestInfo;
8688
type TestStatistics = ();
8789
type TraceData = VersionedProfilerCallTrace;
8890
}
@@ -195,7 +197,7 @@ impl TestCaseSummary<Fuzzing> {
195197
let gas_usages: Vec<GasVector> = results
196198
.into_iter()
197199
.map(|a| match a {
198-
TestCaseSummary::Passed { gas_info, .. } => gas_info,
200+
TestCaseSummary::Passed { gas_info, .. } => gas_info.gas_used,
199201
_ => unreachable!(),
200202
})
201203
.collect();
@@ -278,7 +280,7 @@ impl TestCaseSummary<Single> {
278280
RunCompleted {
279281
status,
280282
call_trace,
281-
gas_used: gas_info,
283+
gas_used,
282284
used_resources,
283285
encountered_errors,
284286
fuzzer_args,
@@ -288,6 +290,7 @@ impl TestCaseSummary<Single> {
288290
contracts_data: &ContractsData,
289291
versioned_program_path: &Utf8Path,
290292
trace_args: &TraceArgs,
293+
gas_report_enabled: bool,
291294
) -> Self {
292295
let name = test_case.name.clone();
293296

@@ -299,6 +302,16 @@ impl TestCaseSummary<Single> {
299302
&fork_data,
300303
);
301304

305+
let gas_info = if gas_report_enabled {
306+
GasSingleTestInfo::new_with_report(
307+
gas_used,
308+
&call_trace.borrow(),
309+
&ContractsDataStore::new(contracts_data, &fork_data),
310+
)
311+
} else {
312+
GasSingleTestInfo::new(gas_used)
313+
};
314+
302315
match status {
303316
RunStatus::Success(data) => match &test_case.config.expected_result {
304317
ExpectedTestResult::Success => {

crates/forge/src/combine_configs.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub fn combine_configs(
2121
save_trace_data: bool,
2222
build_profile: bool,
2323
coverage: bool,
24+
gas_report: bool,
2425
max_n_steps: Option<u32>,
2526
tracked_resource: ForgeTrackedResource,
2627
contracts_data: ContractsData,
@@ -58,6 +59,7 @@ pub fn combine_configs(
5859
trace_args,
5960
detailed_resources: detailed_resources || forge_config_from_scarb.detailed_resources,
6061
execution_data_to_save,
62+
gas_report: gas_report || forge_config_from_scarb.gas_report,
6163
}),
6264
}
6365
}
@@ -76,6 +78,7 @@ mod tests {
7678
false,
7779
false,
7880
false,
81+
false,
7982
None,
8083
ForgeTrackedResource::CairoSteps,
8184
ContractsData::default(),
@@ -93,6 +96,7 @@ mod tests {
9396
false,
9497
false,
9598
false,
99+
false,
96100
None,
97101
ForgeTrackedResource::CairoSteps,
98102
ContractsData::default(),
@@ -121,6 +125,7 @@ mod tests {
121125
false,
122126
false,
123127
false,
128+
false,
124129
None,
125130
ForgeTrackedResource::CairoSteps,
126131
ContractsData::default(),
@@ -149,6 +154,7 @@ mod tests {
149154
detailed_resources: false,
150155
execution_data_to_save: ExecutionDataToSave::default(),
151156
trace_args: TraceArgs::default(),
157+
gas_report: false,
152158
}),
153159
}
154160
);
@@ -165,6 +171,7 @@ mod tests {
165171
save_trace_data: true,
166172
build_profile: true,
167173
coverage: true,
174+
gas_report: true,
168175
max_n_steps: Some(1_000_000),
169176
tracked_resource: ForgeTrackedResource::CairoSteps,
170177
};
@@ -177,6 +184,7 @@ mod tests {
177184
false,
178185
false,
179186
false,
187+
false,
180188
None,
181189
ForgeTrackedResource::CairoSteps,
182190
ContractsData::default(),
@@ -210,6 +218,7 @@ mod tests {
210218
additional_args: vec![],
211219
},
212220
trace_args: TraceArgs::default(),
221+
gas_report: false,
213222
}),
214223
}
215224
);
@@ -226,6 +235,7 @@ mod tests {
226235
save_trace_data: false,
227236
build_profile: false,
228237
coverage: false,
238+
gas_report: false,
229239
max_n_steps: Some(1234),
230240
tracked_resource: ForgeTrackedResource::CairoSteps,
231241
};
@@ -237,6 +247,7 @@ mod tests {
237247
true,
238248
true,
239249
true,
250+
true,
240251
Some(1_000_000),
241252
ForgeTrackedResource::CairoSteps,
242253
ContractsData::default(),
@@ -271,6 +282,7 @@ mod tests {
271282
additional_args: vec![],
272283
},
273284
trace_args: TraceArgs::default(),
285+
gas_report: true,
274286
}),
275287
}
276288
);

crates/forge/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ pub struct TestArgs {
213213
#[arg(long, value_enum, default_value_t)]
214214
tracked_resource: ForgeTrackedResource,
215215

216+
/// Display a table of L2 gas breakdown for each contract and selector
217+
#[arg(long)]
218+
gas_report: bool,
219+
216220
/// Additional arguments for cairo-coverage or cairo-profiler
217221
#[arg(last = true)]
218222
additional_args: Vec<OsString>,

0 commit comments

Comments
 (0)