mpl_candy_guard/guards/
program_gate.rs

1use solana_program::{
2    pubkey,
3    serialize_utils::{read_pubkey, read_u16},
4    system_program,
5};
6
7use super::*;
8use crate::{errors::CandyGuardError, state::GuardType, utils::cmp_pubkeys};
9
10// Default list of authorized programs.
11pub static DEFAULT_PROGRAMS: &[Pubkey] = &[
12    crate::ID,
13    mpl_candy_machine_core::ID,
14    system_program::ID,
15    spl_token::ID,
16    spl_associated_token_account::ID,
17    pubkey!("ComputeBudget111111111111111111111111111111"),
18    pubkey!("SysExL2WDyJi9aRZrXorrjHJut3JwHQ7R9bTyctbNNG"),
19];
20
21// Maximum number of programs in the additional list.
22const MAXIMUM_SIZE: usize = 5;
23
24/// Guard that restricts the programs that can be in a mint transaction. The guard allows the
25/// necessary programs for the mint and any other program specified in the configuration.
26#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
27pub struct ProgramGate {
28    pub additional: Vec<Pubkey>,
29}
30
31impl Guard for ProgramGate {
32    fn size() -> usize {
33        4 + (MAXIMUM_SIZE * 32) // programs
34    }
35
36    fn mask() -> u64 {
37        GuardType::as_mask(GuardType::ProgramGate)
38    }
39
40    fn verify(data: &CandyGuardData) -> Result<()> {
41        if let Some(program_gate) = &data.default.program_gate {
42            if program_gate.additional.len() > MAXIMUM_SIZE {
43                return err!(CandyGuardError::ExceededProgramListSize);
44            }
45        }
46
47        if let Some(groups) = &data.groups {
48            for group in groups {
49                if let Some(program_gate) = &group.guards.program_gate {
50                    if program_gate.additional.len() > MAXIMUM_SIZE {
51                        return err!(CandyGuardError::ExceededProgramListSize);
52                    }
53                }
54            }
55        }
56
57        Ok(())
58    }
59}
60
61impl Condition for ProgramGate {
62    fn validate<'info>(
63        &self,
64        ctx: &mut EvaluationContext,
65        _guard_set: &GuardSet,
66        _mint_args: &[u8],
67    ) -> Result<()> {
68        let ix_sysvar_account = &ctx.accounts.sysvar_instructions;
69        let ix_sysvar_account_info = ix_sysvar_account.to_account_info();
70
71        let mut programs: Vec<Pubkey> =
72            Vec::with_capacity(DEFAULT_PROGRAMS.len() + self.additional.len());
73        programs.extend(DEFAULT_PROGRAMS);
74        programs.extend(&self.additional);
75
76        verify_programs(&ix_sysvar_account_info, &programs)
77    }
78}
79
80pub fn verify_programs(sysvar: &AccountInfo, programs: &[Pubkey]) -> Result<()> {
81    let sysvar_data = sysvar.data.borrow();
82
83    let mut index = 0;
84    // determines the total number of instructions in the transaction
85    let num_instructions =
86        read_u16(&mut index, &sysvar_data).map_err(|_| ProgramError::InvalidAccountData)?;
87
88    'outer: for index in 0..num_instructions {
89        let mut offset = 2 + (index * 2) as usize;
90
91        // offset for the number of accounts
92        offset = read_u16(&mut offset, &sysvar_data).unwrap() as usize;
93        let num_accounts = read_u16(&mut offset, &sysvar_data).unwrap();
94
95        // offset for the program id
96        offset += (num_accounts as usize) * (1 + 32);
97        let program_id = read_pubkey(&mut offset, &sysvar_data).unwrap();
98
99        for program in programs {
100            if cmp_pubkeys(&program_id, program) {
101                continue 'outer;
102            }
103        }
104
105        msg!("Transaction had ix with program id {}", program_id);
106        // if we reach this point, the program id was not found in the
107        // programs list (the validation will fail)
108        return err!(CandyGuardError::UnauthorizedProgramFound);
109    }
110
111    Ok(())
112}