trident_derive_accounts_snapshots/
lib.rs

1use 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        // TODO make sure that these convertions between types are correct
22        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            // Verbously say that if the account is boxed we dont care.
55            #[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            // Verbously say that if the account is boxed we dont care.
132            #[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            // Verbously say that if the account is boxed we dont care.
205            #[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            // TODO It would be helpful to do something like line below.
300            // where we propagate anchor error
301            // However I suggest that this is not possible right now as for
302            // fuzz_example3 the anchor_lang has version 0.28.0. However trident
303            // uses 0.29.0 I think this is the reason why the '?' operator cannot propagate
304            // the error even though I implemnted From<anchor_lang::error::Error> trait
305            // that i
306            // .map_err(|e| e.with_account_name(#name_str).into())?;
307            .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}
359// TODO optional ?
360fn 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
396// TODO optional ?
397fn 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    // CONSTRUCT DESERIALIZE OPTION
623    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    // CONSTRUCT SNAPSHOT STRUCT
698    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    // CONSTRUCT RETURN VALUE
711    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    // CHECK IF STRUCT HAS ANY FIELDS
720    let has_fields = !accs.0.fields.is_empty();
721
722    // USE PHANTOMDATA IF NO FIELDS PRESENT
723    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    // IF IT HAS FIELDS JUST FOLLOW STANDARD BEHAVIOR
739    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        // IF IT HAS NO FIELDS RETURN THE PHANTOM DATA
758        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
784/// Determines if an Account should be wrapped into the `Option` type.
785/// The function returns true if the account has the init or close constraints set
786/// or if it is wrapped into the `Option` type.
787fn 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}