use super::SimulateArgs;
use anyhow::{Context, Result};
use colored::*;
use std::time::Instant;
const SANDWICH_SENSITIVE_PATTERNS: &[&str] = &[
"amm",
"swap",
"pool",
"liquidity",
"reserve",
"price",
"twap",
"oracle",
"slippage",
"minAmount",
"amountOutMin",
"sqrtPrice",
];
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!();
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");
}
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));
}
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(())
}
struct MevSignal {
pattern: &'static str,
description: String,
}
fn analyze_mev_patterns(args: &SimulateArgs) -> Vec<MevSignal> {
let mut signals = Vec::new();
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();
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;
}
}
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(),
});
}
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(),
});
}
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(),
});
}
}
signals.dedup_by_key(|s| s.pattern);
signals
}
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();
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);
}
}
}
if trimmed.contains("Transaction") || trimmed.starts_with("==LOG==") {
txn_count += 1;
}
}
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; 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);
}
}
fn run_mev_analysis(blocks: u32) {
println!(" Analyzing {blocks} blocks for MEV opportunities...\n");
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.",
);
}