trident_derive_accounts_snapshots/
lib.rs1use anchor_syn::{AccountField, AccountTy};
2use convert_case::{Case, Casing};
3use proc_macro2::TokenStream;
4use quote::{quote, ToTokens};
5use syn::parse::{Parse, ParseStream};
6use syn::parse_macro_input;
7use syn::{Ident, ItemStruct, Result as ParseResult};
8
9#[proc_macro_derive(AccountsSnapshots)]
10pub fn derive_accounts_snapshots(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
11 parse_macro_input!(item as TridentAccountsStruct)
12 .to_token_stream()
13 .into()
14}
15
16struct TridentAccountsStruct(anchor_syn::AccountsStruct);
17
18impl Parse for TridentAccountsStruct {
19 fn parse(input: ParseStream) -> ParseResult<Self> {
20 let strct = <ItemStruct as Parse>::parse(input)?;
21 Ok(TridentAccountsStruct(anchor_syn::parser::accounts::parse(
23 &strct,
24 )?))
25 }
26}
27
28fn snapshot_field(field: &anchor_syn::Field, is_optional: bool) -> proc_macro2::TokenStream {
29 let account_ty = field.account_ty();
30 let container_ty = field.container_ty();
31
32 let inner_ty = match &field.ty {
33 anchor_syn::Ty::AccountInfo => {
34 quote! {
35 &'info AccountInfo<'info>
36 }
37 }
38 anchor_syn::Ty::UncheckedAccount => {
39 quote! {
40 UncheckedAccount<'info>
41 }
42 }
43 anchor_syn::Ty::AccountLoader(_) => {
44 quote! {
45 #container_ty<'info,#account_ty>
46 }
47 }
48 anchor_syn::Ty::Sysvar(_) => {
49 quote! {
50 Sysvar<'info,#account_ty>
51 }
52 }
53 anchor_syn::Ty::Account(AccountTy { boxed, .. }) => {
54 #[allow(clippy::if_same_then_else)]
56 if *boxed {
57 quote! {
58 #container_ty<'info,#account_ty>
59 }
60 } else {
61 quote! {
62 #container_ty<'info,#account_ty>
63 }
64 }
65 }
66 anchor_syn::Ty::Program(_) => {
67 quote! {
68 #container_ty<'info,#account_ty>
69 }
70 }
71 anchor_syn::Ty::Interface(_) => {
72 quote! {
73 #container_ty<'info,#account_ty>
74 }
75 }
76 anchor_syn::Ty::InterfaceAccount(_) => {
77 quote! {
78 #container_ty<'info,#account_ty>
79 }
80 }
81 anchor_syn::Ty::Signer => {
82 quote! {
83 Signer<'info>
84 }
85 }
86 anchor_syn::Ty::SystemAccount => {
87 quote! {
88 SystemAccount<'info>
89 }
90 }
91 anchor_syn::Ty::ProgramData => {
92 todo!()
93 }
94 };
95 let f_name = &field.ident;
96
97 if is_optional {
98 quote! {
99 #f_name:Option<#inner_ty>
100 }
101 } else {
102 quote! {
103 #f_name:#inner_ty
104 }
105 }
106}
107fn type_decl_try_from(field: &anchor_syn::Field) -> proc_macro2::TokenStream {
108 let _account_ty = field.account_ty();
109 let _container_ty = field.container_ty();
110
111 let inner_ty = match &field.ty {
112 anchor_syn::Ty::AccountInfo => {
113 quote! {}
114 }
115 anchor_syn::Ty::UncheckedAccount => {
116 quote! {
117 anchor_lang::accounts::unchecked_account::UncheckedAccount
118 }
119 }
120 anchor_syn::Ty::AccountLoader(_) => {
121 quote! {
122 anchor_lang::accounts::account_loader::AccountLoader
123 }
124 }
125 anchor_syn::Ty::Sysvar(_) => {
126 quote! {
127 anchor_lang::accounts::sysvar::Sysvar
128 }
129 }
130 anchor_syn::Ty::Account(AccountTy { boxed, .. }) => {
131 #[allow(clippy::if_same_then_else)]
133 if *boxed {
134 quote! {
135 anchor_lang::accounts::account::Account
136 }
137 } else {
138 quote! {
139 anchor_lang::accounts::account::Account
140 }
141 }
142 }
143 anchor_syn::Ty::Program(_) => {
144 quote! {
145 anchor_lang::accounts::program::Program
146 }
147 }
148 anchor_syn::Ty::Interface(_) => {
149 quote! {
150 anchor_lang::accounts::interface::Interface
151 }
152 }
153 anchor_syn::Ty::InterfaceAccount(_) => {
154 quote! {
155 anchor_lang::accounts::interface_account::InterfaceAccount
156 }
157 }
158 anchor_syn::Ty::Signer => {
159 quote! {
160 anchor_lang::accounts::signer::Signer
161 }
162 }
163 anchor_syn::Ty::SystemAccount => {
164 quote! {
165 anchor_lang::accounts::system_account::SystemAccount
166 }
167 }
168 anchor_syn::Ty::ProgramData => {
169 quote! {}
170 }
171 };
172 quote! {
173 #inner_ty
174 }
175}
176
177fn type_decl_deserialize(field: &anchor_syn::Field, is_optional: bool) -> proc_macro2::TokenStream {
178 let name = &field.ident;
179 let account_ty = field.account_ty();
180 let container_ty = field.container_ty();
181
182 let ty_decl = match &field.ty {
183 anchor_syn::Ty::AccountInfo => {
184 quote! {
185 AccountInfo
186 }
187 }
188 anchor_syn::Ty::UncheckedAccount => {
189 quote! {
190 UncheckedAccount
191 }
192 }
193 anchor_syn::Ty::AccountLoader(_) => {
194 quote! {
195 anchor_lang::accounts::account_loader::AccountLoader<#account_ty>
196 }
197 }
198 anchor_syn::Ty::Sysvar(_) => {
199 quote! {
200 Sysvar<#account_ty>
201 }
202 }
203 anchor_syn::Ty::Account(AccountTy { boxed, .. }) => {
204 #[allow(clippy::if_same_then_else)]
206 if *boxed {
207 quote! {
208 #container_ty<#account_ty>
209 }
210 } else {
211 quote! {
212 #container_ty<#account_ty>
213 }
214 }
215 }
216 anchor_syn::Ty::Program(_) => {
217 quote! {
218 #container_ty<#account_ty>
219 }
220 }
221 anchor_syn::Ty::Interface(_) => {
222 quote! {
223 anchor_lang::accounts::interface::Interface<#account_ty>
224 }
225 }
226 anchor_syn::Ty::InterfaceAccount(_) => {
227 quote! {
228 anchor_lang::accounts::interface_account::InterfaceAccount<#account_ty>
229 }
230 }
231 anchor_syn::Ty::Signer => {
232 quote! {
233 Signer
234 }
235 }
236 anchor_syn::Ty::SystemAccount => {
237 quote! {
238 SystemAccount
239 }
240 }
241 anchor_syn::Ty::ProgramData => {
242 quote! {}
243 }
244 };
245 if is_optional {
246 quote! {
247 #name: Option<#ty_decl>
248 }
249 } else {
250 quote! {
251 #name: #ty_decl
252 }
253 }
254}
255
256impl From<&TridentAccountsStruct> for TokenStream {
257 fn from(accounts: &TridentAccountsStruct) -> Self {
258 generate(accounts)
259 }
260}
261
262impl ToTokens for TridentAccountsStruct {
263 fn to_tokens(&self, tokens: &mut TokenStream) {
264 tokens.extend::<TokenStream>(self.into());
265 }
266}
267
268fn deserialize_option_account(
269 typed_name: TokenStream,
270 ty_decl: TokenStream,
271 f_name_as_string: String,
272 is_optional: bool,
273) -> proc_macro2::TokenStream {
274 if is_optional {
275 quote! {
276 let #typed_name = accounts_iter
277 .next()
278 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
279 .as_ref()
280 .map(|acc| {
281 if acc.key() != *_program_id {
282 #ty_decl::try_from(acc).map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))
283 } else {Err(trident_fuzz::error::FuzzingError::OptionalAccountNotProvided(
284 #f_name_as_string.to_string(),
285 ))
286 }
287 })
288 .transpose()
289 .unwrap_or(None);
290 }
291 } else {
292 quote! {
293 let #typed_name = accounts_iter
294 .next()
295 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
296 .as_ref()
297 .map(#ty_decl::try_from)
298 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?
299 .map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))?;
308 }
309 }
310}
311
312fn deserialize_option_account_info(
313 f_name: &Ident,
314 f_name_as_string: String,
315 is_optional: bool,
316) -> proc_macro2::TokenStream {
317 if is_optional {
318 quote! {
319 let #f_name = accounts_iter
320 .next()
321 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
322 .as_ref();
323 }
324 } else {
325 quote! {
326 let #f_name = accounts_iter
327 .next()
328 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
329 .as_ref()
330 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?;
331 }
332 }
333}
334fn deserialize_option_unchecked_account(
335 f_name: &Ident,
336 f_name_as_string: String,
337 ty_decl: TokenStream,
338 is_optional: bool,
339) -> proc_macro2::TokenStream {
340 if is_optional {
341 quote! {
342 let #f_name = accounts_iter
343 .next()
344 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
345 .as_ref()
346 .map(#ty_decl::try_from);
347 }
348 } else {
349 quote! {
350 let #f_name = accounts_iter
351 .next()
352 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
353 .as_ref()
354 .map(#ty_decl::try_from)
355 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?;
356 }
357 }
358}
359fn deserialize_option_program(
361 typed_name: TokenStream,
362 ty_decl: TokenStream,
363 f_name_as_string: String,
364 is_optional: bool,
365) -> proc_macro2::TokenStream {
366 if is_optional {
367 quote! {
368 let #typed_name = accounts_iter
369 .next()
370 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
371 .as_ref()
372 .map(|acc| {
373 if acc.key() != *_program_id {
374 #ty_decl::try_from(acc).map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))
375 } else {Err(trident_fuzz::error::FuzzingError::OptionalAccountNotProvided(
376 #f_name_as_string.to_string(),
377 ))
378 }
379 })
380 .transpose()
381 .unwrap_or(None);
382 }
383 } else {
384 quote! {
385 let #typed_name = accounts_iter
386 .next()
387 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
388 .as_ref()
389 .map(#ty_decl::try_from)
390 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?
391 .map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))?;
392 }
393 }
394}
395
396fn deserialize_option_signer(
398 typed_name: TokenStream,
399 ty_decl: TokenStream,
400 f_name_as_string: String,
401 is_optional: bool,
402) -> proc_macro2::TokenStream {
403 if is_optional {
404 quote! {
405 let #typed_name = accounts_iter
406 .next()
407 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
408 .as_ref()
409 .map(|acc| {
410 if acc.key() != *_program_id {
411 #ty_decl::try_from(acc).map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))
412 } else {Err(trident_fuzz::error::FuzzingError::OptionalAccountNotProvided(
413 #f_name_as_string.to_string(),
414 ))
415 }
416 })
417 .transpose()
418 .unwrap_or(None);
419 }
420 } else {
421 quote! {
422 let #typed_name = accounts_iter
423 .next()
424 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
425 .as_ref()
426 .map(#ty_decl::try_from)
427 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?
428 .map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))?;
429 }
430 }
431}
432
433fn deserialize_option_sysvar(
434 typed_name: TokenStream,
435 ty_decl: TokenStream,
436 f_name_as_string: String,
437 is_optional: bool,
438) -> proc_macro2::TokenStream {
439 if is_optional {
440 quote! {
441 let #typed_name = accounts_iter
442 .next()
443 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
444 .as_ref()
445 .map(|acc| {
446 if acc.key() != *_program_id {
447 #ty_decl::from_account_info(acc).map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))
448 } else {Err(trident_fuzz::error::FuzzingError::OptionalAccountNotProvided(
449 #f_name_as_string.to_string(),
450 ))
451 }
452 })
453 .transpose()
454 .unwrap_or(None);
455 }
456 } else {
457 quote! {
458 let #typed_name = accounts_iter
459 .next()
460 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
461 .as_ref()
462 .map(#ty_decl::from_account_info)
463 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?
464 .map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))?;
465 }
466 }
467}
468
469fn deserialize_option_interface(
470 typed_name: TokenStream,
471 ty_decl: TokenStream,
472 f_name_as_string: String,
473 is_optional: bool,
474) -> proc_macro2::TokenStream {
475 if is_optional {
476 quote! {
477 let #typed_name = accounts_iter
478 .next()
479 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
480 .as_ref()
481 .map(|acc| {
482 if acc.key() != *_program_id {
483 #ty_decl::try_from(acc).map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))
484 } else {Err(trident_fuzz::error::FuzzingError::OptionalAccountNotProvided(
485 #f_name_as_string.to_string(),
486 ))
487 }
488 })
489 .transpose()
490 .unwrap_or(None);
491 }
492 } else {
493 quote! {
494 let #typed_name = accounts_iter
495 .next()
496 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
497 .as_ref()
498 .map(#ty_decl::try_from)
499 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?
500 .map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))?;
501 }
502 }
503}
504
505fn deserialize_option_interface_account(
506 typed_name: TokenStream,
507 ty_decl: TokenStream,
508 f_name_as_string: String,
509 is_optional: bool,
510) -> proc_macro2::TokenStream {
511 if is_optional {
512 quote! {
513 let #typed_name = accounts_iter
514 .next()
515 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
516 .as_ref()
517 .map(|acc| {
518 if acc.key() != *_program_id {
519 #ty_decl::try_from(acc).map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))
520 } else {Err(trident_fuzz::error::FuzzingError::OptionalAccountNotProvided(
521 #f_name_as_string.to_string(),
522 ))
523 }
524 })
525 .transpose()
526 .unwrap_or(None);
527 }
528 } else {
529 quote! {
530 let #typed_name = accounts_iter
531 .next()
532 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
533 .as_ref()
534 .map(#ty_decl::try_from)
535 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?
536 .map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))?;
537 }
538 }
539}
540
541fn deserialize_option_system_account(
542 typed_name: TokenStream,
543 ty_decl: TokenStream,
544 f_name_as_string: String,
545 is_optional: bool,
546) -> proc_macro2::TokenStream {
547 if is_optional {
548 quote! {
549 let #typed_name = accounts_iter
550 .next()
551 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
552 .as_ref()
553 .map(|acc| {
554 if acc.key() != *_program_id {
555 #ty_decl::try_from(acc).map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))
556 } else {Err(trident_fuzz::error::FuzzingError::OptionalAccountNotProvided(
557 #f_name_as_string.to_string(),
558 ))
559 }
560 })
561 .transpose()
562 .unwrap_or(None);
563 }
564 } else {
565 quote! {
566 let #typed_name = accounts_iter
567 .next()
568 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
569 .as_ref()
570 .map(#ty_decl::try_from)
571 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?
572 .map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))?;
573 }
574 }
575}
576
577fn deserialize_option_account_loader(
578 typed_name: TokenStream,
579 ty_decl: TokenStream,
580 f_name_as_string: String,
581 is_optional: bool,
582) -> proc_macro2::TokenStream {
583 if is_optional {
584 quote! {
585 let #typed_name = accounts_iter
586 .next()
587 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
588 .as_ref()
589 .map(|acc| {
590 if acc.key() != *_program_id {
591 #ty_decl::try_from(acc).map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))
592 } else {Err(trident_fuzz::error::FuzzingError::OptionalAccountNotProvided(
593 #f_name_as_string.to_string(),
594 ))
595 }
596 })
597 .transpose()
598 .unwrap_or(None);
599 }
600 } else {
601 quote! {
602 let #typed_name = accounts_iter
603 .next()
604 .ok_or(trident_fuzz::error::FuzzingError::NotEnoughAccounts(#f_name_as_string.to_string()))?
605 .as_ref()
606 .map(#ty_decl::try_from)
607 .ok_or(trident_fuzz::error::FuzzingError::AccountNotFound(#f_name_as_string.to_string()))?
608 .map_err(|_| trident_fuzz::error::FuzzingError::CannotDeserializeAccount(#f_name_as_string.to_string()))?;
609 }
610 }
611}
612
613fn generate(accs: &TridentAccountsStruct) -> proc_macro2::TokenStream {
614 let context_name = &accs.0.ident;
615 let snapshot_name = syn::Ident::new(&format!("{}Alias", context_name), context_name.span());
616 let context_name_snake_case = context_name.to_string().to_case(Case::Snake);
617 let module_name = syn::Ident::new(
618 &format!("trident_fuzz_{}_snapshot", context_name_snake_case),
619 context_name.span(),
620 );
621
622 let deserialize_fields = accs.0.fields.iter().map(|field| {
624 let is_optional = is_optional(field);
625 match &field {
626 anchor_syn::AccountField::Field(field) => {
627 let f_name = &field.ident;
628 let f_name_as_string = f_name.to_string();
629 let typed_name = type_decl_deserialize(field, is_optional);
630 let ty_decl = type_decl_try_from(field);
631
632 match field.ty {
633 anchor_syn::Ty::AccountInfo => {
634 deserialize_option_account_info(f_name, f_name_as_string, is_optional)
635 }
636 anchor_syn::Ty::UncheckedAccount => deserialize_option_unchecked_account(
637 f_name,
638 f_name_as_string,
639 ty_decl,
640 is_optional,
641 ),
642 anchor_syn::Ty::AccountLoader(_) => deserialize_option_account_loader(
643 typed_name,
644 ty_decl,
645 f_name_as_string,
646 is_optional,
647 ),
648 anchor_syn::Ty::Sysvar(_) => deserialize_option_sysvar(
649 typed_name,
650 ty_decl,
651 f_name_as_string,
652 is_optional,
653 ),
654 anchor_syn::Ty::Account(_) => deserialize_option_account(
655 typed_name,
656 ty_decl,
657 f_name_as_string,
658 is_optional,
659 ),
660 anchor_syn::Ty::Program(_) => deserialize_option_program(
661 typed_name,
662 ty_decl,
663 f_name_as_string,
664 is_optional,
665 ),
666 anchor_syn::Ty::Interface(_) => deserialize_option_interface(
667 typed_name,
668 ty_decl,
669 f_name_as_string,
670 is_optional,
671 ),
672 anchor_syn::Ty::InterfaceAccount(_) => deserialize_option_interface_account(
673 typed_name,
674 ty_decl,
675 f_name_as_string,
676 is_optional,
677 ),
678 anchor_syn::Ty::Signer => deserialize_option_signer(
679 typed_name,
680 ty_decl,
681 f_name_as_string,
682 is_optional,
683 ),
684 anchor_syn::Ty::SystemAccount => deserialize_option_system_account(
685 typed_name,
686 ty_decl,
687 f_name_as_string,
688 is_optional,
689 ),
690 anchor_syn::Ty::ProgramData => todo!(),
691 }
692 }
693 anchor_syn::AccountField::CompositeField(_) => todo!(),
694 }
695 });
696
697 let snapshot_fields = accs.0.fields.iter().map(|field| {
699 let is_optional = is_optional(field);
700
701 let snapshot_field = match &field {
702 anchor_syn::AccountField::Field(field) => snapshot_field(field, is_optional),
703 anchor_syn::AccountField::CompositeField(_composite) => todo!(),
704 };
705 quote! {
706 pub #snapshot_field,
707 }
708 });
709
710 let struct_fields = accs.0.fields.iter().map(|field| {
712 let field_name = match &field {
713 anchor_syn::AccountField::Field(field) => field.ident.to_owned(),
714 anchor_syn::AccountField::CompositeField(_composite) => todo!(),
715 };
716 quote! { #field_name }
717 });
718
719 let has_fields = !accs.0.fields.is_empty();
721
722 let struct_definition = if has_fields {
724 quote! {
725 pub struct #snapshot_name<'info> {
726 #(#snapshot_fields)*
727 }
728 }
729 } else {
730 quote! {
731 pub struct #snapshot_name<'info> {
732 #[allow(dead_code)]
733 _phantom: std::marker::PhantomData<&'info ()>,
734 }
735 }
736 };
737
738 let deserialize_impl = if has_fields {
740 quote! {
741 impl<'info> trident_fuzz::fuzz_deserialize::FuzzDeserialize<'info> for #snapshot_name<'info> {
742 fn deserialize_option(
743 _program_id: &anchor_lang::prelude::Pubkey,
744 accounts: &mut &'info [Option<AccountInfo<'info>>],
745 ) -> core::result::Result<Self, trident_fuzz::error::FuzzingError> {
746 let mut accounts_iter = accounts.iter();
747
748 #(#deserialize_fields)*
749
750 Ok(Self {
751 #(#struct_fields),*
752 })
753 }
754 }
755 }
756 } else {
757 quote! {
759 impl<'info> trident_fuzz::fuzz_deserialize::FuzzDeserialize<'info> for #snapshot_name<'info> {
760 fn deserialize_option(
761 _program_id: &anchor_lang::prelude::Pubkey,
762 _accounts: &mut &'info [Option<AccountInfo<'info>>],
763 ) -> core::result::Result<Self, trident_fuzz::error::FuzzingError> {
764 Ok(Self { _phantom: std::marker::PhantomData })
765 }
766 }
767 }
768 };
769
770 quote! {
771 #[cfg(feature = "trident-fuzzing")]
772 pub mod #module_name {
773 #[cfg(target_os = "solana")]
774 compile_error!("Do not use fuzzing with Production Code");
775 use super::*;
776
777 #deserialize_impl
778
779 #struct_definition
780 }
781 }
782}
783
784fn is_optional(parsed_field: &AccountField) -> bool {
788 let is_optional = match parsed_field {
789 AccountField::Field(field) => field.is_optional,
790 AccountField::CompositeField(_) => false,
791 };
792 let constraints = match parsed_field {
793 AccountField::Field(f) => &f.constraints,
794 AccountField::CompositeField(f) => &f.constraints,
795 };
796
797 constraints.init.is_some() || constraints.is_close() || is_optional || constraints.is_zeroed()
798}