1mod 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#[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 pub fn from_env() -> Self {
42 Self::parse()
43 }
44
45 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#[derive(Subcommand, Debug)]
75pub enum Commands {
76 Audit(AuditArgs),
78 Deploy(DeployArgs),
80 #[command(name = "deploy-safe")]
82 DeploySafe(DeploySafeArgs),
83 Fuzz(FuzzArgs),
85 Invariant(InvariantArgs),
87 Simulate(SimulateArgs),
89 Gas(GasArgs),
91 Report(ReportArgs),
93 Verify(VerifyArgs),
95 Doctor(DoctorArgs),
97 Watch(WatchArgs),
99 Ci(CiArgs),
101 Benchmark(BenchmarkArgs),
103 Scan(ScanArgs),
105 #[command(name = "upgrade-check")]
107 UpgradeCheck(UpgradeCheckArgs),
108 Plugins(PluginArgs),
110 Chain(ChainArgs),
112 Security(SecurityArgs),
114}
115
116#[derive(Debug, clap::Args)]
120pub struct SharedFlags {
121 #[arg(long, global = true, default_value = "ethereum")]
123 pub chain: String,
124
125 #[arg(long, global = true, default_value = ".")]
127 pub project: PathBuf,
128
129 #[arg(long, global = true)]
131 pub json: bool,
132
133 #[arg(long, global = true)]
135 pub markdown: bool,
136
137 #[arg(long, global = true)]
139 pub strict: bool,
140
141 #[arg(long, global = true)]
143 pub offline: bool,
144
145 #[arg(long, global = true)]
147 pub production: bool,
148
149 #[arg(long, global = true)]
151 pub report: bool,
152
153 #[arg(long, global = true, default_value = "4")]
155 pub parallelism: usize,
156}
157
158#[derive(Debug, clap::Args)]
161pub struct AuditArgs {
162 #[command(flatten)]
163 pub shared: SharedFlags,
164
165 #[arg(long, short)]
167 pub full: bool,
168
169 #[arg(long)]
171 pub quick: bool,
172
173 #[arg(long)]
175 pub summary: bool,
176
177 #[arg(long)]
179 pub ai: bool,
180
181 #[arg(long, default_value = "openai")]
183 pub ai_provider: String,
184
185 #[arg(long, default_value = "gpt-5")]
187 pub ai_model: String,
188
189 #[arg(long)]
191 pub ai_api_key: Option<String>,
192
193 #[arg(long)]
195 pub ollama_endpoint: Option<String>,
196
197 #[arg(long)]
199 pub ai_full: bool,
200
201 #[arg(long)]
203 pub exploit: bool,
204
205 #[arg(long)]
207 pub gas: bool,
208
209 #[arg(long)]
211 pub all_chains: bool,
212
213 #[arg(long, default_value = "src")]
215 pub sources: String,
216
217 #[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 pub contract: Option<String>,
229
230 #[arg(long)]
232 pub force: bool,
233
234 #[arg(long)]
236 pub args: Option<String>,
237
238 #[arg(long)]
240 pub salt: Option<String>,
241
242 #[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 pub contract: Option<String>,
254
255 #[arg(long)]
257 pub args: Option<String>,
258
259 #[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 #[arg(long, default_value = "10000")]
271 pub runs: u32,
272
273 #[arg(long)]
275 pub seed: Option<u64>,
276
277 #[arg(long)]
279 pub test: Option<String>,
280
281 pub contract: Option<String>,
283}
284
285#[derive(Debug, clap::Args)]
286pub struct InvariantArgs {
287 #[command(flatten)]
288 pub shared: SharedFlags,
289
290 #[arg(long, default_value = "1000")]
292 pub runs: u32,
293
294 #[arg(long, default_value = "100")]
296 pub depth: u32,
297
298 pub contract: Option<String>,
300
301 #[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 #[arg(long, default_value = "100")]
313 pub blocks: u32,
314
315 #[arg(long)]
317 pub deployer: Option<String>,
318
319 #[arg(long)]
321 pub mev: bool,
322
323 pub contract: Option<String>,
325}
326
327#[derive(Debug, clap::Args)]
328pub struct GasArgs {
329 #[command(flatten)]
330 pub shared: SharedFlags,
331
332 pub contract: Option<String>,
334
335 #[arg(long)]
337 pub diff: Option<String>,
338
339 #[arg(long)]
341 pub all: bool,
342
343 #[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 pub input: Option<PathBuf>,
355
356 #[arg(long, default_value = "markdown")]
358 pub format: String,
359
360 #[arg(long)]
362 pub output: Option<PathBuf>,
363
364 #[arg(long)]
366 pub exploit_paths: bool,
367
368 #[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 pub address: Option<String>,
380
381 pub name: Option<String>,
383
384 #[arg(long)]
386 pub api_key: Option<String>,
387
388 #[arg(long)]
390 pub constructor_args: Option<String>,
391
392 #[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 #[arg(long)]
404 pub fix: bool,
405
406 #[arg(long, short)]
408 pub verbose: bool,
409
410 #[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 #[arg(long, default_value = "src")]
422 pub dirs: String,
423
424 #[arg(long, default_value = "500")]
426 pub debounce_ms: u64,
427
428 #[arg(long)]
430 pub exclude: Option<String>,
431
432 #[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 #[arg(long, default_value = "github")]
444 pub platform: String,
445
446 #[arg(long, default_value = ".github/workflows")]
448 pub output: PathBuf,
449
450 #[arg(long)]
452 pub include_deploy: bool,
453
454 #[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 #[arg(long, default_value = "10")]
466 pub iterations: u32,
467
468 #[arg(long)]
470 pub compare: Option<PathBuf>,
471
472 #[arg(long)]
474 pub save: Option<PathBuf>,
475
476 #[arg(long)]
478 pub module: Option<String>,
479
480 #[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 #[arg(long, default_value = "1")]
492 pub depth: u32,
493
494 #[arg(long)]
496 pub update: bool,
497
498 #[arg(long)]
500 pub vulnerable_only: bool,
501
502 #[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 pub proxy: Option<String>,
514
515 pub implementation: Option<String>,
517
518 #[arg(long)]
520 pub all: bool,
521
522 #[arg(long)]
524 pub storage_collision: bool,
525
526 #[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 #[command(subcommand)]
538 pub action: Option<PluginAction>,
539}
540
541#[derive(Debug, Subcommand)]
542pub enum PluginAction {
543 List,
545 Install {
547 name: String,
548 source: Option<String>,
549 },
550 Remove { name: String },
552 Enable { name: String },
554 Disable { name: String },
556 New { name: String },
558}
559
560#[derive(Debug, clap::Args)]
561pub struct ChainArgs {
562 #[command(flatten)]
563 pub shared: SharedFlags,
564
565 #[command(subcommand)]
567 pub action: Option<ChainAction>,
568}
569
570#[derive(Debug, Subcommand)]
571pub enum ChainAction {
572 List,
574 Info { chain: String },
576 Add {
578 name: String,
579 rpc_url: Option<String>,
580 chain_id: Option<u64>,
581 },
582 Remove { name: String },
584 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 #[command(subcommand)]
598 pub action: Option<SecurityAction>,
599}
600
601#[derive(Debug, Subcommand)]
602pub enum SecurityAction {
603 Config,
605 Threshold { score: u8 },
607 Enable { check: String },
609 Disable { check: String },
611 List,
613 Info { check: String },
615}