use crate::chains::ChainRegistry;
use crate::core::{OutputFormat, ProjectConfig};
use crate::deployment::DeploymentGuard;
use crate::plugins::PluginRegistry;
use crate::security::SecurityEngine;
use crate::utils::Cache;
use super::DeploySafeArgs;
use anyhow::{Context, Result};
use colored::*;
pub fn run(args: &DeploySafeArgs) -> Result<()> {
let mut config = ProjectConfig::from_default_location();
config.chain = args.shared.chain.clone();
config.project_root = args.shared.project.clone();
config.strict = true; config.offline = args.shared.offline;
config.production = args.shared.production;
config.output = if args.shared.json {
OutputFormat::Json
} else {
OutputFormat::Terminal
};
let _cache = Cache::new(&config)?;
let chain_registry = ChainRegistry::default();
let plugin_registry = PluginRegistry::new(&config)?;
let security_engine = SecurityEngine::new(&config, &plugin_registry)?;
let deploy_guard = DeploymentGuard::new(&config, &security_engine)?;
eprintln!("{}", "🔒 Forge Guard — Safe Deployment Mode".bold().green());
eprintln!(
"{}",
" Deployment requires a MANDATORY security pass.".bold()
);
eprintln!("{}", " This mode cannot be bypassed.".dimmed());
eprintln!("{}", " ─────────────────────────────────────".dimmed());
let findings = deploy_guard.run_pre_deployment_checks(&config, &chain_registry)?;
let scores = security_engine.calculate_scores(&findings);
let overall = deploy_guard.calculate_overall_score(&scores);
let risk = deploy_guard.determine_risk_level(overall, &findings);
println!(
"\n{}",
"═══════════════════════════════════════".bright_green()
);
println!(
"{}",
" SAFE DEPLOYMENT — AUDIT REPORT "
.bright_green()
.bold()
);
println!(
"{}",
"═══════════════════════════════════════".bright_green()
);
println!(
"\n 📊 Security Score: {}/100",
overall
.to_string()
.color(if overall >= 85 {
"green"
} else if overall >= 70 {
"yellow"
} else {
"red"
})
.bold()
);
println!(" ⚠️ Risk Level: {}", format!("{}", risk).bold());
println!(" 🛡️ Findings: {} total", findings.len());
let blocking = findings
.iter()
.filter(|f| f.blocks_deployment || f.severity >= crate::core::Severity::High)
.count();
if blocking == 0 && overall >= config.min_deployment_score {
println!(
"\n {} {}",
"✅ SAFE DEPLOYMENT: APPROVED".green().bold(),
"All security checks passed.".green()
);
if let Some(contract) = &args.contract {
println!(" 📄 Contract: {}", contract);
}
let mut cmd = std::process::Command::new("forge");
cmd.arg("create");
if let Some(contract) = &args.contract {
cmd.arg("--contract").arg(contract);
}
let rpc = crate::deployment::verifier::rpc_url_for_chain(&args.shared.chain);
cmd.arg("--rpc-url").arg(&rpc);
let output = cmd.output().context("Forge create failed")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Deployment failed: {}", stderr);
}
let stdout = String::from_utf8_lossy(&output.stdout);
println!("{}", stdout);
if args.verify || config.deployment.auto_verify {
let deployed_addr = crate::cli::deploy::extract_deployed_address(&stdout);
if let Some(ref addr) = deployed_addr {
eprintln!("\n🔍 Running post-deployment verification...");
let contract_name = args.contract.as_deref().unwrap_or("Contract");
let result =
deploy_guard.verify_contract(addr, contract_name, args.args.as_deref());
match result {
Ok(vr) if vr.verified => {
println!(" ✅ {}", vr.details.green());
}
Ok(vr) => {
eprintln!(" ⚠️ {}", vr.details.yellow());
eprintln!(" 🔄 Trying bytecode match...");
let bm = deploy_guard.verify_bytecode(addr, contract_name, &rpc);
if bm.verified {
println!(" ✅ {}", bm.details.green());
}
}
Err(e) => eprintln!(" ⚠️ Verification error: {}", e),
}
}
}
} else {
println!(
"\n {} {}",
"❌ SAFE DEPLOYMENT: BLOCKED".red().bold(),
"Security requirements not met.".red()
);
for f in findings
.iter()
.filter(|f| f.blocks_deployment || f.severity >= crate::core::Severity::High)
.take(10)
{
println!(
" • [{}] {}",
f.severity.to_string().red().bold(),
f.title
);
println!(
" 📄 {}:{}",
f.file.as_deref().unwrap_or("?"),
f.line.map_or("?".into(), |l| l.to_string())
);
println!(" 💡 {}", f.recommendation);
}
anyhow::bail!("Safe deployment blocked — security requirements not met");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deploy_safe_arg_defaults() {
let args = DeploySafeArgs {
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,
},
contract: None,
args: None,
verify: false,
};
assert!(args.contract.is_none());
assert!(args.args.is_none());
assert!(!args.verify);
}
#[test]
fn test_deploy_safe_with_contract() {
let args = DeploySafeArgs {
shared: super::super::SharedFlags::default(),
contract: Some("Vault".into()),
args: Some("0xowner".into()),
verify: true,
};
assert_eq!(args.contract.as_deref(), Some("Vault"));
assert_eq!(args.args.as_deref(), Some("0xowner"));
assert!(args.verify);
}
#[test]
fn test_deploy_safe_default_output_format_is_terminal() {
let config = ProjectConfig::default();
assert_eq!(config.output, crate::core::OutputFormat::Terminal);
}
}