@@ -2,6 +2,7 @@ use crate::coverage::Coverage;
22use crate :: metrics:: Metrics ;
33use crate :: regression:: Regression ;
44use crate :: utils:: resolve_path;
5+ use crate :: Error ;
56use base64:: prelude:: BASE64_STANDARD ;
67use base64:: Engine ;
78use serde:: Deserialize ;
@@ -13,6 +14,7 @@ use std::fs;
1314use std:: str:: FromStr ;
1415
1516#[ derive( Debug , Deserialize , Clone , Default ) ]
17+ #[ serde( deny_unknown_fields) ]
1618pub 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) ]
67133pub 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) ]
74141pub struct _FuzzAccount {
75142 pub address : String ,
76143 pub filename : String ,
77144}
78145
79146#[ derive( Debug , Deserialize , Clone ) ]
147+ #[ serde( deny_unknown_fields) ]
80148pub 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 ) ]
147231pub 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 ) ]
180296pub 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) ]
229400pub struct FuzzAccountRaw {
230401 pub pubkey : String ,
231402 pub account : AccountRaw ,
232403}
233404
234405#[ derive( Debug , Serialize , Deserialize , Clone ) ]
406+ #[ serde( deny_unknown_fields) ]
235407pub struct AccountRaw {
236408 pub lamports : u64 ,
237409 pub data : Vec < String > ,
0 commit comments