1use anyhow::Error;
2use clap::Parser;
3use clap::Subcommand;
4
5use fehler::throws;
6
7mod command;
8
9use crate::command::FuzzCommand;
10
11#[derive(Parser)]
12#[command(
13 name = "Trident",
14 about = "Trident is Rust based fuzzer for Solana programs written using Anchor framework.",
15 version = env!("CARGO_PKG_VERSION")
16)]
17struct Cli {
18 #[clap(subcommand)]
19 command: Command,
20}
21
22#[derive(Subcommand)]
23enum Command {
24 #[command(about = "Show the HowTo message.")]
25 How,
26 #[command(
27 about = "Initialize Trident in the current Anchor workspace.",
28 override_usage = "\nTrident will skip initialization if Trident.toml already exists."
29 )]
30 Init {
31 #[arg(
32 short,
33 long,
34 required = false,
35 help = "Force Trident initialization. Trident dependencies will be updated based on the version of Trident CLI."
36 )]
37 force: bool,
38 #[arg(
39 short,
40 long,
41 required = false,
42 help = "Skip building the program before initializing Trident."
43 )]
44 skip_build: bool,
45 #[arg(
46 short,
47 long,
48 required = false,
49 help = "Specify the name of the program for which fuzz test will be generated.",
50 value_name = "FILE"
51 )]
52 program_name: Option<String>,
53 #[arg(
54 short,
55 long,
56 required = false,
57 help = "Name of the fuzz test to initialize.",
58 value_name = "NAME"
59 )]
60 test_name: Option<String>,
61 #[arg(
62 long,
63 required = false,
64 num_args = 1..,
65 help = "Path(s) to IDL file(s). Specify multiple files separated by spaces. When provided, default target/idl/ directory is ignored.",
66 value_name = "FILE"
67 )]
68 idl_paths: Vec<String>,
69 },
70 #[command(
71 about = "Run fuzz subcommands.",
72 override_usage = "With fuzz subcommands you can add new fuzz test \
73 template or you can run fuzz test on already initialzied one.\
74 \n\n\x1b[1m\x1b[4mEXAMPLE:\x1b[0m\
75 \n trident fuzz add\
76 \n trident fuzz run fuzz_0\
77 \n trident fuzz debug \x1b[92m<FUZZ_TARGET>\x1b[0m \x1b[92m<SEED>\x1b[0m\
78 \n trident fuzz refresh fuzz_0"
79 )]
80 Fuzz {
81 #[clap(subcommand)]
82 subcmd: FuzzCommand,
83 },
84 #[command(about = "Clean build target, additionally perform `anchor clean`")]
85 Clean,
86 #[command(about = "Start HTTP server to serve fuzzing dashboards")]
87 Server {
88 #[arg(
89 short,
90 long,
91 required = false,
92 help = "Directory to monitor for dashboard files",
93 value_name = "DIR",
94 default_value = ".fuzz-artifacts"
95 )]
96 directory: String,
97 #[arg(
98 short,
99 long,
100 required = false,
101 help = "Port to run the server on",
102 value_name = "PORT",
103 default_value = "8000"
104 )]
105 port: u16,
106 #[arg(
107 long,
108 required = false,
109 help = "Host to bind the server to",
110 value_name = "HOST",
111 default_value = "localhost"
112 )]
113 host: String,
114 },
115 #[command(about = "Compare two regression JSON files and identify differing iteration seeds")]
116 Compare {
117 #[arg(help = "Path to the first regression JSON file", value_name = "FILE1")]
118 file1: String,
119 #[arg(help = "Path to the second regression JSON file", value_name = "FILE2")]
120 file2: String,
121 },
122}
123
124#[throws]
125pub async fn start() {
126 let cli = Cli::parse();
127
128 match cli.command {
129 Command::How => command::howto()?,
130 Command::Fuzz { subcmd } => command::fuzz(subcmd).await?,
131 Command::Init {
132 force,
133 skip_build,
134 program_name,
135 test_name,
136 idl_paths,
137 } => command::init(force, skip_build, program_name, test_name, idl_paths).await?,
138 Command::Clean => command::clean().await?,
139 Command::Server {
140 directory,
141 port,
142 host,
143 } => command::server(directory, port, host).await?,
144 Command::Compare { file1, file2 } => command::compare_regression(file1, file2)?,
145 }
146}