Skip to content

Commit 6b02dec

Browse files
zerosnacksrplusq
authored andcommitted
feat: make --gas-report w/ --json output one JSON blob and add contract_path to output (foundry-rs#9216)
* show resolved contract name * split out helpers * add basic test for multiple selectors resolve to same name w/ different args * collect JSON gas reports and render it as one blob * update tests * avoid unnecessary nesting of non-overloaded function names
1 parent 48211fa commit 6b02dec

File tree

2 files changed

+669
-131
lines changed

2 files changed

+669
-131
lines changed

crates/forge/src/gas_report.rs

+124-46
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use comfy_table::{presets::ASCII_MARKDOWN, *};
99
use foundry_common::{calc, TestFunctionExt};
1010
use foundry_evm::traces::CallKind;
1111
use serde::{Deserialize, Serialize};
12+
use serde_json::json;
1213
use std::{collections::BTreeMap, fmt::Display};
1314
use yansi::Paint;
1415

@@ -156,59 +157,136 @@ impl GasReport {
156157

157158
impl Display for GasReport {
158159
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
159-
for (name, contract) in &self.contracts {
160-
if contract.functions.is_empty() {
161-
trace!(name, "gas report contract without functions");
162-
continue;
163-
}
160+
match self.report_type {
161+
GasReportKind::Markdown => {
162+
for (name, contract) in &self.contracts {
163+
if contract.functions.is_empty() {
164+
trace!(name, "gas report contract without functions");
165+
continue;
166+
}
164167

165-
if self.report_type == GasReportKind::JSON {
166-
writeln!(f, "{}", serde_json::to_string(&contract).unwrap())?;
167-
continue;
168+
let table = self.format_table_output(contract, name);
169+
writeln!(f, "{table}")?;
170+
writeln!(f, "\n")?;
171+
}
172+
}
173+
GasReportKind::JSON => {
174+
writeln!(f, "{}", &self.format_json_output())?;
168175
}
169-
170-
let mut table = Table::new();
171-
table.load_preset(ASCII_MARKDOWN);
172-
table.set_header([Cell::new(format!("{name} contract"))
173-
.add_attribute(Attribute::Bold)
174-
.fg(Color::Green)]);
175-
table.add_row([
176-
Cell::new("Deployment Cost").add_attribute(Attribute::Bold).fg(Color::Cyan),
177-
Cell::new("Deployment Size").add_attribute(Attribute::Bold).fg(Color::Cyan),
178-
]);
179-
table.add_row([contract.gas.to_string(), contract.size.to_string()]);
180-
181-
table.add_row([
182-
Cell::new("Function Name").add_attribute(Attribute::Bold).fg(Color::Magenta),
183-
Cell::new("min").add_attribute(Attribute::Bold).fg(Color::Green),
184-
Cell::new("avg").add_attribute(Attribute::Bold).fg(Color::Yellow),
185-
Cell::new("median").add_attribute(Attribute::Bold).fg(Color::Yellow),
186-
Cell::new("max").add_attribute(Attribute::Bold).fg(Color::Red),
187-
Cell::new("# calls").add_attribute(Attribute::Bold),
188-
]);
189-
contract.functions.iter().for_each(|(fname, sigs)| {
190-
sigs.iter().for_each(|(sig, gas_info)| {
191-
// show function signature if overloaded else name
192-
let fn_display =
193-
if sigs.len() == 1 { fname.clone() } else { sig.replace(':', "") };
194-
195-
table.add_row([
196-
Cell::new(fn_display).add_attribute(Attribute::Bold),
197-
Cell::new(gas_info.min.to_string()).fg(Color::Green),
198-
Cell::new(gas_info.mean.to_string()).fg(Color::Yellow),
199-
Cell::new(gas_info.median.to_string()).fg(Color::Yellow),
200-
Cell::new(gas_info.max.to_string()).fg(Color::Red),
201-
Cell::new(gas_info.calls.to_string()),
202-
]);
203-
})
204-
});
205-
writeln!(f, "{table}")?;
206-
writeln!(f, "\n")?;
207176
}
177+
208178
Ok(())
209179
}
210180
}
211181

182+
impl GasReport {
183+
fn format_json_output(&self) -> String {
184+
#[inline]
185+
fn format_gas_info(gas_info: &GasInfo) -> serde_json::Value {
186+
json!({
187+
"calls": gas_info.calls,
188+
"min": gas_info.min,
189+
"mean": gas_info.mean,
190+
"median": gas_info.median,
191+
"max": gas_info.max,
192+
})
193+
}
194+
195+
serde_json::to_string(
196+
&self
197+
.contracts
198+
.iter()
199+
.filter_map(|(name, contract)| {
200+
if contract.functions.is_empty() {
201+
trace!(name, "gas report contract without functions");
202+
return None;
203+
}
204+
205+
let functions = contract
206+
.functions
207+
.iter()
208+
.map(|(fname, sigs)| {
209+
// If there is only one signature, display the gas info directly.
210+
let function_value = if sigs.len() == 1 {
211+
format_gas_info(sigs.values().next().unwrap())
212+
} else {
213+
// If there are multiple signatures, e.g. overloads like:
214+
// - `foo(uint256)`
215+
// - `foo(int256)`
216+
// display the gas info as a map with the signature as the key.
217+
let signatures = sigs
218+
.iter()
219+
.map(|(sig, gas_info)| {
220+
let display_name = sig.replace(':', "");
221+
(display_name, format_gas_info(gas_info))
222+
})
223+
.collect::<BTreeMap<_, _>>();
224+
225+
json!(signatures)
226+
};
227+
228+
(fname.to_string(), function_value)
229+
})
230+
.collect::<BTreeMap<_, _>>();
231+
232+
Some(json!({
233+
"contract": name,
234+
"deployment": {
235+
"gas": contract.gas,
236+
"size": contract.size,
237+
},
238+
"functions": functions,
239+
}))
240+
})
241+
.collect::<Vec<_>>(),
242+
)
243+
.unwrap()
244+
}
245+
246+
// Helper function to format the table output
247+
fn format_table_output(&self, contract: &ContractInfo, name: &str) -> Table {
248+
let mut table = Table::new();
249+
table.load_preset(ASCII_MARKDOWN);
250+
table.set_header([Cell::new(format!("{name} contract"))
251+
.add_attribute(Attribute::Bold)
252+
.fg(Color::Green)]);
253+
254+
table.add_row([
255+
Cell::new("Deployment Cost").add_attribute(Attribute::Bold).fg(Color::Cyan),
256+
Cell::new("Deployment Size").add_attribute(Attribute::Bold).fg(Color::Cyan),
257+
]);
258+
table.add_row([contract.gas.to_string(), contract.size.to_string()]);
259+
260+
table.add_row([
261+
Cell::new("Function Name").add_attribute(Attribute::Bold).fg(Color::Magenta),
262+
Cell::new("min").add_attribute(Attribute::Bold).fg(Color::Green),
263+
Cell::new("avg").add_attribute(Attribute::Bold).fg(Color::Yellow),
264+
Cell::new("median").add_attribute(Attribute::Bold).fg(Color::Yellow),
265+
Cell::new("max").add_attribute(Attribute::Bold).fg(Color::Red),
266+
Cell::new("# calls").add_attribute(Attribute::Bold),
267+
]);
268+
269+
contract.functions.iter().for_each(|(fname, sigs)| {
270+
sigs.iter().for_each(|(sig, gas_info)| {
271+
// Show function signature if overloaded else display function name.
272+
let display_name =
273+
if sigs.len() == 1 { fname.to_string() } else { sig.replace(':', "") };
274+
275+
table.add_row([
276+
Cell::new(display_name).add_attribute(Attribute::Bold),
277+
Cell::new(gas_info.min.to_string()).fg(Color::Green),
278+
Cell::new(gas_info.mean.to_string()).fg(Color::Yellow),
279+
Cell::new(gas_info.median.to_string()).fg(Color::Yellow),
280+
Cell::new(gas_info.max.to_string()).fg(Color::Red),
281+
Cell::new(gas_info.calls.to_string()),
282+
]);
283+
})
284+
});
285+
286+
table
287+
}
288+
}
289+
212290
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
213291
pub struct ContractInfo {
214292
pub gas: u64,

0 commit comments

Comments
 (0)