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::DeployArgs;
use anyhow::{Context, Result};
use colored::*;
pub fn run(args: &DeployArgs) -> Result<()> {
let mut config = ProjectConfig::from_default_location();
config.chain = args.shared.chain.clone();
config.project_root = args.shared.project.clone();
config.strict = args.shared.strict;
config.offline = args.shared.offline;
config.production = args.shared.production;
config.output = if args.shared.json {
OutputFormat::Json
} else if args.shared.markdown {
OutputFormat::Markdown
} 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 — Deployment Guard".bold());
eprintln!("{}", "───────────────────────────────────".dimmed());
eprintln!("\n📋 Phase 1: Running pre-deployment security audit...");
let findings = deploy_guard.run_pre_deployment_checks(&config, &chain_registry)?;
eprintln!("\n📊 Phase 2: Analyzing risk...");
let scores = security_engine.calculate_scores(&findings);
let overall = deploy_guard.calculate_overall_score(&scores);
let risk = deploy_guard.determine_risk_level(overall, &findings);
eprintln!("\n⚖️ Phase 3: Deployment decision...");
let can_deploy = if args.force {
eprintln!(
"⚠️ {} {}",
"WARNING:".yellow().bold(),
"Deployment forced with --force. Warnings still apply.".yellow()
);
true
} else {
deploy_guard.can_deploy(&findings, overall, risk)
};
println!(
"\n{}",
"═══════════════════════════════════════".bright_blue()
);
println!(
"{}",
" DEPLOYMENT GUARD REPORT "
.bright_blue()
.bold()
);
println!(
"{}",
"═══════════════════════════════════════".bright_blue()
);
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)
.color(match risk {
crate::core::RiskLevel::Critical => "red",
crate::core::RiskLevel::High => "red",
crate::core::RiskLevel::Medium => "yellow",
_ => "green",
})
.bold()
);
println!(
" Findings: {} total, {} critical, {} high",
findings.len(),
findings
.iter()
.filter(|f| f.severity == crate::core::Severity::Critical)
.count(),
findings
.iter()
.filter(|f| f.severity == crate::core::Severity::High)
.count(),
);
if can_deploy {
println!(
"\n {} {}",
"✅ DEPLOYMENT: APPROVED".green().bold(),
if args.force {
"(forced)".yellow()
} else {
"".normal()
}
);
if let Some(contract) = &args.contract {
println!(" 📄 Contract: {}", contract);
}
if let Some(salt) = &args.salt {
println!(" 🧂 Create2 Salt: {}", salt);
}
eprintln!("\n🚀 Proceeding with deployment...");
let deploy_output = execute_forge_deploy(args)?;
let deployed_address = extract_deployed_address(&deploy_output);
if let Some(ref addr) = deployed_address {
println!("\n 📍 Deployed at: {}", addr);
if args.verify || config.deployment.auto_verify {
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 rpc =
crate::deployment::verifier::rpc_url_for_chain(&args.shared.chain);
let bm = deploy_guard.verify_bytecode(addr, contract_name, &rpc);
if bm.verified {
println!(" ✅ {}", bm.details.green());
} else {
eprintln!(" ⚠️ {}", bm.details.yellow());
}
}
Err(e) => {
eprintln!(" ⚠️ Verification error: {}", e);
}
}
}
}
} else {
println!(
"\n {} {}",
"❌ DEPLOYMENT: BLOCKED".red().bold(),
"(use --force to bypass)".yellow()
);
println!(
"\n Blocked by {} issues:",
findings.iter().filter(|f| f.blocks_deployment).count()
);
for f in findings.iter().filter(|f| f.blocks_deployment).take(5) {
println!(
" • [{}] {} — {}",
f.severity.to_string().red().bold(),
f.title,
f.file.as_deref().unwrap_or("?")
);
println!(" 💡 {}", f.recommendation);
}
if !args.force {
println!(
"\n {} Use {} to bypass: forge deploy --force",
"⚠️".yellow(),
"WARNING".yellow().bold()
);
}
anyhow::bail!("Deployment blocked by security guard");
}
Ok(())
}
fn execute_forge_deploy(args: &DeployArgs) -> Result<String> {
let mut cmd = std::process::Command::new("forge");
cmd.arg("create");
if let Some(contract) = &args.contract {
cmd.arg("--contract").arg(contract);
}
if let Some(salt) = &args.salt {
cmd.arg("--salt").arg(salt);
}
if let Some(args_str) = &args.args {
for arg in args_str.split(',') {
cmd.arg("--constructor-args").arg(arg.trim());
}
}
cmd.arg("--rpc-url");
cmd.arg(crate::deployment::verifier::rpc_url_for_chain(
&args.shared.chain,
));
let output = cmd.output().context("Failed to execute forge create")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
eprintln!("{}", stderr);
anyhow::bail!("Forge deployment failed");
}
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
println!("{}", stdout);
Ok(stdout)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_deployed_address_from_forge_output() {
let output = "Deployed to: 0x1234567890123456789012345678901234567890\n";
let addr = extract_deployed_address(output);
assert_eq!(
addr,
Some("0x1234567890123456789012345678901234567890".into())
);
}
#[test]
fn test_extract_deployed_address_no_match() {
let output = "Error: something went wrong\n";
assert_eq!(extract_deployed_address(output), None);
}
#[test]
fn test_extract_deployed_address_empty() {
assert_eq!(extract_deployed_address(""), None);
}
#[test]
fn test_extract_deployed_address_from_json() {
let output = r##"{"deployedTo":"0xdead00000000000000000000000000000000beef"}"##;
let addr = extract_deployed_address(output);
assert_eq!(
addr,
Some("0xdead00000000000000000000000000000000beef".into())
);
}
#[test]
fn test_extract_deployed_address_from_json_address_field() {
let output = r##"{"address":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}"##;
let addr = extract_deployed_address(output);
assert_eq!(
addr,
Some("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into())
);
}
#[test]
fn test_extract_deployed_address_multiline_stdout() {
let output =
"some log line\nDeployed to: 0x1111111111111111111111111111111111111111\ntrailing\n";
let addr = extract_deployed_address(output);
assert_eq!(
addr,
Some("0x1111111111111111111111111111111111111111".into())
);
}
#[test]
fn test_extract_deployed_address_case_insensitive_hex() {
let output = "Deployed to: 0XABCDEF0123456789abcdef0123456789abcdef01\n";
let addr = extract_deployed_address(output);
assert_eq!(
addr,
Some("0XABCDEF0123456789abcdef0123456789abcdef01".into())
);
}
}
pub fn extract_deployed_address(output: &str) -> Option<String> {
for line in output.lines() {
let trimmed = line.trim();
if let Some(addr) = trimmed.strip_prefix("Deployed to: ") {
let addr = addr.trim();
if addr.starts_with("0x") || addr.starts_with("0X") {
return Some(addr.to_owned());
}
}
}
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(output) {
if let Some(addr) = parsed["deployedTo"].as_str() {
return Some(addr.to_owned());
}
if let Some(addr) = parsed["address"].as_str() {
return Some(addr.to_owned());
}
}
None
}