forge-guard 0.3.5

Pre-deployment smart contract auditing framework for Foundry
Documentation
//! `forge-guard invariant` — run invariant tests.

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

/// Run invariant tests.
pub fn run(args: &InvariantArgs) -> Result<()> {
    eprintln!("{}", "🔬 Forge Guard — Invariant Testing".bold());
    eprintln!("   Runs:       {}", args.runs);
    eprintln!("   Depth:      {}", args.depth);
    eprintln!(
        "   Contract:   {}",
        args.contract.as_deref().unwrap_or("all")
    );

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

    cmd.arg("--invariant-runs").arg(args.runs.to_string());
    cmd.arg("--invariant-depth").arg(args.depth.to_string());

    if args.fail_on_revert {
        cmd.arg("--fail-on-revert");
    }

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

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

    if !status.success() {
        anyhow::bail!("Invariant tests failed — invariants broken!");
    }

    eprintln!(
        "\n{}",
        "✅ Invariant tests passed — all invariants hold."
            .green()
            .bold()
    );
    Ok(())
}

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

    #[test]
    fn test_invariant_arg_defaults() {
        let args = InvariantArgs {
            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: 1_000,
            depth: 100,
            contract: None,
            fail_on_revert: false,
        };
        assert_eq!(args.runs, 1_000);
        assert_eq!(args.depth, 100);
        assert!(args.contract.is_none());
        assert!(!args.fail_on_revert);
    }

    #[test]
    fn test_invariant_with_custom_config() {
        let args = InvariantArgs {
            shared: super::super::SharedFlags::default(),
            runs: 5_000,
            depth: 200,
            contract: Some("Vault".into()),
            fail_on_revert: true,
        };
        assert_eq!(args.runs, 5_000);
        assert_eq!(args.depth, 200);
        assert_eq!(args.contract.as_deref(), Some("Vault"));
        assert!(args.fail_on_revert);
    }

    #[test]
    fn test_invariant_contract_display_default() {
        let contract: Option<String> = None;
        assert_eq!(contract.as_deref().unwrap_or("all"), "all");
    }
}