forge-guard 0.1.1

Pre-deployment smart contract auditing framework for Foundry
Documentation
//! `forge simulate` โ€” run deployment simulations with MEV analysis.

use super::SimulateArgs;
use anyhow::{Context, Result};
use colored::*;
use std::time::Instant;

/// MEV-sensitive source-level patterns.
const SANDWICH_SENSITIVE_PATTERNS: &[&str] = &[
    "amm",
    "swap",
    "pool",
    "liquidity",
    "reserve",
    "price",
    "twap",
    "oracle",
    "slippage",
    "minAmount",
    "amountOutMin",
    "sqrtPrice",
];

/// Run deployment simulations to detect issues before deploying.
pub fn run(args: &SimulateArgs) -> Result<()> {
    let start = Instant::now();

    eprintln!("{}\n", "๐Ÿงช Forge Guard โ€” Deployment Simulation".bold());
    println!("  Blocks:    {}", args.blocks);
    println!(
        "  MEV:       {}",
        if args.mev {
            "enabled".green()
        } else {
            "disabled".dimmed()
        }
    );
    println!("  Contract:  {}", args.contract.as_deref().unwrap_or("all"));
    println!(
        "  Deployer:  {}",
        args.deployer.as_deref().unwrap_or("default")
    );
    println!();

    // Phase 1: Compile and analyze for MEV patterns (even without --mev flag)
    eprintln!("๐Ÿ“‹ Phase 1: Analyzing contract bytecode for MEV vectors...\n");
    let mev_signals = analyze_mev_patterns(args);

    if !mev_signals.is_empty() {
        println!("  โš ๏ธ  Potential MEV patterns found:\n");
        for signal in &mev_signals {
            println!(
                "     โ€ข {} โ€” {}",
                signal.pattern.yellow(),
                signal.description
            );
        }
        println!();
    } else {
        println!("  โœ… No obvious MEV patterns detected.\n");
    }

    // Phase 2: Build and simulate via forge script
    eprintln!("๐Ÿ“‹ Phase 2: Running deployment simulation...\n");

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

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

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

    cmd.arg("--slow");

    eprintln!(
        "     Running: forge script {}\n",
        args.contract.as_deref().unwrap_or("<default>")
    );

    let output = match cmd.output().context("Failed to run forge script") {
        Ok(o) => o,
        Err(e) => {
            eprintln!("  โš ๏ธ  Simulation failed: {e}");
            eprintln!("     This is non-fatal โ€” forge may not be installed or configured.");
            eprintln!("     Security audit findings are still available.");
            return Ok(());
        }
    };

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        eprintln!("  โš ๏ธ  Simulation stderr:\n{}", stderr);
        eprintln!("     Simulation had warnings โ€” these are non-fatal.");
    } else {
        extract_simulation_results(&String::from_utf8_lossy(&output.stdout));
    }

    // Phase 3: MEV analysis (if enabled)
    if args.mev {
        eprintln!("\n๐Ÿ“‹ Phase 3: MEV analysis...\n");
        run_mev_analysis(args.blocks);
    }

    let elapsed = start.elapsed();
    println!(
        "\n{}",
        "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•".bright_blue()
    );
    println!(
        "  {} SIMULATION COMPLETE in {:.2}s",
        "โœ…".green(),
        elapsed.as_secs_f64()
    );
    println!(
        "{}",
        "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•".bright_blue()
    );

    Ok(())
}

/// Detected MEV pattern.
struct MevSignal {
    pattern: &'static str,
    description: String,
}

/// Analyze source code for potential MEV-sensitive patterns.
fn analyze_mev_patterns(args: &SimulateArgs) -> Vec<MevSignal> {
    let mut signals = Vec::new();

    // Scan source files for MEV-sensitive patterns
    let src_dir = args.shared.project.join("src");
    if !src_dir.exists() {
        return signals;
    }

    let mut source_files = Vec::new();
    for entry in walkdir::WalkDir::new(&src_dir)
        .into_iter()
        .filter_map(|e| e.ok())
    {
        let path = entry.path();
        if path.extension().is_some_and(|ext| ext == "sol") {
            source_files.push(path.to_path_buf());
        }
    }

    for file in &source_files {
        let content = match std::fs::read_to_string(file) {
            Ok(c) => c,
            Err(_) => continue,
        };
        let lower = content.to_lowercase();

        // Check for sandwich-sensitive patterns
        for pattern in SANDWICH_SENSITIVE_PATTERNS {
            if lower.contains(pattern) {
                signals.push(MevSignal {
                    pattern: "Sandwich / Slippage",
                    description: format!(
                        "Contract uses '{}' patterns that may be vulnerable to sandwich attacks. Ensure proper slippage protection.",
                        pattern
                    ),
                });
                break;
            }
        }

        // Check for flash loan patterns
        if lower.contains("flashloan")
            || lower.contains("flash_loan")
            || lower.contains("onflashloan")
        {
            signals.push(MevSignal {
                pattern: "Flash Loan",
                description:
                    "Contract supports flash loans โ€” verify price oracle manipulation resistance."
                        .into(),
            });
        }

        // Check for oracle dependency
        if lower.contains("chainlink") || lower.contains("oracle") || lower.contains("twap") {
            signals.push(MevSignal {
                pattern: "Oracle Dependency",
                description:
                    "Contract depends on price oracles โ€” ensure freshness and manipulation resistance."
                        .into(),
            });
        }

        // Check for unchecked external calls (MEV extraction vector)
        if lower.contains(".call{value")
            || lower.contains(".delegatecall(")
            || lower.contains("selfdestruct")
        {
            signals.push(MevSignal {
                pattern: "Value Extraction",
                description:
                    "Contract has value-moving operations that could be MEV extraction targets."
                        .into(),
            });
        }
    }

    // Deduplicate by pattern
    signals.dedup_by_key(|s| s.pattern);

    signals
}

/// Extract and display key results from forge script simulation output.
fn extract_simulation_results(stdout: &str) {
    let mut gas_used: Vec<u64> = Vec::new();
    let mut txn_count = 0u32;

    for line in stdout.lines() {
        let trimmed = line.trim();

        // Extract gas from forge traces
        if trimmed.contains("gas used") || trimmed.contains("Gas used") {
            if let Some(gas_str) = trimmed
                .split_whitespace()
                .find(|w| w.chars().all(|c| c.is_ascii_digit()))
            {
                if let Ok(gas) = gas_str.parse::<u64>() {
                    gas_used.push(gas);
                }
            }
        }

        // Count transactions
        if trimmed.contains("Transaction") || trimmed.starts_with("==LOG==") {
            txn_count += 1;
        }
    }

    // Deployment cost estimate
    if let Some(&total) = gas_used.last() {
        let estimated_eth_cost = total as f64 * 20_000_000_000.0 / 1_000_000_000_000_000_000.0; // ~20 gwei
        println!("  โ›ฝ Estimated gas:  {}", total.to_string().bold());
        println!(
            "  ๐Ÿ’ฐ Estimated cost: {:.6} ETH at 20 gwei",
            estimated_eth_cost
        );
    }

    if txn_count > 0 {
        println!("  ๐Ÿ“ Transactions:   {}", txn_count);
    }
}

/// Run detailed MEV analysis (placeholder for future enhancement).
fn run_mev_analysis(blocks: u32) {
    println!("  Analyzing {blocks} blocks for MEV opportunities...\n");

    // Analysis categories
    let analyses = [
        (
            "Frontrunning",
            "Check if transactions can be displaced by priority gas auctions",
        ),
        (
            "Sandwiching",
            "Detect AMM swap patterns vulnerable to sandwich attacks",
        ),
        (
            "Backrunning",
            "Identify post-transaction value extraction opportunities",
        ),
        (
            "Liquidation",
            "Scan for liquidation opportunities and competition",
        ),
        ("Arbitrage", "Detect cross-pool/CEX-DEX arbitrage patterns"),
    ];

    for (category, description) in &analyses {
        println!("  โ€ข {}: {}", category.bold(), description.dimmed());
    }

    println!(
        "\n  {} MEV analysis requires a full archival node and historical data for accuracy.",
        "๐Ÿ’ก".dimmed()
    );
    println!(
        "    For production use, consider dedicated MEV tools: searcher, libmev, or Flashbots.",
    );
}