Skip to content

Commit 2643a34

Browse files
authored
Add gas report logic (#3841)
Towards #3660
1 parent ae2bfed commit 2643a34

File tree

16 files changed

+177
-12
lines changed

16 files changed

+177
-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: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use crate::gas::stats::GasStats;
2+
use cheatnet::trace_data::{CallTrace, CallTraceNode};
3+
use debugging::ContractsDataStore;
4+
use starknet_api::core::{ClassHash, EntryPointSelector};
5+
use starknet_api::execution_resources::GasVector;
6+
use std::collections::BTreeMap;
7+
8+
type ContractName = String;
9+
type Selector = String;
10+
11+
#[derive(Debug, Clone)]
12+
pub struct SingleTestGasInfo {
13+
pub gas_used: GasVector,
14+
pub report_data: Option<ReportData>,
15+
}
16+
17+
#[derive(Debug, Clone, Default)]
18+
pub struct ReportData(BTreeMap<ContractName, ContractInfo>);
19+
20+
#[derive(Debug, Clone, Default)]
21+
pub struct ContractInfo {
22+
pub(super) gas_used: GasVector,
23+
pub(super) functions: BTreeMap<Selector, SelectorReportData>,
24+
}
25+
26+
#[derive(Debug, Clone, Default)]
27+
pub struct SelectorReportData {
28+
pub(super) gas_stats: GasStats,
29+
pub(super) n_calls: u64,
30+
pub(super) records: Vec<u64>,
31+
}
32+
33+
impl SingleTestGasInfo {
34+
#[must_use]
35+
pub(crate) fn new(gas_used: GasVector) -> Self {
36+
Self {
37+
gas_used,
38+
report_data: None,
39+
}
40+
}
41+
42+
pub(crate) fn get_with_report_data(
43+
self,
44+
trace: &CallTrace,
45+
contracts_data: &ContractsDataStore,
46+
) -> Self {
47+
let mut report_data = ReportData::default();
48+
let mut stack = trace.nested_calls.clone();
49+
50+
while let Some(call_trace_node) = stack.pop() {
51+
if let CallTraceNode::EntryPointCall(call) = call_trace_node {
52+
let call = call.borrow();
53+
let class_hash = call.entry_point.class_hash.expect(
54+
"class_hash should be set in `fn execute_call_entry_point` in cheatnet",
55+
);
56+
57+
let contract_name = get_contract_name(contracts_data, class_hash);
58+
let selector = get_selector(contracts_data, call.entry_point.entry_point_selector);
59+
let gas = call
60+
.gas_report_data
61+
.as_ref()
62+
.expect("Gas report data must be updated after test execution")
63+
.get_gas();
64+
65+
report_data.update_entry(contract_name, selector, gas);
66+
stack.extend(call.nested_calls.clone());
67+
}
68+
}
69+
report_data.finalize();
70+
71+
Self {
72+
gas_used: self.gas_used,
73+
report_data: Some(report_data),
74+
}
75+
}
76+
}
77+
78+
impl ReportData {
79+
fn update_entry(
80+
&mut self,
81+
contract_name: ContractName,
82+
selector: Selector,
83+
gas_used: GasVector,
84+
) {
85+
let contract_info = self.0.entry(contract_name).or_default();
86+
87+
let current_gas = contract_info.gas_used;
88+
contract_info.gas_used = current_gas.checked_add(gas_used).unwrap_or_else(|| {
89+
panic!("Gas addition overflow when adding {gas_used:?} to {current_gas:?}.")
90+
});
91+
92+
let entry = contract_info.functions.entry(selector).or_default();
93+
entry.records.push(gas_used.l2_gas.0);
94+
entry.n_calls += 1;
95+
}
96+
97+
fn finalize(&mut self) {
98+
for contract_info in self.0.values_mut() {
99+
for gas_info in contract_info.functions.values_mut() {
100+
gas_info.gas_stats = GasStats::new(&gas_info.records);
101+
}
102+
}
103+
}
104+
}
105+
106+
fn get_contract_name(contracts_data: &ContractsDataStore, class_hash: ClassHash) -> ContractName {
107+
contracts_data
108+
.get_contract_name(&class_hash)
109+
.map_or("forked contract", |name| name.0.as_str())
110+
.to_string()
111+
}
112+
113+
fn get_selector(contracts_data: &ContractsDataStore, selector: EntryPointSelector) -> Selector {
114+
contracts_data
115+
.get_selector(&selector)
116+
.expect("`Selector` should be present")
117+
.0
118+
.clone()
119+
}

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::SingleTestGasInfo;
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 = SingleTestGasInfo;
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 = SingleTestGasInfo::new(gas_used);
306+
let gas_info = if gas_report_enabled {
307+
gas_info.get_with_report_data(
308+
&call_trace.borrow(),
309+
&ContractsDataStore::new(contracts_data, &fork_data),
310+
)
311+
} else {
312+
gas_info
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: true,
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)