Skip to content

Commit d2e6e5f

Browse files
author
lukacan
committed
🐛 Improve errors for unexpected cases such as trident toml mistakes
1 parent 4658897 commit d2e6e5f

13 files changed

Lines changed: 302 additions & 38 deletions

File tree

crates/cli/src/command/fuzz.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use trident_client::___private::TestGenerator;
1111
use crate::command::check_fuzz_test_exists;
1212
use crate::command::check_fuzz_test_not_exists;
1313
use crate::command::check_trident_uninitialized;
14+
use crate::command::ensure_running_from_trident_tests;
1415
use crate::command::get_project_root_for_fuzz;
1516
use crate::command::is_anchor_project;
1617
use crate::command::validate_program_name_usage;
@@ -139,11 +140,13 @@ pub(crate) async fn fuzz(subcmd: FuzzCommand) {
139140
exit_code,
140141
seed,
141142
} => {
143+
ensure_running_from_trident_tests(&root)?;
142144
let commander = Commander::new(&root);
143145

144146
commander.run(target, exit_code, seed).await?;
145147
}
146148
FuzzCommand::Debug { target, seed } => {
149+
ensure_running_from_trident_tests(&root)?;
147150
let commander = Commander::new(&root);
148151

149152
commander.run_debug(target, seed).await?;

crates/cli/src/command/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ fn check_trident_uninitialized(root: &str) {
139139
}
140140
}
141141

142+
#[throws]
143+
fn ensure_running_from_trident_tests(root: &str) {
144+
let trident_tests_dir = Path::new(root).join(TESTS_WORKSPACE_DIRECTORY);
145+
let current_dir = std::env::current_dir()?;
146+
147+
if !current_dir.starts_with(&trident_tests_dir) {
148+
bail!("Run this command from trident-tests.",);
149+
}
150+
}
151+
142152
#[throws]
143153
fn check_fuzz_test_exists(root: &str, fuzz_test_name: &str) {
144154
let fuzz_test_dir = Path::new(&root)

crates/client/src/commander/fuzz.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ impl Commander {
2020
exit_code_mode: Option<ExitCodeMode>,
2121
seed: Option<String>,
2222
) {
23-
let config = TridentConfig::new();
23+
let config = TridentConfig::try_new()?;
2424

2525
if config.get_metrics() {
2626
std::env::set_var("FUZZING_METRICS", "true");
@@ -184,7 +184,7 @@ impl Commander {
184184

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

189189
if config.get_metrics() {
190190
if config.get_metrics_json() {

crates/client/src/commander/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ mod fuzz;
1919

2020
#[derive(Error, Debug)]
2121
pub enum Error {
22-
#[error("{0:?}")]
22+
#[error("{0}")]
2323
Io(#[from] io::Error),
24-
#[error("{0:?}")]
24+
#[error("{0}")]
2525
Utf8(#[from] FromUtf8Error),
2626
#[error("build programs failed")]
2727
BuildProgramsFailed,
@@ -33,8 +33,10 @@ pub enum Error {
3333
Coverage(#[from] crate::coverage::CoverageError),
3434
#[error("Cannot find the trident-tests directory in the current workspace")]
3535
BadWorkspace,
36-
#[error("{0:?}")]
36+
#[error("{0}")]
3737
Anyhow(#[from] anyhow::Error),
38+
#[error("Invalid Trident.toml configuration: {0}")]
39+
Config(#[from] trident_config::Error),
3840
}
3941

4042
/// `Commander` allows you to start localnet, build programs,

crates/config/src/constants.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// tomls
22
pub(crate) const TRIDENT_TOML: &str = "Trident.toml";
3+
pub(crate) const TESTS_WORKSPACE_DIRECTORY: &str = "trident-tests";
34

45
// fuzz
56
pub(crate) const DEFAULT_LOOPCOUNT: u64 = 0;

crates/config/src/coverage.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::constants::DEFAULT_COVERAGE_SERVER_PORT;
55
use crate::constants::DEFAULT_LOOPCOUNT;
66

77
#[derive(Debug, Deserialize, Clone)]
8+
#[serde(deny_unknown_fields)]
89
pub struct Coverage {
910
pub enable: Option<bool>,
1011
pub server_port: Option<u16>,

crates/config/src/fuzz.rs

Lines changed: 177 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::coverage::Coverage;
22
use crate::metrics::Metrics;
33
use crate::regression::Regression;
44
use crate::utils::resolve_path;
5+
use crate::Error;
56
use base64::prelude::BASE64_STANDARD;
67
use base64::Engine;
78
use serde::Deserialize;
@@ -13,6 +14,7 @@ use std::fs;
1314
use std::str::FromStr;
1415

1516
#[derive(Debug, Deserialize, Clone, Default)]
17+
#[serde(deny_unknown_fields)]
1618
pub struct Fuzz {
1719
metrics: Option<Metrics>,
1820
regression: Option<Regression>,
@@ -55,28 +57,94 @@ impl Fuzz {
5557
self.coverage.clone().unwrap_or_default()
5658
}
5759

58-
pub fn get_forks(&self) -> Vec<FuzzFork> {
60+
pub fn get_forks(&self) -> Result<Vec<FuzzFork>, Error> {
5961
match self.fork.as_ref() {
60-
Some(forks) => forks.iter().map(FuzzFork::from).collect(),
61-
None => Vec::default(),
62+
Some(forks) => forks.iter().map(FuzzFork::try_from_raw).collect(),
63+
None => Ok(Vec::default()),
6264
}
6365
}
66+
67+
/// Lightweight preflight validation used by CLI startup checks.
68+
/// This validates addresses and file path existence without loading
69+
/// full program binaries/account payloads into memory.
70+
pub fn validate_preflight(&self) -> Result<(), Error> {
71+
if let Some(programs) = &self.programs {
72+
for program in programs {
73+
let _ = Pubkey::from_str(&program.address).map_err(|_| {
74+
Error::Anyhow(anyhow::anyhow!(
75+
"Cannot parse the program address: {}",
76+
program.address
77+
))
78+
})?;
79+
if let Some(authority) = program.upgrade_authority.as_ref() {
80+
let _ = Pubkey::from_str(authority).map_err(|_| {
81+
Error::Anyhow(anyhow::anyhow!(
82+
"Cannot parse upgrade authority: {}",
83+
authority
84+
))
85+
})?;
86+
}
87+
88+
let path = resolve_path(&program.program)?;
89+
if !path.exists() {
90+
return Err(Error::Anyhow(anyhow::anyhow!(
91+
"Failed to read file: {}",
92+
program.program
93+
)));
94+
}
95+
}
96+
}
97+
98+
if let Some(accounts) = &self.accounts {
99+
for account in accounts {
100+
let _ = Pubkey::from_str(&account.address).map_err(|_| {
101+
Error::Anyhow(anyhow::anyhow!(
102+
"Cannot parse account address: {}",
103+
account.address
104+
))
105+
})?;
106+
let path = resolve_path(&account.filename)?;
107+
if !path.exists() {
108+
return Err(Error::Anyhow(anyhow::anyhow!(
109+
"Failed to read file: {}",
110+
account.filename
111+
)));
112+
}
113+
}
114+
}
115+
116+
if let Some(forks) = &self.fork {
117+
for fork in forks {
118+
let _ = Pubkey::from_str(&fork.address).map_err(|_| {
119+
Error::Anyhow(anyhow::anyhow!(
120+
"Cannot parse fork address: {}",
121+
fork.address
122+
))
123+
})?;
124+
}
125+
}
126+
127+
Ok(())
128+
}
64129
}
65130

66131
#[derive(Debug, Deserialize, Clone)]
132+
#[serde(deny_unknown_fields)]
67133
pub struct _FuzzProgram {
68134
pub address: String,
69135
pub upgrade_authority: Option<String>,
70136
pub program: String,
71137
}
72138

73139
#[derive(Debug, Deserialize, Clone)]
140+
#[serde(deny_unknown_fields)]
74141
pub struct _FuzzAccount {
75142
pub address: String,
76143
pub filename: String,
77144
}
78145

79146
#[derive(Debug, Deserialize, Clone)]
147+
#[serde(deny_unknown_fields)]
80148
pub struct _FuzzFork {
81149
pub address: String,
82150
pub cluster: String,
@@ -142,6 +210,22 @@ impl From<&_FuzzFork> for FuzzFork {
142210
}
143211
}
144212
}
213+
impl FuzzFork {
214+
pub fn try_from_raw(value: &_FuzzFork) -> Result<Self, Error> {
215+
let address = Pubkey::from_str(&value.address).map_err(|_| {
216+
Error::Anyhow(anyhow::anyhow!(
217+
"Cannot parse fork address: {}",
218+
value.address
219+
))
220+
})?;
221+
222+
Ok(FuzzFork {
223+
address,
224+
cluster: FuzzCluster::parse(&value.cluster),
225+
overwrite: value.overwrite,
226+
})
227+
}
228+
}
145229

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

163-
let path = resolve_path(program_path);
247+
let path = resolve_path(program_path)
248+
.unwrap_or_else(|_| panic!("Failed to resolve path: {}", program_path));
164249

165250
let program_data =
166251
fs::read(path).unwrap_or_else(|_| panic!("Failed to read file: {}", program_path));
@@ -175,6 +260,37 @@ impl From<&_FuzzProgram> for FuzzProgram {
175260
}
176261
}
177262
}
263+
impl FuzzProgram {
264+
pub fn try_from_raw(value: &_FuzzProgram) -> Result<Self, Error> {
265+
let path = resolve_path(&value.program)?;
266+
let program_data = fs::read(path).map_err(|_| {
267+
Error::Anyhow(anyhow::anyhow!("Failed to read file: {}", value.program))
268+
})?;
269+
270+
let address = Pubkey::from_str(&value.address).map_err(|_| {
271+
Error::Anyhow(anyhow::anyhow!(
272+
"Cannot parse the program address: {}",
273+
value.address
274+
))
275+
})?;
276+
277+
let upgrade_authority = match value.upgrade_authority.as_ref() {
278+
Some(authority) => Some(Pubkey::from_str(authority).map_err(|_| {
279+
Error::Anyhow(anyhow::anyhow!(
280+
"Cannot parse upgrade authority: {}",
281+
authority
282+
))
283+
})?),
284+
None => None,
285+
};
286+
287+
Ok(FuzzProgram {
288+
address,
289+
upgrade_authority,
290+
data: program_data,
291+
})
292+
}
293+
}
178294

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

189-
let path = resolve_path(account_path);
305+
let path = resolve_path(account_path)
306+
.unwrap_or_else(|_| panic!("Failed to resolve path: {}", account_path));
190307

191308
let file_content = fs::read_to_string(path)
192309
.unwrap_or_else(|_| panic!("Failed to read file: {}", account_path));
@@ -224,14 +341,69 @@ impl From<&_FuzzAccount> for FuzzAccount {
224341
FuzzAccount { pubkey, account }
225342
}
226343
}
344+
impl FuzzAccount {
345+
pub fn try_from_raw(value: &_FuzzAccount) -> Result<Self, Error> {
346+
let path = resolve_path(&value.filename)?;
347+
let file_content = fs::read_to_string(path).map_err(|_| {
348+
Error::Anyhow(anyhow::anyhow!("Failed to read file: {}", value.filename))
349+
})?;
350+
351+
let account_raw: FuzzAccountRaw = serde_json::from_str(&file_content).map_err(|_| {
352+
Error::Anyhow(anyhow::anyhow!(
353+
"Failed to parse JSON from file: {}",
354+
value.filename
355+
))
356+
})?;
357+
358+
let pubkey = Pubkey::from_str(&account_raw.pubkey).map_err(|_| {
359+
Error::Anyhow(anyhow::anyhow!(
360+
"Cannot convert address for: {}",
361+
account_raw.pubkey
362+
))
363+
})?;
364+
365+
let owner_address = Pubkey::from_str(&account_raw.account.owner).map_err(|_| {
366+
Error::Anyhow(anyhow::anyhow!(
367+
"Cannot convert address for owner: {}",
368+
account_raw.account.owner
369+
))
370+
})?;
371+
372+
let data_base_64 = account_raw.account.data.first().ok_or_else(|| {
373+
Error::Anyhow(anyhow::anyhow!(
374+
"Cannot read base64 data for account: {}",
375+
account_raw.pubkey
376+
))
377+
})?;
378+
379+
let data = BASE64_STANDARD.decode(data_base_64).map_err(|_| {
380+
Error::Anyhow(anyhow::anyhow!(
381+
"Failed to decode base64 data of {}",
382+
value.filename
383+
))
384+
})?;
385+
386+
let account = AccountSharedData::create(
387+
account_raw.account.lamports,
388+
data,
389+
owner_address,
390+
account_raw.account.executable,
391+
account_raw.account.rent_epoch,
392+
);
393+
394+
Ok(FuzzAccount { pubkey, account })
395+
}
396+
}
227397

228398
#[derive(Debug, Deserialize, Clone)]
399+
#[serde(deny_unknown_fields)]
229400
pub struct FuzzAccountRaw {
230401
pub pubkey: String,
231402
pub account: AccountRaw,
232403
}
233404

234405
#[derive(Debug, Serialize, Deserialize, Clone)]
406+
#[serde(deny_unknown_fields)]
235407
pub struct AccountRaw {
236408
pub lamports: u64,
237409
pub data: Vec<String>,

0 commit comments

Comments
 (0)