Skip to main content

forge_guard/cli/
mod.rs

1//! CLI argument parsing and command dispatch.
2
3mod audit;
4mod benchmark;
5mod chain;
6mod ci;
7mod deploy;
8mod deploy_safe;
9mod doctor;
10mod fuzz;
11mod gas;
12mod invariant;
13mod plugins;
14mod report;
15mod scan;
16mod security;
17mod simulate;
18mod upgrade_check;
19mod verify;
20mod watch;
21
22use clap::{Parser, Subcommand};
23use std::path::PathBuf;
24
25/// Forge Guard — pre-deployment smart contract auditing for Foundry.
26#[derive(Parser, Debug)]
27#[command(
28    name = "forge-guard",
29    version,
30    about = "Pre-deployment smart contract auditing framework for Foundry",
31    long_about = "The most comprehensive pre-deployment smart contract auditing framework for Foundry.\n\nTransforms security auditing from an optional step into a mandatory pre-deployment process.\nBlocks unsafe deployments by default while providing detailed vulnerability reports.",
32    author
33)]
34pub struct Cli {
35    #[command(subcommand)]
36    pub command: Commands,
37}
38
39impl Cli {
40    /// Create a CLI instance from environment arguments.
41    pub fn from_env() -> Self {
42        Self::parse()
43    }
44
45    /// Run the selected command.
46    pub fn run(&self) -> anyhow::Result<()> {
47        use anyhow::Context;
48        match &self.command {
49            Commands::Audit(args) => audit::run(args).context("Audit failed"),
50            Commands::Deploy(args) => deploy::run(args).context("Deploy failed"),
51            Commands::DeploySafe(args) => deploy_safe::run(args).context("Safe deploy failed"),
52            Commands::Fuzz(args) => fuzz::run(args).context("Fuzzing failed"),
53            Commands::Invariant(args) => invariant::run(args).context("Invariant test failed"),
54            Commands::Simulate(args) => simulate::run(args).context("Simulation failed"),
55            Commands::Gas(args) => gas::run(args).context("Gas analysis failed"),
56            Commands::Report(args) => report::run(args).context("Report generation failed"),
57            Commands::Verify(args) => verify::run(args).context("Verification failed"),
58            Commands::Doctor(args) => doctor::run(args).context("Doctor analysis failed"),
59            Commands::Watch(args) => watch::run(args).context("Watch failed"),
60            Commands::Ci(args) => ci::run(args).context("CI generation failed"),
61            Commands::Benchmark(args) => benchmark::run(args).context("Benchmark failed"),
62            Commands::Scan(args) => scan::run(args).context("Scan failed"),
63            Commands::UpgradeCheck(args) => {
64                upgrade_check::run(args).context("Upgrade check failed")
65            }
66            Commands::Plugins(args) => plugins::run(args).context("Plugin operation failed"),
67            Commands::Chain(args) => chain::run(args).context("Chain operation failed"),
68            Commands::Security(args) => security::run(args).context("Security operation failed"),
69        }
70    }
71}
72
73/// All available subcommands.
74#[derive(Subcommand, Debug)]
75pub enum Commands {
76    /// Run a comprehensive security audit
77    Audit(AuditArgs),
78    /// Deploy contracts with automatic security checks
79    Deploy(DeployArgs),
80    /// Deploy with mandatory security pass requirement
81    #[command(name = "deploy-safe")]
82    DeploySafe(DeploySafeArgs),
83    /// Run fuzzing campaigns
84    Fuzz(FuzzArgs),
85    /// Run invariant tests
86    Invariant(InvariantArgs),
87    /// Run deployment simulations
88    Simulate(SimulateArgs),
89    /// Analyze gas usage
90    Gas(GasArgs),
91    /// Generate audit reports from existing results
92    Report(ReportArgs),
93    /// Verify contract deployments
94    Verify(VerifyArgs),
95    /// Analyze project health and configuration
96    Doctor(DoctorArgs),
97    /// Watch files for changes and re-audit
98    Watch(WatchArgs),
99    /// Generate CI/CD pipeline configurations
100    Ci(CiArgs),
101    /// Run performance benchmarks
102    Benchmark(BenchmarkArgs),
103    /// Scan dependencies for vulnerabilities
104    Scan(ScanArgs),
105    /// Analyze upgrade paths and proxy safety
106    #[command(name = "upgrade-check")]
107    UpgradeCheck(UpgradeCheckArgs),
108    /// Manage audit plugins
109    Plugins(PluginArgs),
110    /// Configure chain settings
111    Chain(ChainArgs),
112    /// Configure security settings
113    Security(SecurityArgs),
114}
115
116// ── Shared CLI Flags ─────────────────────────────────────────────
117
118/// Common flags shared across many commands.
119#[derive(Debug, clap::Args)]
120pub struct SharedFlags {
121    /// Target chain to audit for
122    #[arg(long, global = true, default_value = "ethereum")]
123    pub chain: String,
124
125    /// Path to Foundry project root
126    #[arg(long, global = true, default_value = ".")]
127    pub project: PathBuf,
128
129    /// Output as JSON
130    #[arg(long, global = true)]
131    pub json: bool,
132
133    /// Output as Markdown
134    #[arg(long, global = true)]
135    pub markdown: bool,
136
137    /// Strict mode: fail on any finding
138    #[arg(long, global = true)]
139    pub strict: bool,
140
141    /// Offline mode: skip RPC calls
142    #[arg(long, global = true)]
143    pub offline: bool,
144
145    /// Production mode: extra checks
146    #[arg(long, global = true)]
147    pub production: bool,
148
149    /// Generate report file
150    #[arg(long, global = true)]
151    pub report: bool,
152
153    /// Number of parallel workers
154    #[arg(long, global = true, default_value = "4")]
155    pub parallelism: usize,
156}
157
158// ── Per-command args ─────────────────────────────────────────────
159
160#[derive(Debug, clap::Args)]
161pub struct AuditArgs {
162    #[command(flatten)]
163    pub shared: SharedFlags,
164
165    /// Run all available checks
166    #[arg(long, short)]
167    pub full: bool,
168
169    /// Quick mode — skip parser-heavy and expensive checks for fast results
170    #[arg(long)]
171    pub quick: bool,
172
173    /// Show executive summary instead of full terminal report
174    #[arg(long)]
175    pub summary: bool,
176
177    /// Enable AI-powered auditing (uses configured LLM provider)
178    #[arg(long)]
179    pub ai: bool,
180
181    /// AI provider: openai, claude, or ollama
182    #[arg(long, default_value = "openai")]
183    pub ai_provider: String,
184
185    /// AI model identifier (e.g. gpt-5, claude-5-sonnet-20260701)
186    #[arg(long, default_value = "gpt-5")]
187    pub ai_model: String,
188
189    /// AI API key (reads OPENAI_API_KEY or ANTHROPIC_API_KEY env var when empty)
190    #[arg(long)]
191    pub ai_api_key: Option<String>,
192
193    /// Ollama endpoint URL (default: http://localhost:11434)
194    #[arg(long)]
195    pub ollama_endpoint: Option<String>,
196
197    /// Run a full AI audit (security + gas + logic auditors)
198    #[arg(long)]
199    pub ai_full: bool,
200
201    /// Include exploit path analysis
202    #[arg(long)]
203    pub exploit: bool,
204
205    /// Include gas analysis
206    #[arg(long)]
207    pub gas: bool,
208
209    /// Audit all supported chains
210    #[arg(long)]
211    pub all_chains: bool,
212
213    /// Source directories to audit (comma-separated)
214    #[arg(long, default_value = "src")]
215    pub sources: String,
216
217    /// Exclude patterns (comma-separated)
218    #[arg(long)]
219    pub exclude: Option<String>,
220}
221
222#[derive(Debug, clap::Args)]
223pub struct DeployArgs {
224    #[command(flatten)]
225    pub shared: SharedFlags,
226
227    /// Contract name to deploy
228    pub contract: Option<String>,
229
230    /// Bypass deployment guard (warnings still shown)
231    #[arg(long)]
232    pub force: bool,
233
234    /// Constructor arguments (comma-separated)
235    #[arg(long)]
236    pub args: Option<String>,
237
238    /// Create2 salt
239    #[arg(long)]
240    pub salt: Option<String>,
241
242    /// Verify contract after deployment
243    #[arg(long)]
244    pub verify: bool,
245}
246
247#[derive(Debug, clap::Args)]
248pub struct DeploySafeArgs {
249    #[command(flatten)]
250    pub shared: SharedFlags,
251
252    /// Contract name to deploy
253    pub contract: Option<String>,
254
255    /// Constructor arguments (comma-separated)
256    #[arg(long)]
257    pub args: Option<String>,
258
259    /// Verify contract after deployment
260    #[arg(long)]
261    pub verify: bool,
262}
263
264#[derive(Debug, clap::Args)]
265pub struct FuzzArgs {
266    #[command(flatten)]
267    pub shared: SharedFlags,
268
269    /// Number of fuzz runs
270    #[arg(long, default_value = "10000")]
271    pub runs: u32,
272
273    /// Fuzz seed
274    #[arg(long)]
275    pub seed: Option<u64>,
276
277    /// Test function filter
278    #[arg(long)]
279    pub test: Option<String>,
280
281    /// Fuzz contract
282    pub contract: Option<String>,
283}
284
285#[derive(Debug, clap::Args)]
286pub struct InvariantArgs {
287    #[command(flatten)]
288    pub shared: SharedFlags,
289
290    /// Number of runs
291    #[arg(long, default_value = "1000")]
292    pub runs: u32,
293
294    /// Depth of calls per run
295    #[arg(long, default_value = "100")]
296    pub depth: u32,
297
298    /// Invariant contract
299    pub contract: Option<String>,
300
301    /// Fail on revert
302    #[arg(long)]
303    pub fail_on_revert: bool,
304}
305
306#[derive(Debug, clap::Args)]
307pub struct SimulateArgs {
308    #[command(flatten)]
309    pub shared: SharedFlags,
310
311    /// Number of simulation blocks
312    #[arg(long, default_value = "100")]
313    pub blocks: u32,
314
315    /// Simulate with specific deployer address
316    #[arg(long)]
317    pub deployer: Option<String>,
318
319    /// Include MEV analysis
320    #[arg(long)]
321    pub mev: bool,
322
323    /// Contract to simulate deployment for
324    pub contract: Option<String>,
325}
326
327#[derive(Debug, clap::Args)]
328pub struct GasArgs {
329    #[command(flatten)]
330    pub shared: SharedFlags,
331
332    /// Check specific contract
333    pub contract: Option<String>,
334
335    /// Compare with previous report
336    #[arg(long)]
337    pub diff: Option<String>,
338
339    /// Report gas for all functions
340    #[arg(long)]
341    pub all: bool,
342
343    /// Minimum gas threshold for warnings
344    #[arg(long, default_value = "50000")]
345    pub warn_threshold: u64,
346}
347
348#[derive(Debug, clap::Args)]
349pub struct ReportArgs {
350    #[command(flatten)]
351    pub shared: SharedFlags,
352
353    /// Path to audit result JSON
354    pub input: Option<PathBuf>,
355
356    /// Output report format
357    #[arg(long, default_value = "markdown")]
358    pub format: String,
359
360    /// Output file path
361    #[arg(long)]
362    pub output: Option<PathBuf>,
363
364    /// Include exploit paths
365    #[arg(long)]
366    pub exploit_paths: bool,
367
368    /// Summary only
369    #[arg(long)]
370    pub summary: bool,
371}
372
373#[derive(Debug, clap::Args)]
374pub struct VerifyArgs {
375    #[command(flatten)]
376    pub shared: SharedFlags,
377
378    /// Contract address to verify
379    pub address: Option<String>,
380
381    /// Contract name
382    pub name: Option<String>,
383
384    /// Explorer API key
385    #[arg(long)]
386    pub api_key: Option<String>,
387
388    /// Constructor arguments ABI-encoded
389    #[arg(long)]
390    pub constructor_args: Option<String>,
391
392    /// Check all deployments
393    #[arg(long)]
394    pub all: bool,
395}
396
397#[derive(Debug, clap::Args)]
398pub struct DoctorArgs {
399    #[command(flatten)]
400    pub shared: SharedFlags,
401
402    /// Fix issues automatically where possible
403    #[arg(long)]
404    pub fix: bool,
405
406    /// Verbose output
407    #[arg(long, short)]
408    pub verbose: bool,
409
410    /// Check only specific category
411    #[arg(long)]
412    pub check: Option<String>,
413}
414
415#[derive(Debug, clap::Args)]
416pub struct WatchArgs {
417    #[command(flatten)]
418    pub shared: SharedFlags,
419
420    /// Watch specific directories
421    #[arg(long, default_value = "src")]
422    pub dirs: String,
423
424    /// Debounce interval in ms
425    #[arg(long, default_value = "500")]
426    pub debounce_ms: u64,
427
428    /// Exclude patterns
429    #[arg(long)]
430    pub exclude: Option<String>,
431
432    /// Run full audit on change
433    #[arg(long)]
434    pub full: bool,
435}
436
437#[derive(Debug, clap::Args)]
438pub struct CiArgs {
439    #[command(flatten)]
440    pub shared: SharedFlags,
441
442    /// CI platform to generate for
443    #[arg(long, default_value = "github")]
444    pub platform: String,
445
446    /// Output directory for CI configs
447    #[arg(long, default_value = ".github/workflows")]
448    pub output: PathBuf,
449
450    /// Include deployment pipeline
451    #[arg(long)]
452    pub include_deploy: bool,
453
454    /// Overwrite existing files
455    #[arg(long)]
456    pub overwrite: bool,
457}
458
459#[derive(Debug, clap::Args)]
460pub struct BenchmarkArgs {
461    #[command(flatten)]
462    pub shared: SharedFlags,
463
464    /// Number of benchmark iterations
465    #[arg(long, default_value = "10")]
466    pub iterations: u32,
467
468    /// Compare with baseline
469    #[arg(long)]
470    pub compare: Option<PathBuf>,
471
472    /// Save benchmark results
473    #[arg(long)]
474    pub save: Option<PathBuf>,
475
476    /// Benchmark specific module
477    #[arg(long)]
478    pub module: Option<String>,
479
480    /// Warmup iterations
481    #[arg(long, default_value = "3")]
482    pub warmup: u32,
483}
484
485#[derive(Debug, clap::Args)]
486pub struct ScanArgs {
487    #[command(flatten)]
488    pub shared: SharedFlags,
489
490    /// Scan depth (0=direct, 1=direct+indirect, etc.)
491    #[arg(long, default_value = "1")]
492    pub depth: u32,
493
494    /// Update vulnerability database
495    #[arg(long)]
496    pub update: bool,
497
498    /// Output only vulnerable packages
499    #[arg(long)]
500    pub vulnerable_only: bool,
501
502    /// Fail on any vulnerability
503    #[arg(long)]
504    pub fail_fast: bool,
505}
506
507#[derive(Debug, clap::Args)]
508pub struct UpgradeCheckArgs {
509    #[command(flatten)]
510    pub shared: SharedFlags,
511
512    /// Proxy contract address
513    pub proxy: Option<String>,
514
515    /// Implementation contract address
516    pub implementation: Option<String>,
517
518    /// Check all proxies
519    #[arg(long)]
520    pub all: bool,
521
522    /// Check storage collision
523    #[arg(long)]
524    pub storage_collision: bool,
525
526    /// Check UUPS upgrade path
527    #[arg(long)]
528    pub uups: bool,
529}
530
531#[derive(Debug, clap::Args)]
532pub struct PluginArgs {
533    #[command(flatten)]
534    pub shared: SharedFlags,
535
536    /// Plugin subcommand
537    #[command(subcommand)]
538    pub action: Option<PluginAction>,
539}
540
541#[derive(Debug, Subcommand)]
542pub enum PluginAction {
543    /// List installed plugins
544    List,
545    /// Install a plugin
546    Install {
547        name: String,
548        source: Option<String>,
549    },
550    /// Remove a plugin
551    Remove { name: String },
552    /// Enable a plugin
553    Enable { name: String },
554    /// Disable a plugin
555    Disable { name: String },
556    /// Create a new plugin scaffold
557    New { name: String },
558}
559
560#[derive(Debug, clap::Args)]
561pub struct ChainArgs {
562    #[command(flatten)]
563    pub shared: SharedFlags,
564
565    /// Chain subcommand
566    #[command(subcommand)]
567    pub action: Option<ChainAction>,
568}
569
570#[derive(Debug, Subcommand)]
571pub enum ChainAction {
572    /// List supported chains
573    List,
574    /// Show chain information
575    Info { chain: String },
576    /// Add a custom chain
577    Add {
578        name: String,
579        rpc_url: Option<String>,
580        chain_id: Option<u64>,
581    },
582    /// Remove a custom chain
583    Remove { name: String },
584    /// Test chain RPC connectivity
585    Test {
586        chain: String,
587        rpc_url: Option<String>,
588    },
589}
590
591#[derive(Debug, clap::Args)]
592pub struct SecurityArgs {
593    #[command(flatten)]
594    pub shared: SharedFlags,
595
596    /// Security subcommand
597    #[command(subcommand)]
598    pub action: Option<SecurityAction>,
599}
600
601#[derive(Debug, Subcommand)]
602pub enum SecurityAction {
603    /// Show security configuration
604    Config,
605    /// Set security threshold
606    Threshold { score: u8 },
607    /// Enable a specific check
608    Enable { check: String },
609    /// Disable a specific check
610    Disable { check: String },
611    /// List all security checks
612    List,
613    /// Show security check details
614    Info { check: String },
615}