Skip to main content

trident_cli/
lib.rs

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}