From 05c91d25afe58ab5f3555ce8cc5a12a536d3dc0d Mon Sep 17 00:00:00 2001 From: karanjakevin39-collab Date: Mon, 29 Jun 2026 22:56:28 +0300 Subject: [PATCH 1/2] Update gas.rs --- src/commands/gas.rs | 384 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 319 insertions(+), 65 deletions(-) diff --git a/src/commands/gas.rs b/src/commands/gas.rs index 38049e24..df663c42 100644 --- a/src/commands/gas.rs +++ b/src/commands/gas.rs @@ -1,6 +1,7 @@ use crate::utils::{config, optimizer, print as p, profiler}; use anyhow::Result; use clap::Subcommand; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Attribute, Cell, Color, Table}; use std::path::PathBuf; #[derive(Subcommand)] @@ -39,6 +40,43 @@ pub async fn handle(cmd: GasCommands) -> Result<()> { } } +// ── helpers ──────────────────────────────────────────────────────────────── + +fn base_table() -> Table { + let mut t = Table::new(); + t.load_preset(UTF8_FULL) + .apply_modifier(UTF8_ROUND_CORNERS); + t +} + +fn header_cell(text: &str) -> Cell { + Cell::new(text) + .add_attribute(Attribute::Bold) + .fg(Color::Cyan) +} + +fn value_cell(text: &str) -> Cell { + Cell::new(text) +} + +fn good_cell(text: &str) -> Cell { + Cell::new(text).fg(Color::Green) +} + +fn warn_cell(text: &str) -> Cell { + Cell::new(text).fg(Color::Yellow) +} + +fn bad_cell(text: &str) -> Cell { + Cell::new(text).fg(Color::Red) +} + +fn estimate_simulation_cost(size_bytes: usize) -> u64 { + 2_000 + (size_bytes as u64 / 8) +} + +// ── subcommands ──────────────────────────────────────────────────────────── + fn analyze(wasm: PathBuf, network: Option) -> Result<()> { config::validate_file_path(&wasm, Some("wasm"))?; @@ -46,7 +84,7 @@ fn analyze(wasm: PathBuf, network: Option) -> Result<()> { let network = network.unwrap_or(cfg.network); config::validate_network(&network)?; - p::header("Gas Analyzer"); + p::header("Gas & Compute Visualizer — Analyze"); p::kv("Network", &network); p::kv("Wasm", &wasm.display().to_string()); @@ -54,27 +92,80 @@ fn analyze(wasm: PathBuf, network: Option) -> Result<()> { let report = optimizer::analyze_wasm(&wasm)?; let elapsed = t.elapsed(); + let est_cost = estimate_simulation_cost(report.size_bytes); + + // ── Cost breakdown table ────────────────────────────────────────────── println!(); - p::separator(); - p::kv_accent("Size (bytes)", &report.size_bytes.to_string()); - p::kv("SHA256", &report.sha256); - p::kv("Heuristic score", &report.score.to_string()); + let mut table = base_table(); + table.set_header(vec![ + header_cell("Metric"), + header_cell("Value"), + ]); + table.add_row(vec![ + value_cell("WASM size (bytes)"), + value_cell(&report.size_bytes.to_string()), + ]); + table.add_row(vec![ + value_cell("WASM size (KB)"), + value_cell(&format!("{:.2} KB", report.size_bytes as f64 / 1024.0)), + ]); + table.add_row(vec![ + value_cell("SHA-256"), + value_cell(&report.sha256), + ]); + table.add_row(vec![ + value_cell("Heuristic score"), + if report.score >= 80 { + good_cell(&report.score.to_string()) + } else if report.score >= 50 { + warn_cell(&report.score.to_string()) + } else { + bad_cell(&report.score.to_string()) + }, + ]); + table.add_row(vec![ + value_cell("Est. simulation cost (stroops)"), + value_cell(&est_cost.to_string()), + ]); + table.add_row(vec![ + value_cell("Est. ledger footprint reads"), + value_cell(&format!("{}", report.size_bytes / 4096 + 1)), + ]); + table.add_row(vec![ + value_cell("Est. auth cost (stroops)"), + value_cell(&format!("{}", est_cost / 10)), + ]); + table.add_row(vec![ + value_cell("Analysis duration"), + value_cell(&format!("{:?}", elapsed)), + ]); + println!("{table}"); + + // ── Suggestions ─────────────────────────────────────────────────────── if !report.suggestions.is_empty() { println!(); - p::info("Suggestions:"); - for s in &report.suggestions { - println!(" - {}", s); + p::info("Optimization suggestions:"); + let mut stbl = base_table(); + stbl.set_header(vec![header_cell("#"), header_cell("Suggestion")]); + for (i, s) in report.suggestions.iter().enumerate() { + stbl.add_row(vec![ + warn_cell(&(i + 1).to_string()), + value_cell(s), + ]); } + println!("{stbl}"); + } else { + println!(); + p::success("No optimization suggestions — contract looks lean."); } - p::separator(); - p::kv("Duration", &format!("{:?}", elapsed)); + Ok(()) } fn optimize(target: PathBuf, output: PathBuf) -> Result<()> { config::validate_file_path(&target, Some("wasm"))?; - p::header("Gas Optimizer"); + p::header("Gas & Compute Visualizer — Optimize"); p::kv("Input", &target.display().to_string()); p::kv("Output", &output.display().to_string()); @@ -82,20 +173,61 @@ fn optimize(target: PathBuf, output: PathBuf) -> Result<()> { let result = optimizer::optimize_wasm(&target, &output)?; let elapsed = t.elapsed(); + let old_cost = estimate_simulation_cost(result.input_size_bytes); + let new_cost = estimate_simulation_cost(result.output_size_bytes); + let cost_delta = new_cost as i64 - old_cost as i64; + + println!(); + let mut table = base_table(); + table.set_header(vec![ + header_cell("Metric"), + header_cell("Before"), + header_cell("After"), + header_cell("Delta"), + ]); + table.add_row(vec![ + value_cell("Size (bytes)"), + value_cell(&result.input_size_bytes.to_string()), + value_cell(&result.output_size_bytes.to_string()), + if result.reduction_bytes() > 0 { + good_cell(&format!("-{} bytes", result.reduction_bytes())) + } else { + warn_cell("0 bytes") + }, + ]); + table.add_row(vec![ + value_cell("Size (KB)"), + value_cell(&format!("{:.2}", result.input_size_bytes as f64 / 1024.0)), + value_cell(&format!("{:.2}", result.output_size_bytes as f64 / 1024.0)), + good_cell(&format!("{:+.2}%", result.reduction_percent())), + ]); + table.add_row(vec![ + value_cell("Est. sim cost (stroops)"), + value_cell(&old_cost.to_string()), + value_cell(&new_cost.to_string()), + if cost_delta < 0 { + good_cell(&format!("{:+}", cost_delta)) + } else { + warn_cell(&format!("{:+}", cost_delta)) + }, + ]); + table.add_row(vec![ + value_cell("Optimizer"), + value_cell(&result.tool), + value_cell("—"), + value_cell("—"), + ]); + table.add_row(vec![ + value_cell("Duration"), + value_cell(&format!("{:?}", elapsed)), + value_cell("—"), + value_cell("—"), + ]); + println!("{table}"); + println!(); - p::success("Optimization output written"); - p::kv("Optimizer", &result.tool); - p::kv("Bytes in", &result.input_size_bytes.to_string()); - p::kv("Bytes out", &result.output_size_bytes.to_string()); - p::kv( - "Size reduction", - &format!( - "{} bytes ({:+.2}%)", - result.reduction_bytes(), - result.reduction_percent() - ), - ); - p::kv("Duration", &format!("{:?}", elapsed)); + p::success("Optimization complete — output written successfully."); + Ok(()) } @@ -103,9 +235,9 @@ fn diff(old_wasm: PathBuf, new_wasm: PathBuf) -> Result<()> { config::validate_file_path(&old_wasm, Some("wasm"))?; config::validate_file_path(&new_wasm, Some("wasm"))?; - p::header("Gas Diff"); - p::kv("Old wasm", &old_wasm.display().to_string()); - p::kv("New wasm", &new_wasm.display().to_string()); + p::header("Gas & Compute Visualizer — Diff"); + p::kv("Baseline", &old_wasm.display().to_string()); + p::kv("Candidate", &new_wasm.display().to_string()); let mut profile = profiler::Profiler::start(); let old_report = optimizer::analyze_wasm(&old_wasm)?; @@ -113,55 +245,177 @@ fn diff(old_wasm: PathBuf, new_wasm: PathBuf) -> Result<()> { let new_report = optimizer::analyze_wasm(&new_wasm)?; profile.mark("analyze_new"); - let old_est = estimate_simulation_cost(old_report.size_bytes); - let new_est = estimate_simulation_cost(new_report.size_bytes); - let delta = new_est as i64 - old_est as i64; - let pct = if old_est == 0 { + let old_cost = estimate_simulation_cost(old_report.size_bytes); + let new_cost = estimate_simulation_cost(new_report.size_bytes); + let cost_delta = new_cost as i64 - old_cost as i64; + let cost_pct = if old_cost == 0 { + 0.0 + } else { + (cost_delta as f64 / old_cost as f64) * 100.0 + }; + + let size_delta = new_report.size_bytes as i64 - old_report.size_bytes as i64; + let size_pct = if old_report.size_bytes == 0 { 0.0 } else { - (delta as f64 / old_est as f64) * 100.0 + (size_delta as f64 / old_report.size_bytes as f64) * 100.0 }; + let old_auth = old_cost / 10; + let new_auth = new_cost / 10; + let auth_delta = new_auth as i64 - old_auth as i64; + + let old_reads = old_report.size_bytes / 4096 + 1; + let new_reads = new_report.size_bytes / 4096 + 1; + let reads_delta = new_reads as i64 - old_reads as i64; + println!(); - p::separator(); - p::kv("Old size (bytes)", &old_report.size_bytes.to_string()); - p::kv("New size (bytes)", &new_report.size_bytes.to_string()); - p::kv("Old est. sim cost", &old_est.to_string()); - p::kv("New est. sim cost", &new_est.to_string()); - p::kv( - "Estimated delta", - &format!( - "{} ({:+.2}%)", - if delta >= 0 { - format!("+{}", delta) - } else { - delta.to_string() - }, - pct - ), - ); - p::kv( - "Result", - if delta < 0 { - "Improved (lower estimated cost)" - } else if delta > 0 { - "Regressed (higher estimated cost)" + let mut table = base_table(); + table.set_header(vec![ + header_cell("Metric"), + header_cell("Baseline"), + header_cell("Candidate"), + header_cell("Delta"), + header_cell("Change %"), + ]); + + // Size row + table.add_row(vec![ + value_cell("WASM size (bytes)"), + value_cell(&old_report.size_bytes.to_string()), + value_cell(&new_report.size_bytes.to_string()), + if size_delta <= 0 { + good_cell(&format!("{:+}", size_delta)) + } else { + bad_cell(&format!("{:+}", size_delta)) + }, + if size_pct <= 0.0 { + good_cell(&format!("{:+.2}%", size_pct)) + } else { + bad_cell(&format!("{:+.2}%", size_pct)) + }, + ]); + + // Sim cost row + table.add_row(vec![ + value_cell("Est. sim cost (stroops)"), + value_cell(&old_cost.to_string()), + value_cell(&new_cost.to_string()), + if cost_delta <= 0 { + good_cell(&format!("{:+}", cost_delta)) + } else { + bad_cell(&format!("{:+}", cost_delta)) + }, + if cost_pct <= 0.0 { + good_cell(&format!("{:+.2}%", cost_pct)) + } else { + bad_cell(&format!("{:+.2}%", cost_pct)) + }, + ]); + + // Auth cost row + table.add_row(vec![ + value_cell("Est. auth cost (stroops)"), + value_cell(&old_auth.to_string()), + value_cell(&new_auth.to_string()), + if auth_delta <= 0 { + good_cell(&format!("{:+}", auth_delta)) + } else { + bad_cell(&format!("{:+}", auth_delta)) + }, + value_cell("—"), + ]); + + // Ledger reads row + table.add_row(vec![ + value_cell("Est. ledger footprint reads"), + value_cell(&old_reads.to_string()), + value_cell(&new_reads.to_string()), + if reads_delta <= 0 { + good_cell(&format!("{:+}", reads_delta)) + } else { + bad_cell(&format!("{:+}", reads_delta)) + }, + value_cell("—"), + ]); + + // Heuristic score row + table.add_row(vec![ + value_cell("Heuristic score"), + value_cell(&old_report.score.to_string()), + value_cell(&new_report.score.to_string()), + if new_report.score >= old_report.score { + good_cell(&format!("{:+}", new_report.score as i32 - old_report.score as i32)) } else { - "No change" + bad_cell(&format!("{:+}", new_report.score as i32 - old_report.score as i32)) }, - ); + value_cell("—"), + ]); + + println!("{table}"); + + // ── Verdict ─────────────────────────────────────────────────────────── + println!(); + if cost_delta < 0 { + p::success(&format!( + "Candidate is BETTER — saves {} stroops ({:+.2}%)", + cost_delta.abs(), + cost_pct + )); + } else if cost_delta > 0 { + p::warn(&format!( + "Candidate REGRESSED — costs {} more stroops ({:+.2}%)", + cost_delta, + cost_pct + )); + } else { + p::info("No change in estimated compute cost."); + } + + // ── Profile table ───────────────────────────────────────────────────── + println!(); + let mut ptbl = base_table(); + ptbl.set_header(vec![header_cell("Step"), header_cell("Elapsed")]); for point in profile.points() { - p::kv( - &format!("Step {}", point.label), - &format!("{:?}", point.elapsed), - ); + ptbl.add_row(vec![ + value_cell(&point.label), + value_cell(&format!("{:?}", point.elapsed)), + ]); } - p::kv("Total profile", &format!("{:?}", profile.total_elapsed())); - p::separator(); + ptbl.add_row(vec![ + value_cell("Total"), + value_cell(&format!("{:?}", profile.total_elapsed())), + ]); + println!("{ptbl}"); Ok(()) } -fn estimate_simulation_cost(size_bytes: usize) -> u64 { - 2_000 + (size_bytes as u64 / 8) +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn estimate_simulation_cost_zero() { + assert_eq!(estimate_simulation_cost(0), 2_000); + } + + #[test] + fn estimate_simulation_cost_nonzero() { + // 8 bytes → 2000 + 1 = 2001 + assert_eq!(estimate_simulation_cost(8), 2_001); + } + + #[test] + fn estimate_simulation_cost_large() { + // 80_000 bytes → 2000 + 10000 = 12000 + assert_eq!(estimate_simulation_cost(80_000), 12_000); + } + + #[test] + fn base_table_has_utf8_preset() { + let table = base_table(); + // Just ensure it constructs without panic + let _ = table.to_string(); + } } From 7c4b93cc28501f0e50e2876232e6d5f4f73344ed Mon Sep 17 00:00:00 2001 From: karanjakevin39-collab Date: Mon, 29 Jun 2026 22:58:27 +0300 Subject: [PATCH 2/2] Update Cargo.toml --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 158937d8..2491be0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "=0.8.8" colored = "=2.1.0" +comfy-table = "7.1.1" dirs = "=5.0.1" anyhow = "1.0" chrono = { version = "0.4", features = ["serde"] }