trident_fuzz/address_storage.rs
1use solana_sdk::pubkey::Pubkey;
2use solana_sdk::signer::Signer;
3
4use crate::trident::Trident;
5
6/// A storage container for managing and tracking public key addresses
7///
8/// `AddressStorage` provides a convenient way to store and retrieve addresses during fuzz testing.
9/// It can generate random addresses or derive PDAs, and allows you to randomly select from stored addresses.
10pub struct AddressStorage {
11 addresses: Vec<Pubkey>,
12}
13
14/// Seeds and program ID for deriving Program Derived Addresses (PDAs)
15///
16/// This structure holds the necessary information to derive a PDA using
17/// `Pubkey::try_find_program_address`.
18pub struct PdaSeeds<'a> {
19 pub seeds: &'a [&'a [u8]],
20 pub program_id: Pubkey,
21}
22
23impl<'a> PdaSeeds<'a> {
24 /// Creates a new `PdaSeeds` instance
25 ///
26 /// # Arguments
27 /// * `seeds` - The seeds to use for PDA derivation
28 /// * `program_id` - The program ID to use for PDA derivation
29 ///
30 /// # Returns
31 /// A new `PdaSeeds` instance
32 pub fn new(seeds: &'a [&'a [u8]], program_id: Pubkey) -> Self {
33 Self { seeds, program_id }
34 }
35}
36
37/// Derives a Program Derived Address (PDA) from seeds and program ID
38///
39/// # Arguments
40/// * `seeds` - The seeds to use for PDA derivation
41/// * `program_id` - The program ID to use for PDA derivation
42///
43/// # Returns
44/// The derived PDA if successful, or `None` if derivation fails
45fn derive_pda(seeds: &[&[u8]], program_id: &Pubkey) -> Option<Pubkey> {
46 if let Some((address, _)) = Pubkey::try_find_program_address(seeds, program_id) {
47 Some(address)
48 } else {
49 None
50 }
51}
52
53impl Default for AddressStorage {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl AddressStorage {
60 /// Creates a new empty `AddressStorage` instance
61 ///
62 /// # Returns
63 /// A new `AddressStorage` with no stored addresses
64 fn new() -> Self {
65 let addresses: Vec<Pubkey> = Vec::new();
66 Self { addresses }
67 }
68
69 /// Inserts a new address into storage
70 ///
71 /// Generates a new address (either a PDA or random keypair) and stores it.
72 /// If PDA seeds are provided, attempts to derive a PDA. If derivation fails
73 /// or no seeds are provided, generates a random keypair address.
74 ///
75 /// # Arguments
76 /// * `trident` - The Trident instance for random number generation
77 /// * `seeds` - Optional PDA seeds for deriving a program-derived address
78 ///
79 /// # Returns
80 /// The newly created and stored address
81 pub fn insert(&mut self, trident: &mut Trident, seeds: Option<PdaSeeds>) -> Pubkey {
82 let address = self.get_or_create_address(seeds, trident);
83 self.addresses.push(address);
84 address
85 }
86
87 /// Inserts an existing address into storage
88 ///
89 /// Stores a pre-existing address without generating a new one.
90 /// Useful when you need to track addresses created elsewhere.
91 ///
92 /// # Arguments
93 /// * `address` - The address to store
94 pub fn insert_with_address(&mut self, address: Pubkey) {
95 self.addresses.push(address);
96 }
97
98 /// Retrieves a random address from storage
99 ///
100 /// Randomly selects one of the stored addresses using Trident's RNG.
101 /// This is useful for fuzzing operations that need to work with previously
102 /// created accounts.
103 ///
104 /// # Arguments
105 /// * `trident` - The Trident instance for random number generation
106 ///
107 /// # Returns
108 /// * `Some(Pubkey)` - A randomly selected address from storage
109 /// * `None` - If the storage is empty
110 pub fn get(&self, trident: &mut Trident) -> Option<Pubkey> {
111 if self.is_empty() {
112 return None;
113 }
114 let accounts_num = self.addresses.len();
115 let account_id = trident.random_from_range(0..accounts_num);
116 Some(self.addresses[account_id])
117 }
118
119 /// Retrieves a random address from storage, excluding specified addresses
120 ///
121 /// Randomly selects one of the stored addresses using Trident's RNG, ensuring
122 /// the selected address is not in the exclusion list. This is useful for fuzzing
123 /// operations that need distinct accounts (e.g., sender and receiver must be different).
124 ///
125 /// # Arguments
126 /// * `trident` - The Trident instance for random number generation
127 /// * `except_addresses` - Slice of addresses to exclude from selection
128 ///
129 /// # Returns
130 /// * `Some(Pubkey)` - A randomly selected address that is not in the exclusion list
131 /// * `None` - If storage is empty or all addresses are in the exclusion list
132 ///
133 /// # Examples
134 /// ```ignore
135 /// let sender = storage.get(&mut trident)?;
136 /// // Get a different address for receiver
137 /// let receiver = storage.get_except(&mut trident, &[sender])?;
138 /// ```
139 pub fn get_except(&self, trident: &mut Trident, except_addresses: &[Pubkey]) -> Option<Pubkey> {
140 if self.is_empty() {
141 return None;
142 }
143
144 let accounts_num = self.addresses.len();
145
146 // If all addresses would be excluded, return None
147 if except_addresses.len() >= accounts_num {
148 let all_excluded = self
149 .addresses
150 .iter()
151 .all(|addr| except_addresses.contains(addr));
152 if all_excluded {
153 return None;
154 }
155 }
156
157 // Try to find a valid address by random sampling
158 // We try up to accounts_num times to find a non-excluded address
159 for _ in 0..accounts_num {
160 let account_id = trident.random_from_range(0..accounts_num);
161 let candidate = self.addresses[account_id];
162
163 if !except_addresses.contains(&candidate) {
164 return Some(candidate);
165 }
166 }
167
168 // Fallback: if random sampling failed, do a linear search
169 // This should rarely happen but ensures we return a valid address if one exists
170 self.addresses
171 .iter()
172 .find(|addr| !except_addresses.contains(addr))
173 .copied()
174 }
175
176 /// Checks if the storage is empty
177 ///
178 /// # Returns
179 /// `true` if no addresses are stored, `false` otherwise
180 pub fn is_empty(&self) -> bool {
181 self.addresses.is_empty()
182 }
183
184 /// Returns the number of stored addresses
185 ///
186 /// # Returns
187 /// The count of addresses currently in storage
188 pub fn len(&self) -> usize {
189 self.addresses.len()
190 }
191
192 /// Gets an existing address or creates a new one
193 ///
194 /// Internal helper method that either derives a PDA from the provided seeds
195 /// or generates a random keypair address.
196 ///
197 /// # Arguments
198 /// * `seeds` - Optional PDA seeds for deriving a program-derived address
199 /// * `trident` - The Trident instance for random number generation
200 ///
201 /// # Returns
202 /// A derived PDA if seeds are provided and derivation succeeds, otherwise a random address
203 fn get_or_create_address(&self, seeds: Option<PdaSeeds>, trident: &mut Trident) -> Pubkey {
204 match seeds {
205 Some(seeds) => {
206 if let Some(pubkey) = derive_pda(seeds.seeds, &seeds.program_id) {
207 pubkey
208 } else {
209 let mut secret = [0; 32];
210 trident.random_bytes(&mut secret);
211 solana_sdk::signer::keypair::Keypair::new_from_array(secret).pubkey()
212 }
213 }
214 None => {
215 let mut secret = [0; 32];
216 trident.random_bytes(&mut secret);
217 solana_sdk::signer::keypair::Keypair::new_from_array(secret).pubkey()
218 }
219 }
220 }
221}