forge-guard 0.3.6

Pre-deployment smart contract auditing framework for Foundry
Documentation
//! `forge-guard upgrade-check` — analyze upgrade paths and proxy safety.

use crate::core::{Finding, Severity};

use super::UpgradeCheckArgs;
use anyhow::Result;
use colored::*;

/// Analyze upgrade paths and proxy safety.
pub fn run(args: &UpgradeCheckArgs) -> Result<()> {
    eprintln!("{}", "⬆️  Forge Guard — Upgrade Check".bold());

    let mut findings: Vec<Finding> = Vec::new();

    if args.storage_collision {
        eprintln!("\n🔍 Checking for storage collisions...");
        findings.extend(check_storage_collisions(args)?);
    }

    if args.uups {
        eprintln!("\n🔍 Checking UUPS upgrade path...");
        findings.extend(check_uups(args)?);
    }

    // Generic proxy checks
    eprintln!("\n🔍 Analyzing proxy configuration...");
    findings.extend(check_proxy_config(args)?);

    // Report findings
    if findings.is_empty() {
        println!("\n{} No upgrade issues found!", "".green().bold());
    } else {
        println!(
            "\n{} {} upgrade-related issues found:",
            "⚠️".yellow().bold(),
            findings.len()
        );
        for f in &findings {
            let color = if f.severity >= Severity::High {
                "red"
            } else {
                "yellow"
            };
            println!(
                "\n  [{}] {}",
                f.severity.to_string().color(color).bold(),
                f.title.bold()
            );
            println!("       {}", f.description);
            println!("       💡 {}", f.recommendation);
        }
    }

    if args.shared.json {
        println!("{}", serde_json::to_string_pretty(&findings)?);
    }

    Ok(())
}

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

    #[test]
    fn test_upgrade_check_arg_defaults() {
        let args = UpgradeCheckArgs {
            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,
            },
            proxy: None,
            implementation: None,
            all: false,
            storage_collision: false,
            uups: false,
        };
        assert!(args.proxy.is_none());
        assert!(args.implementation.is_none());
        assert!(!args.all);
        assert!(!args.storage_collision);
        assert!(!args.uups);
    }

    #[test]
    fn test_upgrade_check_with_addresses() {
        let args = UpgradeCheckArgs {
            shared: super::super::SharedFlags::default(),
            proxy: Some("0xproxy".into()),
            implementation: Some("0ximpl".into()),
            all: true,
            storage_collision: true,
            uups: true,
        };
        assert_eq!(args.proxy.as_deref(), Some("0xproxy"));
        assert_eq!(args.implementation.as_deref(), Some("0ximpl"));
        assert!(args.all);
        assert!(args.storage_collision);
        assert!(args.uups);
    }

    #[test]
    fn test_check_storage_collisions_returns_empty() {
        let args = UpgradeCheckArgs {
            shared: super::super::SharedFlags::default(),
            proxy: None,
            implementation: None,
            all: false,
            storage_collision: false,
            uups: false,
        };
        let findings = check_storage_collisions(&args).unwrap();
        assert!(findings.is_empty());
    }

    #[test]
    fn test_check_uups_returns_empty() {
        let args = UpgradeCheckArgs {
            shared: super::super::SharedFlags::default(),
            proxy: None,
            implementation: None,
            all: false,
            storage_collision: false,
            uups: false,
        };
        let findings = check_uups(&args).unwrap();
        assert!(findings.is_empty());
    }

    #[test]
    fn test_check_proxy_config_returns_empty() {
        let args = UpgradeCheckArgs {
            shared: super::super::SharedFlags::default(),
            proxy: None,
            implementation: None,
            all: false,
            storage_collision: false,
            uups: false,
        };
        let findings = check_proxy_config(&args).unwrap();
        assert!(findings.is_empty());
    }

    #[test]
    fn test_severity_comparison_in_upgrade_check() {
        // Test the severity comparison logic used in run()
        assert!(Severity::High >= Severity::High);
        assert!(Severity::Critical >= Severity::High);
        assert!(!(Severity::Medium >= Severity::High));
        assert!(!(Severity::Low >= Severity::High));
    }
}

fn check_storage_collisions(_args: &UpgradeCheckArgs) -> Result<Vec<Finding>> {
    // Future: deep storage layout comparison
    Ok(Vec::new())
}

fn check_uups(_args: &UpgradeCheckArgs) -> Result<Vec<Finding>> {
    // Future: UUPS upgrade path validation
    Ok(Vec::new())
}

fn check_proxy_config(_args: &UpgradeCheckArgs) -> Result<Vec<Finding>> {
    // Future: full proxy analysis
    Ok(Vec::new())
}