use super::InvariantArgs;
use anyhow::{Context, Result};
use colored::*;
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,
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");
}
}