trident_cli/
lib.rs

1use anyhow::Error;
2use anyhow::{Context, Result};
3use clap::{Parser, Subcommand};
4use fehler::throws;
5
6// subcommand functions to call and nested subcommands
7mod command;
8// bring nested subcommand enums into scope
9use command::FuzzCommand;
10use termimad::MadSkin;
11
12macro_rules! load_template {
13    ($file:expr) => {
14        include_str!(concat!(env!("CARGO_MANIFEST_DIR"), $file))
15    };
16}
17
18/// Simple program to greet a person
19#[derive(Parser)]
20#[command(
21    name = "Trident",
22    about = "Trident is Rust based fuzzer for Solana programs written using Anchor framework."
23)]
24struct Cli {
25    #[clap(subcommand)]
26    command: Command,
27}
28
29#[derive(Subcommand)]
30enum Command {
31    #[command(about = "Show the HowTo message.")]
32    How,
33    #[command(
34        about = "Initialize Trident in the current Anchor workspace.",
35        override_usage = "\nTrident will skip initialization if Trident.toml already exists."
36    )]
37    Init {
38        #[arg(
39            short,
40            long,
41            required = false,
42            help = "Force Trident initialization. Trident dependencies will be updated based on the version of Trident CLI."
43        )]
44        force: 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    },
62    #[command(
63        about = "Run fuzz subcommands.",
64        override_usage = "With fuzz subcommands you can add new fuzz test \
65        template or you can run fuzz test on already initialzied one.\
66        \n\n\x1b[1m\x1b[4mEXAMPLE:\x1b[0m\
67        \n    trident add\
68        \n    trident fuzz run-hfuzz fuzz_0\
69        \n    trident fuzz debug-hfuzz \x1b[92m<FUZZ_TARGET>\x1b[0m \x1b[92m<PATH_TO_CRASHFILE>\x1b[0m"
70    )]
71    Fuzz {
72        #[clap(subcommand)]
73        subcmd: FuzzCommand,
74    },
75    #[command(about = "Clean Honggfuzz build targets ,additionally perform `anchor clean`")]
76    Clean,
77}
78
79#[throws]
80pub async fn start() {
81    let cli = Cli::parse();
82
83    match cli.command {
84        Command::How => command::howto()?,
85        Command::Fuzz { subcmd } => command::fuzz(subcmd).await?,
86        Command::Init {
87            force,
88            program_name,
89            test_name,
90        } => command::init(force, program_name, test_name).await?,
91        Command::Clean => command::clean().await?,
92    }
93}
94
95// Climbs each parent directory until we find target.
96fn _discover(target: &str) -> Result<Option<String>> {
97    let _cwd = std::env::current_dir()?;
98    let mut cwd_opt = Some(_cwd.as_path());
99
100    while let Some(cwd) = cwd_opt {
101        for f in std::fs::read_dir(cwd)
102            .with_context(|| format!("Error reading the directory with path: {}", cwd.display()))?
103        {
104            let p = f
105                .with_context(|| {
106                    format!("Error reading the directory with path: {}", cwd.display())
107                })?
108                .path();
109            if let Some(filename) = p.file_name() {
110                if filename.to_str() == Some(target) {
111                    return Ok(Some(cwd.to_string_lossy().to_string()));
112                }
113            }
114        }
115
116        cwd_opt = cwd.parent();
117    }
118
119    Ok(None)
120}
121
122fn show_howto() {
123    let markdown_input = load_template!("/src/howto.md");
124
125    // Create a MadSkin for styling the Markdown.
126    let skin = MadSkin::default();
127
128    // Print the markdown content to the terminal.
129    skin.print_text(markdown_input);
130}