Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/cli/src/command/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use trident_client::___private::TestGenerator;
use crate::command::check_fuzz_test_exists;
use crate::command::check_fuzz_test_not_exists;
use crate::command::check_trident_uninitialized;
use crate::command::ensure_running_from_trident_tests;
use crate::command::get_project_root_for_fuzz;
use crate::command::is_anchor_project;
use crate::command::validate_program_name_usage;
Expand Down Expand Up @@ -139,11 +140,13 @@ pub(crate) async fn fuzz(subcmd: FuzzCommand) {
exit_code,
seed,
} => {
ensure_running_from_trident_tests(&root)?;
let commander = Commander::new(&root);

commander.run(target, exit_code, seed).await?;
}
FuzzCommand::Debug { target, seed } => {
ensure_running_from_trident_tests(&root)?;
let commander = Commander::new(&root);

commander.run_debug(target, seed).await?;
Expand Down
10 changes: 10 additions & 0 deletions crates/cli/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ fn check_trident_uninitialized(root: &str) {
}
}

#[throws]
fn ensure_running_from_trident_tests(root: &str) {
let trident_tests_dir = Path::new(root).join(TESTS_WORKSPACE_DIRECTORY);
let current_dir = std::env::current_dir()?;

if !current_dir.starts_with(&trident_tests_dir) {
bail!("Run this command from trident-tests.",);
}
}

#[throws]
fn check_fuzz_test_exists(root: &str, fuzz_test_name: &str) {
let fuzz_test_dir = Path::new(&root)
Expand Down
4 changes: 2 additions & 2 deletions crates/client/src/commander/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl Commander {
exit_code_mode: Option<ExitCodeMode>,
seed: Option<String>,
) {
let config = TridentConfig::new();
let config = TridentConfig::try_new()?;

if config.get_metrics() {
std::env::set_var("FUZZING_METRICS", "true");
Expand Down Expand Up @@ -184,7 +184,7 @@ impl Commander {

#[throws]
pub async fn run_debug(&self, target: String, seed: String) {
let config = TridentConfig::new();
let config = TridentConfig::try_new()?;

if config.get_metrics() {
if config.get_metrics_json() {
Expand Down
8 changes: 5 additions & 3 deletions crates/client/src/commander/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ mod fuzz;

#[derive(Error, Debug)]
pub enum Error {
#[error("{0:?}")]
#[error("{0}")]
Io(#[from] io::Error),
#[error("{0:?}")]
#[error("{0}")]
Utf8(#[from] FromUtf8Error),
#[error("build programs failed")]
BuildProgramsFailed,
Expand All @@ -33,8 +33,10 @@ pub enum Error {
Coverage(#[from] crate::coverage::CoverageError),
#[error("Cannot find the trident-tests directory in the current workspace")]
BadWorkspace,
#[error("{0:?}")]
#[error("{0}")]
Anyhow(#[from] anyhow::Error),
#[error("Invalid Trident.toml configuration: {0}")]
Config(#[from] trident_config::Error),
}

/// `Commander` allows you to start localnet, build programs,
Expand Down
1 change: 1 addition & 0 deletions crates/config/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// tomls
pub(crate) const TRIDENT_TOML: &str = "Trident.toml";
pub(crate) const TESTS_WORKSPACE_DIRECTORY: &str = "trident-tests";

// fuzz
pub(crate) const DEFAULT_LOOPCOUNT: u64 = 0;
Expand Down
1 change: 1 addition & 0 deletions crates/config/src/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::constants::DEFAULT_COVERAGE_SERVER_PORT;
use crate::constants::DEFAULT_LOOPCOUNT;

#[derive(Debug, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct Coverage {
pub enable: Option<bool>,
pub server_port: Option<u16>,
Expand Down
182 changes: 177 additions & 5 deletions crates/config/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::coverage::Coverage;
use crate::metrics::Metrics;
use crate::regression::Regression;
use crate::utils::resolve_path;
use crate::Error;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use serde::Deserialize;
Expand All @@ -13,6 +14,7 @@ use std::fs;
use std::str::FromStr;

#[derive(Debug, Deserialize, Clone, Default)]
#[serde(deny_unknown_fields)]
pub struct Fuzz {
metrics: Option<Metrics>,
regression: Option<Regression>,
Expand Down Expand Up @@ -55,28 +57,94 @@ impl Fuzz {
self.coverage.clone().unwrap_or_default()
}

pub fn get_forks(&self) -> Vec<FuzzFork> {
pub fn get_forks(&self) -> Result<Vec<FuzzFork>, Error> {
match self.fork.as_ref() {
Some(forks) => forks.iter().map(FuzzFork::from).collect(),
None => Vec::default(),
Some(forks) => forks.iter().map(FuzzFork::try_from_raw).collect(),
None => Ok(Vec::default()),
}
}

/// Lightweight preflight validation used by CLI startup checks.
/// This validates addresses and file path existence without loading
/// full program binaries/account payloads into memory.
pub fn validate_preflight(&self) -> Result<(), Error> {
if let Some(programs) = &self.programs {
for program in programs {
let _ = Pubkey::from_str(&program.address).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Cannot parse the program address: {}",
program.address
))
})?;
if let Some(authority) = program.upgrade_authority.as_ref() {
let _ = Pubkey::from_str(authority).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Cannot parse upgrade authority: {}",
authority
))
})?;
}

let path = resolve_path(&program.program)?;
if !path.exists() {
return Err(Error::Anyhow(anyhow::anyhow!(
"Failed to read file: {}",
program.program
)));
}
}
}

if let Some(accounts) = &self.accounts {
for account in accounts {
let _ = Pubkey::from_str(&account.address).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Cannot parse account address: {}",
account.address
))
})?;
let path = resolve_path(&account.filename)?;
if !path.exists() {
return Err(Error::Anyhow(anyhow::anyhow!(
"Failed to read file: {}",
account.filename
)));
}
}
}

if let Some(forks) = &self.fork {
for fork in forks {
let _ = Pubkey::from_str(&fork.address).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Cannot parse fork address: {}",
fork.address
))
})?;
}
}

Ok(())
}
}

#[derive(Debug, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct _FuzzProgram {
pub address: String,
pub upgrade_authority: Option<String>,
pub program: String,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct _FuzzAccount {
pub address: String,
pub filename: String,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct _FuzzFork {
pub address: String,
pub cluster: String,
Expand Down Expand Up @@ -142,6 +210,22 @@ impl From<&_FuzzFork> for FuzzFork {
}
}
}
impl FuzzFork {
pub fn try_from_raw(value: &_FuzzFork) -> Result<Self, Error> {
let address = Pubkey::from_str(&value.address).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Cannot parse fork address: {}",
value.address
))
})?;

Ok(FuzzFork {
address,
cluster: FuzzCluster::parse(&value.cluster),
overwrite: value.overwrite,
})
}
}

#[derive(Debug, Deserialize, Clone)]
pub struct FuzzProgram {
Expand All @@ -160,7 +244,8 @@ impl From<&_FuzzProgram> for FuzzProgram {
.as_ref()
.map(|upgrade_authority| Pubkey::from_str(upgrade_authority).unwrap());

let path = resolve_path(program_path);
let path = resolve_path(program_path)
.unwrap_or_else(|_| panic!("Failed to resolve path: {}", program_path));

let program_data =
fs::read(path).unwrap_or_else(|_| panic!("Failed to read file: {}", program_path));
Expand All @@ -175,6 +260,37 @@ impl From<&_FuzzProgram> for FuzzProgram {
}
}
}
impl FuzzProgram {
pub fn try_from_raw(value: &_FuzzProgram) -> Result<Self, Error> {
let path = resolve_path(&value.program)?;
let program_data = fs::read(path).map_err(|_| {
Error::Anyhow(anyhow::anyhow!("Failed to read file: {}", value.program))
})?;

let address = Pubkey::from_str(&value.address).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Cannot parse the program address: {}",
value.address
))
})?;

let upgrade_authority = match value.upgrade_authority.as_ref() {
Some(authority) => Some(Pubkey::from_str(authority).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Cannot parse upgrade authority: {}",
authority
))
})?),
None => None,
};

Ok(FuzzProgram {
address,
upgrade_authority,
data: program_data,
})
}
}

#[derive(Debug, Deserialize, Clone)]
pub struct FuzzAccount {
Expand All @@ -186,7 +302,8 @@ impl From<&_FuzzAccount> for FuzzAccount {
fn from(_f: &_FuzzAccount) -> Self {
let account_path = &_f.filename;

let path = resolve_path(account_path);
let path = resolve_path(account_path)
.unwrap_or_else(|_| panic!("Failed to resolve path: {}", account_path));

let file_content = fs::read_to_string(path)
.unwrap_or_else(|_| panic!("Failed to read file: {}", account_path));
Expand Down Expand Up @@ -224,14 +341,69 @@ impl From<&_FuzzAccount> for FuzzAccount {
FuzzAccount { pubkey, account }
}
}
impl FuzzAccount {
pub fn try_from_raw(value: &_FuzzAccount) -> Result<Self, Error> {
let path = resolve_path(&value.filename)?;
let file_content = fs::read_to_string(path).map_err(|_| {
Error::Anyhow(anyhow::anyhow!("Failed to read file: {}", value.filename))
})?;

let account_raw: FuzzAccountRaw = serde_json::from_str(&file_content).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Failed to parse JSON from file: {}",
value.filename
))
})?;

let pubkey = Pubkey::from_str(&account_raw.pubkey).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Cannot convert address for: {}",
account_raw.pubkey
))
})?;

let owner_address = Pubkey::from_str(&account_raw.account.owner).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Cannot convert address for owner: {}",
account_raw.account.owner
))
})?;

let data_base_64 = account_raw.account.data.first().ok_or_else(|| {
Error::Anyhow(anyhow::anyhow!(
"Cannot read base64 data for account: {}",
account_raw.pubkey
))
})?;

let data = BASE64_STANDARD.decode(data_base_64).map_err(|_| {
Error::Anyhow(anyhow::anyhow!(
"Failed to decode base64 data of {}",
value.filename
))
})?;

let account = AccountSharedData::create(
account_raw.account.lamports,
data,
owner_address,
account_raw.account.executable,
account_raw.account.rent_epoch,
);

Ok(FuzzAccount { pubkey, account })
}
}

#[derive(Debug, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct FuzzAccountRaw {
pub pubkey: String,
pub account: AccountRaw,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct AccountRaw {
pub lamports: u64,
pub data: Vec<String>,
Expand Down
Loading
Loading