1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use solana_program::{
    pubkey,
    serialize_utils::{read_pubkey, read_u16},
    system_program,
};

use super::*;
use crate::{errors::CandyGuardError, state::GuardType, utils::cmp_pubkeys};

// Default list of authorized programs.
pub static DEFAULT_PROGRAMS: &[Pubkey] = &[
    crate::ID,
    mpl_core_candy_machine_core::ID,
    system_program::ID,
    spl_token::ID,
    spl_associated_token_account::ID,
    pubkey!("ComputeBudget111111111111111111111111111111"),
    pubkey!("SysExL2WDyJi9aRZrXorrjHJut3JwHQ7R9bTyctbNNG"),
];

// Maximum number of programs in the additional list.
const MAXIMUM_SIZE: usize = 5;

/// Guard that restricts the programs that can be in a mint transaction. The guard allows the
/// necessary programs for the mint and any other program specified in the configuration.
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
pub struct ProgramGate {
    pub additional: Vec<Pubkey>,
}

impl Guard for ProgramGate {
    fn size() -> usize {
        4 + (MAXIMUM_SIZE * 32) // programs
    }

    fn mask() -> u64 {
        GuardType::as_mask(GuardType::ProgramGate)
    }

    fn verify(data: &CandyGuardData) -> Result<()> {
        if let Some(program_gate) = &data.default.program_gate {
            if program_gate.additional.len() > MAXIMUM_SIZE {
                return err!(CandyGuardError::ExceededProgramListSize);
            }
        }

        if let Some(groups) = &data.groups {
            for group in groups {
                if let Some(program_gate) = &group.guards.program_gate {
                    if program_gate.additional.len() > MAXIMUM_SIZE {
                        return err!(CandyGuardError::ExceededProgramListSize);
                    }
                }
            }
        }

        Ok(())
    }
}

impl Condition for ProgramGate {
    fn validate<'info>(
        &self,
        ctx: &mut EvaluationContext,
        _guard_set: &GuardSet,
        _mint_args: &[u8],
    ) -> Result<()> {
        let ix_sysvar_account = &ctx.accounts.sysvar_instructions;
        let ix_sysvar_account_info = ix_sysvar_account.to_account_info();

        let mut programs: Vec<Pubkey> =
            Vec::with_capacity(DEFAULT_PROGRAMS.len() + self.additional.len());
        programs.extend(DEFAULT_PROGRAMS);
        programs.extend(&self.additional);

        verify_programs(&ix_sysvar_account_info, &programs)
    }
}

pub fn verify_programs(sysvar: &AccountInfo, programs: &[Pubkey]) -> Result<()> {
    let sysvar_data = sysvar.data.borrow();

    let mut index = 0;
    // determines the total number of instructions in the transaction
    let num_instructions =
        read_u16(&mut index, &sysvar_data).map_err(|_| ProgramError::InvalidAccountData)?;

    'outer: for index in 0..num_instructions {
        let mut offset = 2 + (index * 2) as usize;

        // offset for the number of accounts
        offset = read_u16(&mut offset, &sysvar_data).unwrap() as usize;
        let num_accounts = read_u16(&mut offset, &sysvar_data).unwrap();

        // offset for the program id
        offset += (num_accounts as usize) * (1 + 32);
        let program_id = read_pubkey(&mut offset, &sysvar_data).unwrap();

        for program in programs {
            if cmp_pubkeys(&program_id, program) {
                continue 'outer;
            }
        }

        msg!("Transaction had ix with program id {}", program_id);
        // if we reach this point, the program id was not found in the
        // programs list (the validation will fail)
        return err!(CandyGuardError::UnauthorizedProgramFound);
    }

    Ok(())
}