trident_template/
fuzz_instructions_generator.rs1use std::collections::{hash_map::Entry, HashMap};
2
3use quote::{quote, ToTokens};
4
5use trident_idl_spec::{
6 Idl, IdlInstructionAccount, IdlInstructionAccountItem, IdlInstructionAccounts,
7};
8
9use super::{
10 get_fuzz_accounts, get_instruction_inputs, get_instruction_ixops, get_instruction_variants,
11 get_types,
12};
13use crate::instruction_account::{InstructionAccount, InstructionAccountType};
14
15pub fn generate_source_code(idls: &[Idl]) -> String {
17 let mut all_instructions: Vec<syn::Variant> = Vec::new();
19 let mut all_instruction_inputs: Vec<syn::ItemStruct> = Vec::new();
20 let mut all_instructions_ixops_impls: Vec<syn::ItemImpl> = Vec::new();
21 let mut all_fuzz_accounts: Vec<syn::FnArg> = Vec::new();
22 let mut all_types: Vec<syn::Item> = Vec::new();
23
24 for idl in idls {
26 let instruction_accounts = get_instructions_accounts(idl);
27 let program_accounts = get_program_accounts(idl);
28
29 all_instructions.extend(get_instruction_variants(idl));
30 all_instruction_inputs.extend(get_instruction_inputs(idl));
31 all_instructions_ixops_impls.extend(get_instruction_ixops(idl, &instruction_accounts));
32 all_fuzz_accounts.extend(get_fuzz_accounts(idl, &instruction_accounts));
33 all_types.extend(get_types(idl, program_accounts));
34 }
35
36 let module_definition = quote! {
38 use trident_fuzz::fuzzing::*;
39 use borsh::{BorshDeserialize, BorshSerialize};
40
41 #[derive(Arbitrary, DisplayIx, FuzzTestExecutor)]
44 pub enum FuzzInstruction {
45 #(#all_instructions),*
46 }
47
48 #(#all_instruction_inputs)*
49
50 #(#all_instructions_ixops_impls)*
51
52 #[derive(Default)]
55 pub struct FuzzAccounts {
56 #(#all_fuzz_accounts),*
57 }
58
59 #(#all_types)*
60 };
61
62 module_definition.into_token_stream().to_string()
64}
65
66fn get_instructions_accounts(idl: &Idl) -> HashMap<String, InstructionAccount> {
67 idl.instructions.iter().fold(
68 HashMap::<String, InstructionAccount>::new(),
69 |mut instruction_accounts, instruction| {
70 for account in &instruction.accounts {
71 match account {
72 IdlInstructionAccountItem::Composite(idl_instruction_accounts) => {
73 process_composite_account(
74 &instruction.name,
75 idl_instruction_accounts,
76 &mut instruction_accounts,
77 );
78 }
79 IdlInstructionAccountItem::Single(idl_instruction_account) => {
80 process_single_account(
81 &instruction.name,
82 idl_instruction_account,
83 &mut instruction_accounts,
84 );
85 }
86 }
87 }
88 instruction_accounts
89 },
90 )
91}
92
93fn process_composite_account(
94 instruction_name: &str,
95 idl_instruction_accounts: &IdlInstructionAccounts,
96 instruction_accounts: &mut HashMap<String, InstructionAccount>,
97) {
98 for account in &idl_instruction_accounts.accounts {
99 match account {
100 IdlInstructionAccountItem::Single(idl_instruction_account) => {
101 process_single_account(
102 instruction_name,
103 idl_instruction_account,
104 instruction_accounts,
105 );
106 }
107 IdlInstructionAccountItem::Composite(idl_instruction_accounts) => {
109 process_composite_account(
110 instruction_name,
111 idl_instruction_accounts,
112 instruction_accounts,
113 );
114 }
115 }
116 }
117}
118
119fn process_single_account(
120 instruction_name: &str,
121 idl_instruction_account: &IdlInstructionAccount,
122 instruction_accounts: &mut HashMap<String, InstructionAccount>,
123) {
124 let account_name = &idl_instruction_account.name;
125 let account_type = evaluate_account_type(idl_instruction_account);
126
127 match instruction_accounts.entry(account_name.to_string()) {
128 Entry::Vacant(entry) => {
129 let mut new_account = InstructionAccount::new(account_name.to_string());
131
132 new_account.insert(instruction_name.to_owned(), account_type);
134 entry.insert(new_account);
135 }
136 Entry::Occupied(mut entry) => {
137 let account = entry.get_mut();
139 account.insert(instruction_name.to_owned(), account_type);
140 }
141 };
142}
143
144#[allow(clippy::manual_map)]
145fn evaluate_account_type(
146 idl_instruction_account: &IdlInstructionAccount,
147) -> InstructionAccountType {
148 if let Some(address) = &idl_instruction_account.address {
150 InstructionAccountType::Constant(
151 address.to_string(),
152 idl_instruction_account.writable,
153 idl_instruction_account.signer,
154 )
155 } else if idl_instruction_account.signer {
157 InstructionAccountType::Keypair(
158 idl_instruction_account.writable,
159 idl_instruction_account.signer,
160 )
161 } else if let Some(idl_pda) = &idl_instruction_account.pda {
163 InstructionAccountType::Pda(
164 idl_pda.clone(),
165 idl_instruction_account.writable,
166 idl_instruction_account.signer,
167 )
168 } else {
172 InstructionAccountType::default()
173 }
174}
175
176fn get_program_accounts(idl: &Idl) -> HashMap<String, Vec<u8>> {
177 idl.accounts
180 .iter()
181 .fold(HashMap::new(), |mut program_accounts, account| {
182 program_accounts.insert(account.name.clone(), account.discriminator.clone());
183 program_accounts
184 })
185}