mod analyzers;
mod models;
use anyhow::Result;
use clap::{Parser, ArgAction};
use std::path::PathBuf;
use solana_fender::{Severity, analyze_program_dir, analyze_program_file, findings_to_markdown};
use std::collections::HashMap;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long, group = "target")]
program: Option<PathBuf>,
#[arg(short, long, group = "target")]
file: Option<PathBuf>,
#[arg(long, action = ArgAction::SetTrue)]
ignore_low: bool,
#[arg(long, action = ArgAction::SetTrue)]
ignore_medium: bool,
#[arg(long, action = ArgAction::SetTrue)]
ignore_high: bool,
#[arg(long, action = ArgAction::SetTrue)]
ignore_critical: bool,
#[arg(long, action = ArgAction::SetTrue)]
debug: bool,
#[arg(long, action = ArgAction::SetTrue)]
markdown: bool,
#[arg(long)]
output: Option<PathBuf>,
}
fn main() -> Result<()> {
let args = Args::parse();
std::env::set_var("SOLANA_FENDER_DEBUG", args.debug.to_string());
std::env::set_var("SOLANA_FENDER_SUPPRESS_OUTPUT", args.markdown.to_string());
if args.program.is_none() && args.file.is_none() {
eprintln!("Error: Either --program or --file must be specified");
std::process::exit(1);
}
let mut findings = if let Some(program_path) = args.program.as_ref() {
analyze_program_dir(program_path.clone())?
} else if let Some(file_path) = args.file.as_ref() {
analyze_program_file(file_path.clone())?
} else {
unreachable!("Either program or file must be provided due to clap group constraint");
};
findings.retain(|finding| {
match finding.severity {
Severity::Low => !args.ignore_low,
Severity::Medium => !args.ignore_medium,
Severity::High => !args.ignore_high,
Severity::Critical => !args.ignore_critical,
}
});
if args.markdown {
let program_name = if let Some(program_path) = args.program.as_ref() {
program_path.file_name()
.and_then(|name| name.to_str())
.unwrap_or("solana_program")
} else if let Some(file_path) = args.file.as_ref() {
file_path.file_name()
.and_then(|name| name.to_str())
.unwrap_or("solana_file")
} else {
"solana_program"
};
let markdown_output = findings_to_markdown(
findings.clone(),
program_name,
args.output.as_deref(),
)?;
if args.output.is_none() {
println!("{}", markdown_output);
}
}
if findings.is_empty() {
Ok(())
} else {
if !args.markdown {
println!("\nFound {} security issues in the program", findings.len());
}
std::process::exit(1);
}
}