solana_address_book/
pda_seeds.rs

1//! # PDA Seeds Management
2//!
3//! Utilities for working with Program Derived Addresses (PDAs) and their seeds.
4//!
5//! This module provides types and functions for creating, managing, and debugging
6//! PDAs in Solana programs. It includes a flexible seed system that can handle
7//! various data types and provides human-readable representations for debugging.
8//!
9//! ## Features
10//!
11//! - **Flexible Seed Types**: Support for strings, pubkeys, and raw bytes as seeds
12//! - **PDA Derivation**: Helper functions for finding PDAs with bumps
13//! - **Debug Support**: Human-readable seed representations for logging
14//! - **Verification**: Methods to verify PDA derivation correctness
15
16use anchor_lang::prelude::*;
17
18/// Result of PDA derivation containing all relevant information
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct DerivedPda {
21    /// The derived public key
22    pub key: Pubkey,
23    /// The bump seed used to derive the PDA
24    pub bump: u8,
25    /// String representations of seeds for debugging
26    pub seed_strings: Vec<String>,
27    /// Raw seed bytes used for derivation
28    pub seeds: Vec<Vec<u8>>,
29}
30
31impl DerivedPda {
32    /// Verify that this PDA matches what find_program_address would return
33    pub fn verify(&self, program_id: &Pubkey) -> bool {
34        let seed_refs: Vec<&[u8]> = self.seeds.iter().map(|s| s.as_slice()).collect();
35        let (expected_key, expected_bump) = Pubkey::find_program_address(&seed_refs, program_id);
36        self.key == expected_key && self.bump == expected_bump
37    }
38}
39
40/// Convert a seed to a string representation for debugging
41pub fn seed_to_string<T: AsRef<[u8]>>(seed: T) -> String {
42    // Try to convert common types to readable strings
43    let bytes = seed.as_ref();
44
45    // Check if it's likely a string (all printable ASCII)
46    if bytes.iter().all(|&b| b.is_ascii_graphic() || b == b' ')
47        && let Ok(s) = std::str::from_utf8(bytes)
48    {
49        return s.to_string();
50    }
51
52    // Check if it's a pubkey (32 bytes)
53    if bytes.len() == 32
54        && let Ok(pubkey) = Pubkey::try_from(bytes)
55    {
56        return pubkey.to_string();
57    }
58
59    // Default to hex encoding for other byte arrays
60    hex::encode(bytes)
61}
62
63/// Find a PDA with bump and return along with seed strings for display
64///
65/// This function is similar to `find_pda_with_bump` but also returns
66/// string representations of the seeds for debugging/display purposes.
67///
68/// # Arguments
69/// * `seeds` - Array of items that can be referenced as byte slices
70/// * `program_id` - The program ID to derive the address for
71///
72/// # Returns
73/// * `DerivedPda` - Struct containing the derived public key, bump seed, seed strings, and raw seeds
74pub fn find_pda_with_bump_and_strings(seeds: &[&[u8]], program_id: &Pubkey) -> DerivedPda {
75    // Convert seeds to byte slices for PDA calculation
76    let seed_bytes: Vec<&[u8]> = seeds.iter().map(|s| s.as_ref()).collect();
77
78    // Find the PDA and bump
79    let (pubkey, bump) = Pubkey::find_program_address(&seed_bytes, program_id);
80
81    // Convert seeds to strings for display
82    let seed_strings: Vec<String> = seeds.iter().map(seed_to_string).collect();
83
84    // Store owned copies of the seed bytes
85    let seeds_owned: Vec<Vec<u8>> = seeds.iter().map(|s| s.as_ref().to_vec()).collect();
86
87    DerivedPda {
88        key: pubkey,
89        bump,
90        seed_strings,
91        seeds: seeds_owned,
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_string_seed() {
101        let program_id = Pubkey::new_unique();
102        let seed = "test_seed";
103
104        let (pda, bump) = Pubkey::find_program_address(&[seed.as_bytes()], &program_id);
105
106        // Verify the PDA is deterministic
107        let (pda2, bump2) = Pubkey::find_program_address(&[seed.as_bytes()], &program_id);
108        assert_eq!(pda, pda2);
109        assert_eq!(bump, bump2);
110
111        // Verify the PDA is on curve
112        assert!(!pda.is_on_curve());
113    }
114
115    #[test]
116    fn test_multiple_string_seeds() {
117        let program_id = Pubkey::new_unique();
118        let seed1 = "user";
119        let seed2 = "profile";
120        let seeds: Vec<&[u8]> = vec![seed1.as_bytes(), seed2.as_bytes()];
121
122        let (pda, _bump) = Pubkey::find_program_address(&seeds, &program_id);
123
124        // Verify different seed order produces different PDA
125        let seeds_reversed: Vec<&[u8]> = vec![seed2.as_bytes(), seed1.as_bytes()];
126        let (pda_reversed, _) = Pubkey::find_program_address(&seeds_reversed, &program_id);
127        assert_ne!(pda, pda_reversed);
128    }
129
130    #[test]
131    fn test_pubkey_seed() {
132        let program_id = Pubkey::new_unique();
133        let user_pubkey = Pubkey::new_unique();
134        let seeds: Vec<&[u8]> = vec![user_pubkey.as_ref()];
135
136        let (pda, bump) = Pubkey::find_program_address(&seeds, &program_id);
137
138        // Verify the PDA is deterministic with pubkey seed
139        let (pda2, bump2) = Pubkey::find_program_address(&seeds, &program_id);
140        assert_eq!(pda, pda2);
141        assert_eq!(bump, bump2);
142    }
143
144    #[test]
145    fn test_mixed_seeds() {
146        let program_id = Pubkey::new_unique();
147        let prefix = "vault";
148        let owner = Pubkey::new_unique();
149        let id: u64 = 12345;
150        let id_bytes = id.to_le_bytes();
151
152        let seeds: Vec<&[u8]> = vec![prefix.as_bytes(), owner.as_ref(), &id_bytes];
153
154        let (pda, _bump) = Pubkey::find_program_address(&seeds, &program_id);
155        assert!(!pda.is_on_curve());
156    }
157
158    #[test]
159    fn test_byte_array_seed() {
160        let program_id = Pubkey::new_unique();
161        let bytes: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
162        let seeds: Vec<&[u8]> = vec![&bytes];
163
164        let (pda, _bump) = Pubkey::find_program_address(&seeds, &program_id);
165        assert!(!pda.is_on_curve());
166    }
167
168    #[test]
169    fn test_slice_seed() {
170        let program_id = Pubkey::new_unique();
171        let vec_bytes = vec![9, 8, 7, 6, 5, 4, 3, 2, 1];
172        let seeds: Vec<&[u8]> = vec![&vec_bytes];
173
174        let (pda, _bump) = Pubkey::find_program_address(&seeds, &program_id);
175        assert!(!pda.is_on_curve());
176    }
177
178    #[test]
179    fn test_find_pda_with_strings() {
180        let program_id = Pubkey::new_unique();
181        let seed1 = "metadata";
182        let seed2 = Pubkey::new_unique();
183        let seed3: u32 = 42;
184        let seed3_bytes = seed3.to_le_bytes();
185
186        let seeds: Vec<&[u8]> = vec![seed1.as_bytes(), seed2.as_ref(), &seed3_bytes];
187
188        let derived_pda = find_pda_with_bump_and_strings(&seeds, &program_id);
189
190        // Verify we got the right number of string representations
191        assert_eq!(derived_pda.seed_strings.len(), 3);
192        assert_eq!(derived_pda.seed_strings[0], "metadata");
193        assert_eq!(derived_pda.seed_strings[1], seed2.to_string());
194
195        // Verify the PDA matches what we'd get from the basic function
196        let (pda2, bump2) = Pubkey::find_program_address(&seeds, &program_id);
197        assert_eq!(derived_pda.key, pda2);
198        assert_eq!(derived_pda.bump, bump2);
199
200        // Verify the stored seeds are correct
201        assert_eq!(derived_pda.seeds.len(), 3);
202        assert_eq!(derived_pda.seeds[0], seed1.as_bytes());
203        assert_eq!(derived_pda.seeds[1], seed2.as_ref());
204        assert_eq!(derived_pda.seeds[2], seed3_bytes.as_ref());
205
206        // Verify that find_program_address returns the same result
207        let seed_refs: Vec<&[u8]> = derived_pda.seeds.iter().map(|s| s.as_slice()).collect();
208        let (expected_key, expected_bump) = Pubkey::find_program_address(&seed_refs, &program_id);
209        assert_eq!(derived_pda.key, expected_key);
210        assert_eq!(derived_pda.bump, expected_bump);
211
212        // Verify the verify method works
213        assert!(derived_pda.verify(&program_id));
214    }
215
216    #[test]
217    fn test_empty_seeds() {
218        let program_id = Pubkey::new_unique();
219        let seeds: Vec<&[u8]> = vec![];
220
221        let (pda, _bump) = Pubkey::find_program_address(&seeds, &program_id);
222
223        // Empty seeds should still produce a valid PDA
224        assert!(!pda.is_on_curve());
225    }
226
227    #[test]
228    fn test_max_seed_length() {
229        let program_id = Pubkey::new_unique();
230        // Max seed length is 32 bytes per seed
231        let max_seed = vec![0u8; 32];
232        let seeds: Vec<&[u8]> = vec![&max_seed];
233
234        let (pda, _bump) = Pubkey::find_program_address(&seeds, &program_id);
235        assert!(!pda.is_on_curve());
236    }
237
238    #[test]
239    fn test_different_programs_same_seeds() {
240        let program_id1 = Pubkey::new_unique();
241        let program_id2 = Pubkey::new_unique();
242        let seed = "same_seed";
243        let seeds: Vec<&[u8]> = vec![seed.as_bytes()];
244
245        let (pda1, _) = Pubkey::find_program_address(&seeds, &program_id1);
246        let (pda2, _) = Pubkey::find_program_address(&seeds, &program_id2);
247
248        // Same seeds with different programs should produce different PDAs
249        assert_ne!(pda1, pda2);
250    }
251
252    #[test]
253    fn test_bump_determinism() {
254        let program_id = Pubkey::new_unique();
255        let seed = "deterministic";
256        let seeds: Vec<&[u8]> = vec![seed.as_bytes()];
257
258        // Run multiple times to ensure determinism
259        let results: Vec<(Pubkey, u8)> = (0..10)
260            .map(|_| Pubkey::find_program_address(&seeds, &program_id))
261            .collect();
262
263        // All results should be identical
264        for result in &results[1..] {
265            assert_eq!(result.0, results[0].0);
266            assert_eq!(result.1, results[0].1);
267        }
268    }
269
270    #[test]
271    fn test_known_pda() {
272        // Test with a known program ID to ensure consistency
273        let program_id = Pubkey::default(); // All zeros
274        let seed = "test";
275        let seeds: Vec<&[u8]> = vec![seed.as_bytes()];
276
277        let (pda, bump) = Pubkey::find_program_address(&seeds, &program_id);
278
279        // The PDA should be consistent for these known inputs
280        let (expected_pda, expected_bump) =
281            Pubkey::find_program_address(&[seed.as_bytes()], &program_id);
282
283        assert_eq!(pda, expected_pda);
284        assert_eq!(bump, expected_bump);
285    }
286
287    #[test]
288    fn test_derived_pda_verify() {
289        let program_id = Pubkey::new_unique();
290        let seed1 = "vault";
291        let seed2 = Pubkey::new_unique();
292        let seeds: Vec<&[u8]> = vec![seed1.as_ref(), seed2.as_ref()];
293
294        let derived_pda = find_pda_with_bump_and_strings(&seeds, &program_id);
295
296        // Verify method should return true for correct program_id
297        assert!(derived_pda.verify(&program_id));
298
299        // Verify method should return false for different program_id
300        let different_program = Pubkey::new_unique();
301        assert!(!derived_pda.verify(&different_program));
302
303        // Manually verify that stored seeds produce the same PDA
304        let seed_refs: Vec<&[u8]> = derived_pda.seeds.iter().map(|s| s.as_slice()).collect();
305        let (manual_key, manual_bump) = Pubkey::find_program_address(&seed_refs, &program_id);
306        assert_eq!(derived_pda.key, manual_key);
307        assert_eq!(derived_pda.bump, manual_bump);
308    }
309
310    #[test]
311    fn test_derived_pda_fields() {
312        let program_id = Pubkey::new_unique();
313        let string_seed = "config";
314        let pubkey_seed = Pubkey::new_unique();
315        let byte_seed: u64 = 999;
316        let byte_seed_bytes = byte_seed.to_le_bytes();
317
318        let seeds: Vec<&[u8]> = vec![
319            string_seed.as_bytes(),
320            pubkey_seed.as_ref(),
321            &byte_seed_bytes,
322        ];
323        let derived_pda = find_pda_with_bump_and_strings(&seeds, &program_id);
324
325        // Check all fields are populated correctly
326        assert!(!derived_pda.key.is_on_curve());
327        // bump is a u8 so it's always <= 255
328        assert_eq!(derived_pda.seed_strings.len(), 3);
329        assert_eq!(derived_pda.seeds.len(), 3);
330
331        // Check string representations
332        assert_eq!(derived_pda.seed_strings[0], "config");
333        assert_eq!(derived_pda.seed_strings[1], pubkey_seed.to_string());
334        // Byte arrays are hex encoded
335        assert_eq!(derived_pda.seed_strings[2], hex::encode(byte_seed_bytes));
336
337        // Check raw seeds match inputs
338        assert_eq!(derived_pda.seeds[0], string_seed.as_bytes());
339        assert_eq!(derived_pda.seeds[1], pubkey_seed.as_ref());
340        assert_eq!(derived_pda.seeds[2], byte_seed_bytes.as_ref());
341
342        // Verify consistency with find_program_address
343        assert!(derived_pda.verify(&program_id));
344    }
345
346    #[test]
347    fn test_explicit_miner_pda_example() {
348        // Example: Miner PDA like in quarry-mine
349        let program_id = Pubkey::new_unique(); // Would be quarry_mine::ID in real code
350        let replica_quarry = Pubkey::new_unique();
351        let merge_miner = Pubkey::new_unique();
352
353        // Manual calculation exactly as in real code
354        let (expected_miner_pda, expected_bump) = Pubkey::find_program_address(
355            &[b"Miner", replica_quarry.as_ref(), merge_miner.as_ref()],
356            &program_id,
357        );
358
359        // Using our helper with the same seeds
360        let seeds: Vec<&[u8]> = vec![b"Miner", replica_quarry.as_ref(), merge_miner.as_ref()];
361        let derived_pda = find_pda_with_bump_and_strings(&seeds, &program_id);
362
363        // Verify they produce the same result
364        assert_eq!(derived_pda.key, expected_miner_pda);
365        assert_eq!(derived_pda.bump, expected_bump);
366
367        // Verify seed strings are useful for debugging
368        assert_eq!(derived_pda.seed_strings[0], "Miner");
369        assert_eq!(derived_pda.seed_strings[1], replica_quarry.to_string());
370        assert_eq!(derived_pda.seed_strings[2], merge_miner.to_string());
371
372        // Verify raw seeds match what was passed to find_program_address
373        assert_eq!(derived_pda.seeds[0], b"Miner");
374        assert_eq!(derived_pda.seeds[1], replica_quarry.as_ref());
375        assert_eq!(derived_pda.seeds[2], merge_miner.as_ref());
376    }
377
378    #[test]
379    fn test_explicit_vault_pda_example() {
380        // Example: Token vault PDA pattern
381        let program_id = Pubkey::new_unique();
382        let authority = Pubkey::new_unique();
383        let token_mint = Pubkey::new_unique();
384        let vault_id: u64 = 1;
385
386        // Manual calculation as would be done in production
387        let (expected_vault_pda, expected_bump) = Pubkey::find_program_address(
388            &[
389                b"vault",
390                authority.as_ref(),
391                token_mint.as_ref(),
392                &vault_id.to_le_bytes(),
393            ],
394            &program_id,
395        );
396
397        // Using our helper
398        let vault_id_bytes = vault_id.to_le_bytes();
399        let seeds: Vec<&[u8]> = vec![
400            b"vault",
401            authority.as_ref(),
402            token_mint.as_ref(),
403            &vault_id_bytes,
404        ];
405        let derived_pda = find_pda_with_bump_and_strings(&seeds, &program_id);
406
407        // Must produce identical results
408        assert_eq!(derived_pda.key, expected_vault_pda);
409        assert_eq!(derived_pda.bump, expected_bump);
410        assert!(derived_pda.verify(&program_id));
411    }
412
413    #[test]
414    fn test_explicit_metadata_pda_example() {
415        // Example: Metaplex metadata account pattern
416        let metadata_program_id = Pubkey::new_unique(); // Would be mpl_token_metadata::ID
417        let mint_pubkey = Pubkey::new_unique();
418
419        // Manual calculation as in Metaplex
420        let (expected_metadata_pda, expected_bump) = Pubkey::find_program_address(
421            &[
422                b"metadata",
423                metadata_program_id.as_ref(),
424                mint_pubkey.as_ref(),
425            ],
426            &metadata_program_id,
427        );
428
429        // Using our helper
430        let seeds: Vec<&[u8]> = vec![
431            b"metadata",
432            metadata_program_id.as_ref(),
433            mint_pubkey.as_ref(),
434        ];
435        let derived_pda = find_pda_with_bump_and_strings(&seeds, &metadata_program_id);
436
437        // Verify exact match
438        assert_eq!(derived_pda.key, expected_metadata_pda);
439        assert_eq!(derived_pda.bump, expected_bump);
440
441        // Verify seeds are stored correctly
442        assert_eq!(derived_pda.seeds[0], b"metadata");
443        assert_eq!(derived_pda.seeds[1], metadata_program_id.as_ref());
444        assert_eq!(derived_pda.seeds[2], mint_pubkey.as_ref());
445    }
446
447    #[test]
448    fn test_explicit_associated_token_account_example() {
449        // Example: ATA derivation pattern
450        let token_program_id = Pubkey::new_unique(); // Would be spl_token::ID
451        let associated_token_program_id = Pubkey::new_unique(); // Would be spl_associated_token_account::ID
452        let wallet = Pubkey::new_unique();
453        let mint = Pubkey::new_unique();
454
455        // Manual ATA calculation
456        let (expected_ata, _expected_bump) = Pubkey::find_program_address(
457            &[wallet.as_ref(), token_program_id.as_ref(), mint.as_ref()],
458            &associated_token_program_id,
459        );
460
461        // Using our helper
462        let seeds: Vec<&[u8]> = vec![wallet.as_ref(), token_program_id.as_ref(), mint.as_ref()];
463        let derived_pda = find_pda_with_bump_and_strings(&seeds, &associated_token_program_id);
464
465        // Must match the expected ATA
466        assert_eq!(derived_pda.key, expected_ata);
467
468        // Verify we can reconstruct the same PDA from stored seeds
469        let (reconstructed_ata, reconstructed_bump) = Pubkey::find_program_address(
470            &[
471                &derived_pda.seeds[0],
472                &derived_pda.seeds[1],
473                &derived_pda.seeds[2],
474            ],
475            &associated_token_program_id,
476        );
477        assert_eq!(reconstructed_ata, expected_ata);
478        assert_eq!(reconstructed_bump, derived_pda.bump);
479    }
480
481    #[test]
482    fn test_explicit_escrow_pda_example() {
483        // Example: Escrow account with mixed seed types
484        let program_id = Pubkey::new_unique();
485        let initializer = Pubkey::new_unique();
486        let escrow_seed = b"escrow";
487        let escrow_id: u32 = 12345;
488        let timestamp: i64 = 1234567890;
489
490        // Manual calculation with explicit byte arrays
491        let (expected_escrow_pda, expected_bump) = Pubkey::find_program_address(
492            &[
493                escrow_seed,
494                initializer.as_ref(),
495                &escrow_id.to_le_bytes(),
496                &timestamp.to_le_bytes(),
497            ],
498            &program_id,
499        );
500
501        // Using our helper with same seeds
502        let escrow_id_bytes = escrow_id.to_le_bytes();
503        let timestamp_bytes = timestamp.to_le_bytes();
504        let seeds: Vec<&[u8]> = vec![
505            b"escrow",
506            initializer.as_ref(),
507            &escrow_id_bytes,
508            &timestamp_bytes,
509        ];
510
511        // First, use the basic function
512        let (pda_basic, bump_basic) = Pubkey::find_program_address(&seeds, &program_id);
513        assert_eq!(pda_basic, expected_escrow_pda);
514        assert_eq!(bump_basic, expected_bump);
515
516        // Then use the detailed function
517        let derived_pda = find_pda_with_bump_and_strings(&seeds, &program_id);
518        assert_eq!(derived_pda.key, expected_escrow_pda);
519        assert_eq!(derived_pda.bump, expected_bump);
520
521        // Verify stored seeds exactly match what we passed to find_program_address
522        assert_eq!(derived_pda.seeds[0], escrow_seed);
523        assert_eq!(derived_pda.seeds[1], initializer.as_ref());
524        assert_eq!(derived_pda.seeds[2], escrow_id.to_le_bytes().as_ref());
525        assert_eq!(derived_pda.seeds[3], timestamp.to_le_bytes().as_ref());
526
527        // Double-check by manually reconstructing
528        let (manual_check, manual_bump) = Pubkey::find_program_address(
529            &[
530                &derived_pda.seeds[0],
531                &derived_pda.seeds[1],
532                &derived_pda.seeds[2],
533                &derived_pda.seeds[3],
534            ],
535            &program_id,
536        );
537        assert_eq!(manual_check, expected_escrow_pda);
538        assert_eq!(manual_bump, expected_bump);
539    }
540}