forge-guard 0.3.5

Pre-deployment smart contract auditing framework for Foundry
Documentation
//! `forge-guard fuzz` — run fuzzing campaigns.

use super::FuzzArgs;
use anyhow::{Context, Result};
use colored::*;

/// Run fuzzing campaigns against smart contracts.
pub fn run(args: &FuzzArgs) -> Result<()> {
    eprintln!("{}", "🎯 Forge Guard — Fuzzing Campaign".bold());
    eprintln!("   Runs:     {}", args.runs);
    eprintln!(
        "   Seed:     {}",
        args.seed.map_or("random".into(), |s| s.to_string())
    );
    eprintln!("   Contract: {}", args.contract.as_deref().unwrap_or("all"));

    // Build forge fuzz command
    let mut cmd = std::process::Command::new("forge");
    cmd.arg("test");

    cmd.arg("--fuzz-runs").arg(args.runs.to_string());

    if let Some(seed) = args.seed {
        cmd.arg("--fuzz-seed").arg(seed.to_string());
    }

    if let Some(test) = &args.test {
        cmd.arg("--match-test").arg(test);
    }

    if let Some(contract) = &args.contract {
        cmd.arg("--match-contract").arg(contract);
    }

    eprintln!("\n🚀 Running fuzzing campaign...\n");
    let status = cmd.status().context("Failed to run forge test")?;

    if !status.success() {
        anyhow::bail!("Fuzzing found issues — inspect output above");
    }

    eprintln!(
        "\n{}",
        "✅ Fuzzing passed — no issues found.".green().bold()
    );
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_fuzz_arg_defaults() {
        let args = FuzzArgs {
            shared: super::super::SharedFlags {
                chain: "ethereum".into(),
                project: std::path::PathBuf::from("."),
                json: false,
                markdown: false,
                html: false,
                strict: false,
                offline: false,
                production: false,
                report: false,
                parallelism: 4,
            },
            runs: 10_000,
            seed: None,
            test: None,
            contract: None,
        };
        assert_eq!(args.runs, 10_000);
        assert!(args.seed.is_none());
        assert!(args.test.is_none());
        assert!(args.contract.is_none());
    }

    #[test]
    fn test_fuzz_with_all_options() {
        let args = FuzzArgs {
            shared: super::super::SharedFlags::default(),
            runs: 50_000,
            seed: Some(42),
            test: Some("testFuzzDeposit".into()),
            contract: Some("Vault".into()),
        };
        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"));
    }

    #[test]
    fn test_fuzz_seed_display() {
        let with_seed = Some(42u64);
        let without_seed: Option<u64> = None;
        assert_eq!(with_seed.map_or("random".into(), |s| s.to_string()), "42");
        assert_eq!(
            without_seed.map_or("random".into(), |s| s.to_string()),
            "random"
        );
    }

    #[test]
    fn test_fuzz_contract_display() {
        let with_contract = Some("Vault");
        let without_contract: Option<&str> = None;
        assert_eq!(with_contract.unwrap_or("all"), "Vault");
        assert_eq!(without_contract.unwrap_or("all"), "all");
    }
}