mod audit;
mod benchmark;
mod chain;
mod ci;
mod deploy;
mod deploy_safe;
mod doctor;
mod fuzz;
mod gas;
mod import;
mod install_hook;
mod invariant;
mod notify;
mod plugins;
mod report;
mod sbom;
mod scan;
mod security;
mod simulate;
mod upgrade_check;
mod verify;
mod watch;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(
name = "forge-guard",
version,
about = "Pre-deployment smart contract auditing framework for Foundry",
long_about = "The most comprehensive pre-deployment smart contract auditing framework for Foundry.\n\nTransforms security auditing from an optional step into a mandatory pre-deployment process.\nBlocks unsafe deployments by default while providing detailed vulnerability reports.",
author
)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
impl Cli {
pub fn from_env() -> Self {
Self::parse()
}
pub fn run(&self) -> anyhow::Result<()> {
use anyhow::Context;
match &self.command {
Commands::Audit(args) => audit::run(args).context("Audit failed"),
Commands::Deploy(args) => deploy::run(args).context("Deploy failed"),
Commands::DeploySafe(args) => deploy_safe::run(args).context("Safe deploy failed"),
Commands::Fuzz(args) => fuzz::run(args).context("Fuzzing failed"),
Commands::Invariant(args) => invariant::run(args).context("Invariant test failed"),
Commands::Simulate(args) => simulate::run(args).context("Simulation failed"),
Commands::Gas(args) => gas::run(args).context("Gas analysis failed"),
Commands::Report(args) => report::run(args).context("Report generation failed"),
Commands::Verify(args) => verify::run(args).context("Verification failed"),
Commands::Doctor(args) => doctor::run(args).context("Doctor analysis failed"),
Commands::Watch(args) => watch::run(args).context("Watch failed"),
Commands::Ci(args) => ci::run(args).context("CI generation failed"),
Commands::Benchmark(args) => benchmark::run(args).context("Benchmark failed"),
Commands::Scan(args) => scan::run(args).context("Scan failed"),
Commands::UpgradeCheck(args) => {
upgrade_check::run(args).context("Upgrade check failed")
}
Commands::Plugins(args) => plugins::run(args).context("Plugin operation failed"),
Commands::Chain(args) => chain::run(args).context("Chain operation failed"),
Commands::Sbom(args) => sbom::run(args).context("SBOM generation failed"),
Commands::InstallHook(args) => install_hook::run(args).context("Install hook failed"),
Commands::Security(args) => security::run(args).context("Security operation failed"),
Commands::Import(args) => import::run(args).context("Import failed"),
Commands::Notify(args) => notify::run(args).context("Notification failed"),
}
}
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Audit(AuditArgs),
Deploy(DeployArgs),
#[command(name = "deploy-safe")]
DeploySafe(DeploySafeArgs),
Fuzz(FuzzArgs),
Invariant(InvariantArgs),
Simulate(SimulateArgs),
Gas(GasArgs),
Report(ReportArgs),
Verify(VerifyArgs),
Doctor(DoctorArgs),
Watch(WatchArgs),
Ci(CiArgs),
Benchmark(BenchmarkArgs),
Scan(ScanArgs),
#[command(name = "upgrade-check")]
UpgradeCheck(UpgradeCheckArgs),
Plugins(PluginArgs),
Chain(ChainArgs),
Sbom(SbomArgs),
#[command(name = "install-hook")]
InstallHook(install_hook::InstallHookArgs),
Security(SecurityArgs),
Import(ImportArgs),
Notify(NotifyArgs),
}
#[derive(Debug, Default, clap::Args)]
pub struct SharedFlags {
#[arg(long, global = true, default_value = "ethereum")]
pub chain: String,
#[arg(long, global = true, default_value = ".")]
pub project: PathBuf,
#[arg(long, global = true)]
pub json: bool,
#[arg(long, global = true)]
pub markdown: bool,
#[arg(long, global = true)]
pub html: bool,
#[arg(long, global = true)]
pub strict: bool,
#[arg(long, global = true)]
pub offline: bool,
#[arg(long, global = true)]
pub production: bool,
#[arg(long, global = true)]
pub report: bool,
#[arg(long, global = true, default_value = "4")]
pub parallelism: usize,
}
#[derive(Debug, clap::Args)]
pub struct AuditArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, short)]
pub full: bool,
#[arg(long)]
pub quick: bool,
#[arg(long)]
pub summary: bool,
#[arg(long)]
pub ai: bool,
#[arg(long, default_value = "openai")]
pub ai_provider: String,
#[arg(long, default_value = "gpt-5")]
pub ai_model: String,
#[arg(long)]
pub ai_api_key: Option<String>,
#[arg(long)]
pub ollama_endpoint: Option<String>,
#[arg(long)]
pub ai_full: bool,
#[arg(long)]
pub exploit: bool,
#[arg(long)]
pub gas: bool,
#[arg(long)]
pub all_chains: bool,
#[arg(long, default_value = "src")]
pub sources: String,
#[arg(long)]
pub exclude: Option<String>,
#[arg(long)]
pub template: Option<String>,
#[arg(long)]
pub list_templates: bool,
#[arg(long)]
pub suppressions: Option<PathBuf>,
#[arg(long)]
pub show_suppressed: bool,
#[arg(long)]
pub generate_suppressions: bool,
#[arg(long)]
pub notify: bool,
}
#[derive(Debug, clap::Args)]
pub struct DeployArgs {
#[command(flatten)]
pub shared: SharedFlags,
pub contract: Option<String>,
#[arg(long)]
pub force: bool,
#[arg(long)]
pub args: Option<String>,
#[arg(long)]
pub salt: Option<String>,
#[arg(long)]
pub verify: bool,
#[arg(long)]
pub notify: bool,
}
#[derive(Debug, clap::Args)]
pub struct DeploySafeArgs {
#[command(flatten)]
pub shared: SharedFlags,
pub contract: Option<String>,
#[arg(long)]
pub args: Option<String>,
#[arg(long)]
pub verify: bool,
#[arg(long)]
pub notify: bool,
}
#[derive(Debug, clap::Args)]
pub struct FuzzArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, default_value = "10000")]
pub runs: u32,
#[arg(long)]
pub seed: Option<u64>,
#[arg(long)]
pub test: Option<String>,
pub contract: Option<String>,
}
#[derive(Debug, clap::Args)]
pub struct InvariantArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, default_value = "1000")]
pub runs: u32,
#[arg(long, default_value = "100")]
pub depth: u32,
pub contract: Option<String>,
#[arg(long)]
pub fail_on_revert: bool,
}
#[derive(Debug, clap::Args)]
pub struct SimulateArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, default_value = "100")]
pub blocks: u32,
#[arg(long)]
pub deployer: Option<String>,
#[arg(long)]
pub mev: bool,
pub contract: Option<String>,
}
#[derive(Debug, clap::Args)]
pub struct GasArgs {
#[command(flatten)]
pub shared: SharedFlags,
pub contract: Option<String>,
#[arg(long)]
pub diff: Option<String>,
#[arg(long)]
pub all: bool,
#[arg(long, default_value = "50000")]
pub warn_threshold: u64,
}
#[derive(Debug, clap::Args)]
pub struct ReportArgs {
#[command(flatten)]
pub shared: SharedFlags,
pub input: Option<PathBuf>,
#[arg(long, default_value = "markdown")]
pub format: String,
#[arg(long)]
pub output: Option<PathBuf>,
#[arg(long)]
pub exploit_paths: bool,
#[arg(long)]
pub summary: bool,
}
#[derive(Debug, clap::Args)]
pub struct VerifyArgs {
#[command(flatten)]
pub shared: SharedFlags,
pub address: Option<String>,
pub name: Option<String>,
#[arg(long)]
pub api_key: Option<String>,
#[arg(long)]
pub constructor_args: Option<String>,
#[arg(long)]
pub all: bool,
}
#[derive(Debug, clap::Args)]
pub struct DoctorArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long)]
pub fix: bool,
#[arg(long, short)]
pub verbose: bool,
#[arg(long)]
pub check: Option<String>,
#[arg(long)]
pub sync: bool,
#[arg(long)]
pub dry_run: bool,
#[arg(long)]
pub diff: bool,
}
#[derive(Debug, clap::Args)]
pub struct WatchArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, default_value = "src")]
pub dirs: String,
#[arg(long, default_value = "500")]
pub debounce_ms: u64,
#[arg(long)]
pub exclude: Option<String>,
#[arg(long)]
pub full: bool,
}
#[derive(Debug, clap::Args)]
pub struct CiArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, default_value = "github")]
pub platform: String,
#[arg(long, default_value = ".github/workflows")]
pub output: PathBuf,
#[arg(long)]
pub include_deploy: bool,
#[arg(long)]
pub overwrite: bool,
}
#[derive(Debug, clap::Args)]
pub struct BenchmarkArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, default_value = "10")]
pub iterations: u32,
#[arg(long)]
pub compare: Option<PathBuf>,
#[arg(long)]
pub save: Option<PathBuf>,
#[arg(long)]
pub module: Option<String>,
#[arg(long, default_value = "3")]
pub warmup: u32,
}
#[derive(Debug, clap::Args)]
pub struct SbomArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, default_value = "cyclonedx")]
pub format: String,
#[arg(long, short)]
pub output: Option<PathBuf>,
#[arg(long)]
pub ci: bool,
}
#[derive(Debug, clap::Args)]
pub struct ScanArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, default_value = "1")]
pub depth: u32,
#[arg(long)]
pub update: bool,
#[arg(long)]
pub vulnerable_only: bool,
#[arg(long)]
pub fail_fast: bool,
}
#[derive(Debug, clap::Args)]
pub struct UpgradeCheckArgs {
#[command(flatten)]
pub shared: SharedFlags,
pub proxy: Option<String>,
pub implementation: Option<String>,
#[arg(long)]
pub all: bool,
#[arg(long)]
pub storage_collision: bool,
#[arg(long)]
pub uups: bool,
}
#[derive(Debug, clap::Args)]
pub struct PluginArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[command(subcommand)]
pub action: Option<PluginAction>,
}
#[derive(Debug, Subcommand)]
pub enum PluginAction {
List,
Install {
name: String,
source: Option<String>,
},
Remove { name: String },
Enable { name: String },
Disable { name: String },
New { name: String },
}
#[derive(Debug, clap::Args)]
pub struct ChainArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[command(subcommand)]
pub action: Option<ChainAction>,
}
#[derive(Debug, Subcommand)]
pub enum ChainAction {
List,
Info { chain: String },
Add {
name: String,
rpc_url: Option<String>,
chain_id: Option<u64>,
},
Remove { name: String },
Test {
chain: String,
rpc_url: Option<String>,
},
}
#[derive(Debug, clap::Args)]
pub struct SecurityArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[command(subcommand)]
pub action: Option<SecurityAction>,
}
#[derive(Debug, Subcommand)]
pub enum SecurityAction {
Config,
Threshold { score: u8 },
Enable { check: String },
Disable { check: String },
List,
Info { check: String },
}
#[derive(Debug, clap::Args)]
pub struct ImportArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long, default_value = "slither")]
pub from: String,
pub input: Option<PathBuf>,
#[arg(long)]
pub findings: Option<PathBuf>,
#[arg(long, short)]
pub output: Option<PathBuf>,
}
#[derive(Debug, clap::Args)]
pub struct NotifyArgs {
#[command(flatten)]
pub shared: SharedFlags,
#[arg(long)]
pub webhook: Option<String>,
#[arg(long)]
pub kind: Option<String>,
#[arg(long)]
pub findings: Option<PathBuf>,
#[arg(long, default_value = "Forge Guard Audit")]
pub title: String,
#[arg(long)]
pub message: Option<String>,
#[arg(long)]
pub severity: Option<String>,
#[arg(long)]
pub on_critical: bool,
#[arg(long)]
pub on_high: bool,
#[arg(long)]
pub dry_run: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn test_cli_parse_audit() {
let cli = Cli::try_parse_from(["forge-guard", "audit"]).unwrap();
assert!(matches!(cli.command, Commands::Audit(_)));
}
#[test]
fn test_cli_parse_deploy() {
let cli = Cli::try_parse_from(["forge-guard", "deploy"]).unwrap();
assert!(matches!(cli.command, Commands::Deploy(_)));
}
#[test]
fn test_cli_parse_deploy_safe() {
let cli = Cli::try_parse_from(["forge-guard", "deploy-safe"]).unwrap();
assert!(matches!(cli.command, Commands::DeploySafe(_)));
}
#[test]
fn test_cli_parse_fuzz() {
let cli = Cli::try_parse_from(["forge-guard", "fuzz"]).unwrap();
assert!(matches!(cli.command, Commands::Fuzz(_)));
}
#[test]
fn test_cli_parse_invariant() {
let cli = Cli::try_parse_from(["forge-guard", "invariant"]).unwrap();
assert!(matches!(cli.command, Commands::Invariant(_)));
}
#[test]
fn test_cli_parse_simulate() {
let cli = Cli::try_parse_from(["forge-guard", "simulate"]).unwrap();
assert!(matches!(cli.command, Commands::Simulate(_)));
}
#[test]
fn test_cli_parse_gas() {
let cli = Cli::try_parse_from(["forge-guard", "gas"]).unwrap();
assert!(matches!(cli.command, Commands::Gas(_)));
}
#[test]
fn test_cli_parse_report() {
let cli = Cli::try_parse_from(["forge-guard", "report"]).unwrap();
assert!(matches!(cli.command, Commands::Report(_)));
}
#[test]
fn test_cli_parse_verify() {
let cli = Cli::try_parse_from(["forge-guard", "verify"]).unwrap();
assert!(matches!(cli.command, Commands::Verify(_)));
}
#[test]
fn test_cli_parse_doctor() {
let cli = Cli::try_parse_from(["forge-guard", "doctor"]).unwrap();
assert!(matches!(cli.command, Commands::Doctor(_)));
}
#[test]
fn test_cli_parse_watch() {
let cli = Cli::try_parse_from(["forge-guard", "watch"]).unwrap();
assert!(matches!(cli.command, Commands::Watch(_)));
}
#[test]
fn test_cli_parse_ci() {
let cli = Cli::try_parse_from(["forge-guard", "ci"]).unwrap();
assert!(matches!(cli.command, Commands::Ci(_)));
}
#[test]
fn test_cli_parse_benchmark() {
let cli = Cli::try_parse_from(["forge-guard", "benchmark"]).unwrap();
assert!(matches!(cli.command, Commands::Benchmark(_)));
}
#[test]
fn test_cli_parse_scan() {
let cli = Cli::try_parse_from(["forge-guard", "scan"]).unwrap();
assert!(matches!(cli.command, Commands::Scan(_)));
}
#[test]
fn test_cli_parse_upgrade_check() {
let cli = Cli::try_parse_from(["forge-guard", "upgrade-check"]).unwrap();
assert!(matches!(cli.command, Commands::UpgradeCheck(_)));
}
#[test]
fn test_cli_parse_plugins() {
let cli = Cli::try_parse_from(["forge-guard", "plugins"]).unwrap();
assert!(matches!(cli.command, Commands::Plugins(_)));
}
#[test]
fn test_cli_parse_chain() {
let cli = Cli::try_parse_from(["forge-guard", "chain"]).unwrap();
assert!(matches!(cli.command, Commands::Chain(_)));
}
#[test]
fn test_cli_parse_security() {
let cli = Cli::try_parse_from(["forge-guard", "security"]).unwrap();
assert!(matches!(cli.command, Commands::Security(_)));
}
#[test]
fn test_cli_parse_install_hook() {
let cli = Cli::try_parse_from(["forge-guard", "install-hook"]).unwrap();
assert!(matches!(cli.command, Commands::InstallHook(_)));
}
#[test]
fn test_cli_parse_install_hook_uninstall() {
let cli = Cli::try_parse_from(["forge-guard", "install-hook", "--uninstall"]).unwrap();
assert!(matches!(cli.command, Commands::InstallHook(_)));
}
#[test]
fn test_cli_parse_doctor_sync() {
let cli = Cli::try_parse_from(["forge-guard", "doctor", "--sync"]).unwrap();
if let Commands::Doctor(args) = cli.command {
assert!(args.sync);
} else {
panic!("Expected Doctor command");
}
}
#[test]
fn test_cli_parse_doctor_dry_run() {
let cli = Cli::try_parse_from(["forge-guard", "doctor", "--sync", "--dry-run"]).unwrap();
if let Commands::Doctor(args) = cli.command {
assert!(args.sync);
assert!(args.dry_run);
} else {
panic!("Expected Doctor command");
}
}
#[test]
fn test_cli_parse_audit_template() {
let cli = Cli::try_parse_from(["forge-guard", "audit", "--template", "defi"]).unwrap();
if let Commands::Audit(args) = cli.command {
assert_eq!(args.template.as_deref(), Some("defi"));
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_cli_parse_audit_list_templates() {
let cli = Cli::try_parse_from(["forge-guard", "audit", "--list-templates"]).unwrap();
if let Commands::Audit(args) = cli.command {
assert!(args.list_templates);
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_shared_flags_defaults() {
let cli = Cli::try_parse_from(["forge-guard", "audit"]).unwrap();
if let Commands::Audit(args) = cli.command {
assert_eq!(args.shared.chain, "ethereum");
assert!(!args.shared.json);
assert!(!args.shared.markdown);
assert!(!args.shared.strict);
assert!(!args.shared.offline);
assert!(!args.shared.production);
assert!(!args.shared.report);
assert_eq!(args.shared.parallelism, 4);
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_shared_flags_custom() {
let cli = Cli::try_parse_from([
"forge-guard",
"audit",
"--chain",
"base",
"--json",
"--strict",
"--offline",
"--production",
"--parallelism",
"8",
"--project",
"/tmp/project",
])
.unwrap();
if let Commands::Audit(args) = cli.command {
assert_eq!(args.shared.chain, "base");
assert!(args.shared.json);
assert!(args.shared.strict);
assert!(args.shared.offline);
assert!(args.shared.production);
assert_eq!(args.shared.parallelism, 8);
assert_eq!(
args.shared.project,
std::path::PathBuf::from("/tmp/project")
);
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_audit_args_defaults() {
let cli = Cli::try_parse_from(["forge-guard", "audit"]).unwrap();
if let Commands::Audit(args) = cli.command {
assert!(!args.full);
assert!(!args.quick);
assert!(!args.summary);
assert!(!args.ai);
assert_eq!(args.ai_provider, "openai");
assert_eq!(args.ai_model, "gpt-5");
assert!(args.ai_api_key.is_none());
assert!(!args.exploit);
assert!(!args.gas);
assert!(!args.all_chains);
assert_eq!(args.sources, "src");
assert!(args.exclude.is_none());
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_audit_args_full() {
let cli = Cli::try_parse_from([
"forge-guard",
"audit",
"--full",
"--chain",
"arbitrum",
"--json",
"--report",
"--exploit",
"--gas",
])
.unwrap();
if let Commands::Audit(args) = cli.command {
assert!(args.full);
assert!(args.exploit);
assert!(args.gas);
assert!(args.shared.json);
assert!(args.shared.report);
assert_eq!(args.shared.chain, "arbitrum");
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_audit_ai_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"audit",
"--ai",
"--ai-provider",
"claude",
"--ai-model",
"claude-5-sonnet-20260701",
"--ai-full",
])
.unwrap();
if let Commands::Audit(args) = cli.command {
assert!(args.ai);
assert_eq!(args.ai_provider, "claude");
assert_eq!(args.ai_model, "claude-5-sonnet-20260701");
assert!(args.ai_full);
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_deploy_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"deploy",
"MyContract",
"--force",
"--salt",
"0xabc",
"--verify",
"--args",
"arg1,arg2",
])
.unwrap();
if let Commands::Deploy(args) = cli.command {
assert_eq!(args.contract.as_deref(), Some("MyContract"));
assert!(args.force);
assert_eq!(args.salt.as_deref(), Some("0xabc"));
assert!(args.verify);
assert_eq!(args.args.as_deref(), Some("arg1,arg2"));
} else {
panic!("Expected Deploy command");
}
}
#[test]
fn test_fuzz_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"fuzz",
"--runs",
"50000",
"--seed",
"42",
"--test",
"testFuzzDeposit",
"Vault",
])
.unwrap();
if let Commands::Fuzz(args) = cli.command {
assert_eq!(args.runs, 50_000);
assert_eq!(args.seed, Some(42));
assert_eq!(args.test.as_deref(), Some("testFuzzDeposit"));
assert_eq!(args.contract.as_deref(), Some("Vault"));
} else {
panic!("Expected Fuzz command");
}
}
#[test]
fn test_simulate_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"simulate",
"MyContract",
"--blocks",
"200",
"--mev",
])
.unwrap();
if let Commands::Simulate(args) = cli.command {
assert_eq!(args.contract.as_deref(), Some("MyContract"));
assert_eq!(args.blocks, 200);
assert!(args.mev);
} else {
panic!("Expected Simulate command");
}
}
#[test]
fn test_invariant_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"invariant",
"--runs",
"2000",
"--depth",
"150",
"--fail-on-revert",
])
.unwrap();
if let Commands::Invariant(args) = cli.command {
assert_eq!(args.runs, 2000);
assert_eq!(args.depth, 150);
assert!(args.fail_on_revert);
} else {
panic!("Expected Invariant command");
}
}
#[test]
fn test_ci_args_defaults() {
let cli = Cli::try_parse_from(["forge-guard", "ci"]).unwrap();
if let Commands::Ci(args) = cli.command {
assert_eq!(args.platform, "github");
assert_eq!(args.output, std::path::PathBuf::from(".github/workflows"));
assert!(!args.include_deploy);
assert!(!args.overwrite);
} else {
panic!("Expected Ci command");
}
}
#[test]
fn test_ci_args_custom() {
let cli = Cli::try_parse_from([
"forge-guard",
"ci",
"--platform",
"gitlab",
"--include-deploy",
"--overwrite",
"--output",
".gitlab",
])
.unwrap();
if let Commands::Ci(args) = cli.command {
assert_eq!(args.platform, "gitlab");
assert!(args.include_deploy);
assert!(args.overwrite);
assert_eq!(args.output, std::path::PathBuf::from(".gitlab"));
} else {
panic!("Expected Ci command");
}
}
#[test]
fn test_benchmark_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"benchmark",
"--iterations",
"50",
"--warmup",
"5",
"--module",
"pattern_matching",
])
.unwrap();
if let Commands::Benchmark(args) = cli.command {
assert_eq!(args.iterations, 50);
assert_eq!(args.warmup, 5);
assert_eq!(args.module.as_deref(), Some("pattern_matching"));
} else {
panic!("Expected Benchmark command");
}
}
#[test]
fn test_gas_args() {
let cli =
Cli::try_parse_from(["forge-guard", "gas", "--all", "--warn-threshold", "100000"])
.unwrap();
if let Commands::Gas(args) = cli.command {
assert!(args.all);
assert_eq!(args.warn_threshold, 100000);
assert!(args.contract.is_none());
} else {
panic!("Expected Gas command");
}
}
#[test]
fn test_scan_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"scan",
"--depth",
"2",
"--update",
"--vulnerable-only",
"--fail-fast",
])
.unwrap();
if let Commands::Scan(args) = cli.command {
assert_eq!(args.depth, 2);
assert!(args.update);
assert!(args.vulnerable_only);
assert!(args.fail_fast);
} else {
panic!("Expected Scan command");
}
}
#[test]
fn test_upgrade_check_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"upgrade-check",
"--storage-collision",
"--uups",
"--all",
])
.unwrap();
if let Commands::UpgradeCheck(args) = cli.command {
assert!(args.storage_collision);
assert!(args.uups);
assert!(args.all);
} else {
panic!("Expected UpgradeCheck command");
}
}
#[test]
fn test_doctor_args() {
let cli = Cli::try_parse_from(["forge-guard", "doctor", "--fix", "--verbose"]).unwrap();
if let Commands::Doctor(args) = cli.command {
assert!(args.fix);
assert!(args.verbose);
assert!(args.check.is_none());
} else {
panic!("Expected Doctor command");
}
}
#[test]
fn test_verify_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"verify",
"0x1234",
"MyContract",
"--api-key",
"test-key",
"--all",
])
.unwrap();
if let Commands::Verify(args) = cli.command {
assert_eq!(args.address.as_deref(), Some("0x1234"));
assert_eq!(args.name.as_deref(), Some("MyContract"));
assert_eq!(args.api_key.as_deref(), Some("test-key"));
assert!(args.all);
} else {
panic!("Expected Verify command");
}
}
#[test]
fn test_watch_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"watch",
"--dirs",
"src,test-contracts",
"--debounce-ms",
"1000",
"--full",
])
.unwrap();
if let Commands::Watch(args) = cli.command {
assert_eq!(args.dirs, "src,test-contracts");
assert_eq!(args.debounce_ms, 1000);
assert!(args.full);
} else {
panic!("Expected Watch command");
}
}
#[test]
fn test_plugins_list() {
let cli = Cli::try_parse_from(["forge-guard", "plugins", "list"]).unwrap();
if let Commands::Plugins(args) = cli.command {
assert!(matches!(args.action, Some(PluginAction::List)));
} else {
panic!("Expected Plugins command");
}
}
#[test]
fn test_plugins_install() {
let cli = Cli::try_parse_from(["forge-guard", "plugins", "install", "my-plugin"]).unwrap();
if let Commands::Plugins(args) = cli.command {
assert!(matches!(args.action, Some(PluginAction::Install { .. })));
} else {
panic!("Expected Plugins command");
}
}
#[test]
fn test_plugins_remove() {
let cli = Cli::try_parse_from(["forge-guard", "plugins", "remove", "bad-plugin"]).unwrap();
if let Commands::Plugins(args) = cli.command {
assert!(matches!(args.action, Some(PluginAction::Remove { .. })));
} else {
panic!("Expected Plugins command");
}
}
#[test]
fn test_plugins_enable() {
let cli = Cli::try_parse_from(["forge-guard", "plugins", "enable", "my-plugin"]).unwrap();
if let Commands::Plugins(args) = cli.command {
assert!(matches!(args.action, Some(PluginAction::Enable { .. })));
} else {
panic!("Expected Plugins command");
}
}
#[test]
fn test_plugins_disable() {
let cli = Cli::try_parse_from(["forge-guard", "plugins", "disable", "my-plugin"]).unwrap();
if let Commands::Plugins(args) = cli.command {
assert!(matches!(args.action, Some(PluginAction::Disable { .. })));
} else {
panic!("Expected Plugins command");
}
}
#[test]
fn test_plugins_new() {
let cli =
Cli::try_parse_from(["forge-guard", "plugins", "new", "my-awesome-plugin"]).unwrap();
if let Commands::Plugins(args) = cli.command {
assert!(matches!(args.action, Some(PluginAction::New { .. })));
} else {
panic!("Expected Plugins command");
}
}
#[test]
fn test_chain_list() {
let cli = Cli::try_parse_from(["forge-guard", "chain", "list"]).unwrap();
if let Commands::Chain(args) = cli.command {
assert!(matches!(args.action, Some(ChainAction::List)));
} else {
panic!("Expected Chain command");
}
}
#[test]
fn test_chain_info() {
let cli = Cli::try_parse_from(["forge-guard", "chain", "info", "base"]).unwrap();
if let Commands::Chain(args) = cli.command {
assert!(matches!(args.action, Some(ChainAction::Info { .. })));
} else {
panic!("Expected Chain command");
}
}
#[test]
fn test_chain_add() {
let cli = Cli::try_parse_from([
"forge-guard",
"chain",
"add",
"my-chain",
"https://rpc.my-chain.io",
"99999",
])
.unwrap();
if let Commands::Chain(args) = cli.command {
assert!(matches!(args.action, Some(ChainAction::Add { .. })));
} else {
panic!("Expected Chain command");
}
}
#[test]
fn test_security_list() {
let cli = Cli::try_parse_from(["forge-guard", "security", "list"]).unwrap();
if let Commands::Security(args) = cli.command {
assert!(matches!(args.action, Some(SecurityAction::List)));
} else {
panic!("Expected Security command");
}
}
#[test]
fn test_security_threshold() {
let cli = Cli::try_parse_from(["forge-guard", "security", "threshold", "85"]).unwrap();
if let Commands::Security(args) = cli.command {
assert!(matches!(
args.action,
Some(SecurityAction::Threshold { .. })
));
} else {
panic!("Expected Security command");
}
}
#[test]
fn test_markdown_flag() {
let cli = Cli::try_parse_from(["forge-guard", "audit", "--markdown", "--report"]).unwrap();
if let Commands::Audit(args) = cli.command {
assert!(args.shared.markdown);
assert!(args.shared.report);
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_quick_audit() {
let cli = Cli::try_parse_from(["forge-guard", "audit", "--quick", "--summary"]).unwrap();
if let Commands::Audit(args) = cli.command {
assert!(args.quick);
assert!(args.summary);
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_all_chains_flag() {
let cli = Cli::try_parse_from(["forge-guard", "audit", "--all-chains"]).unwrap();
if let Commands::Audit(args) = cli.command {
assert!(args.all_chains);
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_deploy_safe_args() {
let cli =
Cli::try_parse_from(["forge-guard", "deploy-safe", "SecureVault", "--verify"]).unwrap();
if let Commands::DeploySafe(args) = cli.command {
assert_eq!(args.contract.as_deref(), Some("SecureVault"));
assert!(args.verify);
} else {
panic!("Expected DeploySafe command");
}
}
#[test]
fn test_report_args_json() {
let cli = Cli::try_parse_from(["forge-guard", "report", "--format", "json"]).unwrap();
if let Commands::Report(args) = cli.command {
assert_eq!(args.format, "json");
assert!(!args.summary);
} else {
panic!("Expected Report command");
}
}
#[test]
fn test_verify_all_without_address() {
let cli = Cli::try_parse_from(["forge-guard", "verify", "--all"]).unwrap();
if let Commands::Verify(args) = cli.command {
assert!(args.all);
assert!(args.address.is_none());
} else {
panic!("Expected Verify command");
}
}
#[test]
fn test_report_summary() {
let cli = Cli::try_parse_from(["forge-guard", "report", "--summary"]).unwrap();
if let Commands::Report(args) = cli.command {
assert!(args.summary);
} else {
panic!("Expected Report command");
}
}
#[test]
fn test_scan_vulnerable_only() {
let cli = Cli::try_parse_from(["forge-guard", "scan", "--vulnerable-only"]).unwrap();
if let Commands::Scan(args) = cli.command {
assert!(args.vulnerable_only);
} else {
panic!("Expected Scan command");
}
}
#[test]
fn test_doctor_check_category() {
let cli =
Cli::try_parse_from(["forge-guard", "doctor", "--check", "dependencies"]).unwrap();
if let Commands::Doctor(args) = cli.command {
assert_eq!(args.check.as_deref(), Some("dependencies"));
} else {
panic!("Expected Doctor command");
}
}
#[test]
fn test_gas_contract_specific() {
let cli =
Cli::try_parse_from(["forge-guard", "gas", "Vault", "--diff", "prev.json"]).unwrap();
if let Commands::Gas(args) = cli.command {
assert_eq!(args.contract.as_deref(), Some("Vault"));
assert_eq!(args.diff.as_deref(), Some("prev.json"));
} else {
panic!("Expected Gas command");
}
}
#[test]
fn test_verify_with_constructor_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"verify",
"0xabc",
"Token",
"--constructor-args",
"0x0001",
])
.unwrap();
if let Commands::Verify(args) = cli.command {
assert_eq!(args.constructor_args.as_deref(), Some("0x0001"));
} else {
panic!("Expected Verify command");
}
}
#[test]
fn test_upgrade_check_proxy() {
let cli =
Cli::try_parse_from(["forge-guard", "upgrade-check", "0xproxy", "0ximpl"]).unwrap();
if let Commands::UpgradeCheck(args) = cli.command {
assert_eq!(args.proxy.as_deref(), Some("0xproxy"));
assert_eq!(args.implementation.as_deref(), Some("0ximpl"));
} else {
panic!("Expected UpgradeCheck command");
}
}
#[test]
fn test_benchmark_save_and_compare() {
let cli = Cli::try_parse_from([
"forge-guard",
"benchmark",
"--save",
"results.json",
"--compare",
"baseline.json",
])
.unwrap();
if let Commands::Benchmark(args) = cli.command {
assert_eq!(
args.save.as_deref(),
Some(std::path::Path::new("results.json"))
);
assert_eq!(
args.compare.as_deref(),
Some(std::path::Path::new("baseline.json"))
);
} else {
panic!("Expected Benchmark command");
}
}
#[test]
fn test_watch_exclude() {
let cli = Cli::try_parse_from(["forge-guard", "watch", "--exclude", "*.test.sol"]).unwrap();
if let Commands::Watch(args) = cli.command {
assert_eq!(args.exclude.as_deref(), Some("*.test.sol"));
} else {
panic!("Expected Watch command");
}
}
#[test]
fn test_simulate_deployer() {
let cli =
Cli::try_parse_from(["forge-guard", "simulate", "--deployer", "0xdeployer"]).unwrap();
if let Commands::Simulate(args) = cli.command {
assert_eq!(args.deployer.as_deref(), Some("0xdeployer"));
} else {
panic!("Expected Simulate command");
}
}
#[test]
fn test_security_disable_check() {
let cli = Cli::try_parse_from(["forge-guard", "security", "disable", "FA-H-001"]).unwrap();
if let Commands::Security(args) = cli.command {
assert!(matches!(args.action, Some(SecurityAction::Disable { .. })));
} else {
panic!("Expected Security command");
}
}
#[test]
fn test_from_env_try_parse() {
let cli = Cli::try_parse_from(["forge-guard", "audit", "--report", "--json"]).unwrap();
assert!(matches!(cli.command, Commands::Audit(_)));
if let Commands::Audit(args) = cli.command {
assert!(args.shared.report);
assert!(args.shared.json);
}
}
#[test]
fn test_cli_parse_import() {
let cli = Cli::try_parse_from(["forge-guard", "import", "results.json"]).unwrap();
assert!(matches!(cli.command, Commands::Import(_)));
}
#[test]
fn test_import_args_from_slither() {
let cli = Cli::try_parse_from([
"forge-guard",
"import",
"--from",
"mythril",
"mythril_out.json",
"--findings",
"reports/audit.json",
])
.unwrap();
if let Commands::Import(args) = cli.command {
assert_eq!(args.from, "mythril");
assert_eq!(
args.input.as_deref(),
Some(std::path::Path::new("mythril_out.json"))
);
assert_eq!(
args.findings.as_deref(),
Some(std::path::Path::new("reports/audit.json"))
);
} else {
panic!("Expected Import command");
}
}
#[test]
fn test_import_args_defaults() {
let cli = Cli::try_parse_from(["forge-guard", "import"]).unwrap();
if let Commands::Import(args) = cli.command {
assert_eq!(args.from, "slither");
assert!(args.input.is_none());
assert!(args.findings.is_none());
assert!(args.output.is_none());
} else {
panic!("Expected Import command");
}
}
#[test]
fn test_cli_parse_notify() {
let cli = Cli::try_parse_from(["forge-guard", "notify", "--dry-run"]).unwrap();
assert!(matches!(cli.command, Commands::Notify(_)));
}
#[test]
fn test_notify_args() {
let cli = Cli::try_parse_from([
"forge-guard",
"notify",
"--webhook",
"https://hooks.slack.com/services/T/B/X",
"--findings",
"reports/audit.json",
"--on-critical",
"--title",
"Nightly audit",
])
.unwrap();
if let Commands::Notify(args) = cli.command {
assert_eq!(
args.webhook.as_deref(),
Some("https://hooks.slack.com/services/T/B/X")
);
assert_eq!(
args.findings.as_deref(),
Some(std::path::Path::new("reports/audit.json"))
);
assert!(args.on_critical);
assert!(!args.on_high);
assert!(!args.dry_run);
assert_eq!(args.title, "Nightly audit");
} else {
panic!("Expected Notify command");
}
}
#[test]
fn test_notify_defaults() {
let cli = Cli::try_parse_from(["forge-guard", "notify"]).unwrap();
if let Commands::Notify(args) = cli.command {
assert_eq!(args.title, "Forge Guard Audit");
assert!(args.webhook.is_none());
assert!(args.kind.is_none());
assert!(args.severity.is_none());
assert!(!args.dry_run);
} else {
panic!("Expected Notify command");
}
}
#[test]
fn test_audit_suppression_flags() {
let cli = Cli::try_parse_from([
"forge-guard",
"audit",
"--suppressions",
".forge-guard-suppressions",
"--show-suppressed",
])
.unwrap();
if let Commands::Audit(args) = cli.command {
assert_eq!(
args.suppressions.as_deref(),
Some(std::path::Path::new(".forge-guard-suppressions"))
);
assert!(args.show_suppressed);
assert!(!args.generate_suppressions);
assert!(!args.notify);
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_audit_generate_suppressions_and_notify() {
let cli = Cli::try_parse_from([
"forge-guard",
"audit",
"--generate-suppressions",
"--notify",
])
.unwrap();
if let Commands::Audit(args) = cli.command {
assert!(args.generate_suppressions);
assert!(args.notify);
assert!(args.suppressions.is_none());
} else {
panic!("Expected Audit command");
}
}
#[test]
fn test_deploy_notify_flag() {
let cli = Cli::try_parse_from(["forge-guard", "deploy", "--notify"]).unwrap();
if let Commands::Deploy(args) = cli.command {
assert!(args.notify);
} else {
panic!("Expected Deploy command");
}
}
#[test]
fn test_deploy_safe_notify_flag() {
let cli = Cli::try_parse_from(["forge-guard", "deploy-safe", "--notify"]).unwrap();
if let Commands::DeploySafe(args) = cli.command {
assert!(args.notify);
} else {
panic!("Expected DeploySafe command");
}
}
}