Skip to content

Commit 6a477e8

Browse files
authored
Display gas report table for fork contracts (#3876)
Towards #3660
1 parent c76eb53 commit 6a477e8

File tree

4 files changed

+111
-20
lines changed

4 files changed

+111
-20
lines changed

crates/forge-runner/src/gas.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use starknet_api::transaction::fields::GasVectorComputationMode;
1818

1919
pub mod report;
2020
pub mod stats;
21+
mod utils;
2122

2223
#[tracing::instrument(skip_all, level = "debug")]
2324
pub fn calculate_used_gas(

crates/forge-runner/src/gas/report.rs

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::gas::stats::GasStats;
2+
use crate::gas::utils::shorten_felt;
23
use cheatnet::trace_data::{CallTrace, CallTraceNode};
34
use comfy_table::modifiers::UTF8_ROUND_CORNERS;
45
use comfy_table::{Attribute, Cell, Color, Table};
@@ -12,14 +13,20 @@ use std::fmt::Display;
1213
type ContractName = String;
1314
type Selector = String;
1415

16+
#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
17+
pub enum ContractId {
18+
LocalContract(ContractName),
19+
ForkedContract(ClassHash),
20+
}
21+
1522
#[derive(Debug, Clone)]
1623
pub struct SingleTestGasInfo {
1724
pub gas_used: GasVector,
1825
pub report_data: Option<ReportData>,
1926
}
2027

2128
#[derive(Debug, Clone, Default)]
22-
pub struct ReportData(BTreeMap<ContractName, ContractInfo>);
29+
pub struct ReportData(BTreeMap<ContractId, ContractInfo>);
2330

2431
#[derive(Debug, Clone, Default)]
2532
pub struct ContractInfo {
@@ -58,15 +65,15 @@ impl SingleTestGasInfo {
5865
"class_hash should be set in `fn execute_call_entry_point` in cheatnet",
5966
);
6067

61-
let contract_name = get_contract_name(contracts_data, class_hash);
68+
let contract_id = get_contract_id(contracts_data, class_hash);
6269
let selector = get_selector(contracts_data, call.entry_point.entry_point_selector);
6370
let gas = call
6471
.gas_report_data
6572
.as_ref()
6673
.expect("Gas report data must be updated after test execution")
6774
.get_gas();
6875

69-
report_data.update_entry(contract_name, selector, gas);
76+
report_data.update_entry(contract_id, selector, gas);
7077
stack.extend(call.nested_calls.clone());
7178
}
7279
}
@@ -80,13 +87,8 @@ impl SingleTestGasInfo {
8087
}
8188

8289
impl ReportData {
83-
fn update_entry(
84-
&mut self,
85-
contract_name: ContractName,
86-
selector: Selector,
87-
gas_used: GasVector,
88-
) {
89-
let contract_info = self.0.entry(contract_name).or_default();
90+
fn update_entry(&mut self, contract_id: ContractId, selector: Selector, gas_used: GasVector) {
91+
let contract_info = self.0.entry(contract_id).or_default();
9092

9193
let current_gas = contract_info.gas_used;
9294
contract_info.gas_used = current_gas.checked_add(gas_used).unwrap_or_else(|| {
@@ -124,11 +126,26 @@ impl Display for ReportData {
124126
}
125127
}
126128

127-
fn get_contract_name(contracts_data: &ContractsDataStore, class_hash: ClassHash) -> ContractName {
128-
contracts_data
129-
.get_contract_name(&class_hash)
130-
.map_or("forked contract", |name| name.0.as_str())
131-
.to_string()
129+
impl Display for ContractId {
130+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131+
let name = match self {
132+
ContractId::LocalContract(name) => format!("{name} Contract"),
133+
ContractId::ForkedContract(class_hash) => {
134+
format!(
135+
"forked contract\n(class hash: {})",
136+
shorten_felt(class_hash.0)
137+
)
138+
}
139+
};
140+
write!(f, "{name}")
141+
}
142+
}
143+
144+
fn get_contract_id(contracts_data: &ContractsDataStore, class_hash: ClassHash) -> ContractId {
145+
match contracts_data.get_contract_name(&class_hash) {
146+
Some(name) => ContractId::LocalContract(name.0.clone()),
147+
None => ContractId::ForkedContract(class_hash),
148+
}
132149
}
133150

134151
fn get_selector(contracts_data: &ContractsDataStore, selector: EntryPointSelector) -> Selector {
@@ -139,13 +156,11 @@ fn get_selector(contracts_data: &ContractsDataStore, selector: EntryPointSelecto
139156
.clone()
140157
}
141158

142-
pub fn format_table_output(contract_info: &ContractInfo, name: &ContractName) -> Table {
159+
pub fn format_table_output(contract_info: &ContractInfo, contract_id: &ContractId) -> Table {
143160
let mut table = Table::new();
144161
table.apply_modifier(UTF8_ROUND_CORNERS);
145162

146-
table.set_header(vec![
147-
Cell::new(format!("{name} Contract")).fg(Color::Magenta),
148-
]);
163+
table.set_header(vec![Cell::new(contract_id.to_string()).fg(Color::Magenta)]);
149164
table.add_row(vec![
150165
Cell::new("Function Name").add_attribute(Attribute::Bold),
151166
Cell::new("Min").add_attribute(Attribute::Bold),
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use starknet_types_core::felt::Felt;
2+
3+
pub(super) fn shorten_felt(felt: Felt) -> String {
4+
let padded = format!("{felt:#066x}");
5+
let first = &padded[..6];
6+
let last = &padded[padded.len() - 4..];
7+
format!("{first}…{last}")
8+
}
9+
10+
#[cfg(test)]
11+
mod tests {
12+
use super::*;
13+
14+
#[test]
15+
fn test_long() {
16+
let felt = Felt::from_hex_unchecked(
17+
"0x01c902da594beda43db10142ecf1fc3a098b56e8d95f3cd28587a0c6ba05a451",
18+
);
19+
assert_eq!("0x01c9…a451", &shorten_felt(felt));
20+
}
21+
22+
#[test]
23+
fn test_short() {
24+
let felt = Felt::from_hex_unchecked("0x123");
25+
assert_eq!("0x0000…0123", &shorten_felt(felt));
26+
}
27+
}

crates/forge/tests/e2e/gas_report.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::e2e::common::runner::{setup_package, test_runner};
1+
use crate::e2e::common::runner::{
2+
BASE_FILE_PATTERNS, Package, setup_package, setup_package_with_file_patterns, test_runner,
3+
};
24
use indoc::indoc;
35
use shared::test_utils::output_assert::assert_stdout_contains;
46

@@ -116,6 +118,52 @@ fn multiple_contracts_and_constructor() {
116118
);
117119
}
118120

121+
#[test]
122+
fn fork() {
123+
let temp =
124+
setup_package_with_file_patterns(Package::Name("forking".to_string()), BASE_FILE_PATTERNS);
125+
126+
let output = test_runner(&temp)
127+
.arg("test_track_resources")
128+
.arg("--gas-report")
129+
.assert()
130+
.code(0);
131+
132+
assert_stdout_contains(
133+
output,
134+
indoc! {r"
135+
[..]Compiling[..]
136+
[..]Finished[..]
137+
138+
Collected 1 test(s) from forking package
139+
Running 1 test(s) from src/
140+
[PASS] forking::tests::test_track_resources (l1_gas: ~0, l1_data_gas: ~[..], l2_gas: ~[..])
141+
╭---------------------------+-------+-------+-------+---------+---------╮
142+
| forked contract | | | | | |
143+
| (class hash: 0x06a7…1550) | | | | | |
144+
+=======================================================================+
145+
| Function Name | Min | Max | Avg | Std Dev | # Calls |
146+
|---------------------------+-------+-------+-------+---------+---------|
147+
| get_balance | 40000 | 40000 | 40000 | 0 | 1 |
148+
|---------------------------+-------+-------+-------+---------+---------|
149+
| increase_balance | 40000 | 40000 | 40000 | 0 | 1 |
150+
╰---------------------------+-------+-------+-------+---------+---------╯
151+
╭---------------------------+-------+-------+-------+---------+---------╮
152+
| forked contract | | | | | |
153+
| (class hash: 0x07aa…af4b) | | | | | |
154+
+=======================================================================+
155+
| Function Name | Min | Max | Avg | Std Dev | # Calls |
156+
|---------------------------+-------+-------+-------+---------+---------|
157+
| get_balance | 13840 | 13840 | 13840 | 0 | 1 |
158+
|---------------------------+-------+-------+-------+---------+---------|
159+
| increase_balance | 25840 | 25840 | 25840 | 0 | 1 |
160+
╰---------------------------+-------+-------+-------+---------+---------╯
161+
162+
Tests: 1 passed, 0 failed, 0 ignored, [..] filtered out
163+
"},
164+
);
165+
}
166+
119167
#[test]
120168
fn no_transactions() {
121169
let temp = setup_package("simple_package");

0 commit comments

Comments
 (0)