Skip to main content

agsol_common/
signer_pda.rs

1use solana_program::account_info::AccountInfo;
2use solana_program::program_error::ProgramError;
3use solana_program::pubkey::Pubkey;
4
5pub type SignerPdaError = &'static str;
6
7/// PDA with easy access to its signer seeds.
8#[derive(Debug, Clone, Copy)]
9pub struct SignerPda<'a, 'b> {
10    pub pda: Pubkey,
11    pub bump: [u8; 1],
12    pub seeds: &'b [&'a [u8]],
13}
14
15impl<'a, 'b> SignerPda<'a, 'b> {
16    /// Computes a new PDA and checks whether it matches the expected address.
17    pub fn new_checked(
18        seeds: &'b [&'a [u8]],
19        program_id: &Pubkey,
20        expected: &AccountInfo,
21    ) -> Result<Self, ProgramError> {
22        let (pda, bump) = Self::find_and_check(seeds, program_id, expected.key)?;
23        Ok(Self {
24            pda,
25            bump: [bump],
26            seeds,
27        })
28    }
29
30    /// Checks whether there's an existing PDA account with the provided owner.
31    pub fn check_owner(
32        seeds: &'b [&'a [u8]],
33        program_id: &Pubkey,
34        owner: &Pubkey,
35        expected: &AccountInfo,
36    ) -> Result<(), ProgramError> {
37        Self::find_and_check(seeds, program_id, expected.key)?;
38        if expected.owner != owner {
39            Err(ProgramError::IllegalOwner)
40        } else {
41            Ok(())
42        }
43    }
44
45    fn find_and_check(
46        seeds: &'b [&'a [u8]],
47        program_id: &Pubkey,
48        expected: &Pubkey,
49    ) -> Result<(Pubkey, u8), ProgramError> {
50        let (pda, bump) = Pubkey::find_program_address(seeds, program_id);
51        if &pda != expected {
52            Err(ProgramError::InvalidSeeds)
53        } else {
54            Ok((pda, bump))
55        }
56    }
57
58    /// Returns the signer seeds (seeds + bump seed) of the PDA.
59    pub fn signer_seeds(&'a self) -> Vec<&'a [u8]> {
60        let mut signer_seeds = self.seeds.to_vec();
61        signer_seeds.push(&self.bump);
62        signer_seeds
63    }
64}
65
66#[cfg(test)]
67mod test {
68    use super::*;
69
70    #[test]
71    fn test_checks_and_seeds() {
72        let program_id = Pubkey::new_unique();
73        let seed_pubkey = Pubkey::new_unique();
74        let seeds = &[b"this is a seed", seed_pubkey.as_ref()];
75        let (pda, bump) = Pubkey::find_program_address(seeds, &program_id);
76
77        let mut data = [2_u8, 3, 4, 5, 6, 7];
78        let mut lamports = 1500;
79        let mut account_info = AccountInfo::new(
80            &pda,
81            false,
82            false,
83            &mut lamports,
84            data.as_mut_slice(),
85            &program_id,
86            false,
87            0,
88        );
89        let signer_pda = SignerPda::new_checked(seeds, &program_id, &account_info).unwrap();
90        assert_eq!(signer_pda.pda, pda);
91        let mut expected_signer_seeds = seeds.to_vec();
92        let bump_slice = [bump];
93        expected_signer_seeds.push(&bump_slice);
94        assert_eq!(signer_pda.signer_seeds(), expected_signer_seeds);
95
96        // bad program_id
97        assert_eq!(
98            SignerPda::new_checked(seeds, &Pubkey::new_unique(), &account_info)
99                .err()
100                .unwrap(),
101            ProgramError::InvalidSeeds
102        );
103        // bad seeeds
104        assert_eq!(
105            SignerPda::new_checked(&[b"bad seed"], &program_id, &account_info)
106                .err()
107                .unwrap(),
108            ProgramError::InvalidSeeds
109        );
110        // check existing
111        assert!(SignerPda::check_owner(seeds, &program_id, &program_id, &account_info).is_ok());
112        // bad owner
113        let new_owner = Pubkey::new_unique();
114        account_info.owner = &new_owner;
115        assert_eq!(
116            SignerPda::check_owner(seeds, &program_id, &program_id, &account_info)
117                .err()
118                .unwrap(),
119            ProgramError::IllegalOwner
120        );
121
122        assert!(SignerPda::check_owner(seeds, &program_id, &new_owner, &account_info).is_ok());
123    }
124}