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}