mpl_token_auth_rules/state/v2/constraint/
pda_match.rs

1use solana_program::{
2    msg,
3    pubkey::{Pubkey, PUBKEY_BYTES},
4};
5
6use crate::{
7    error::RuleSetError,
8    state::RuleResult,
9    state::{
10        try_from_bytes,
11        v2::{Constraint, ConstraintType, Str32, HEADER_SECTION},
12        Header,
13    },
14    utils::assert_derivation,
15};
16
17const DEFAULT_PUBKEY: Pubkey = Pubkey::new_from_array([0u8; 32]);
18
19/// Constraint representing a test where a resulting PDA derivation of seeds must prove the
20/// account is a PDA.
21///
22/// This constraint requires `PayloadType` values of `PayloadType::Seeds`. The `field` values
23/// in the Rule are used to locate them in the `Payload`.  The seeds in the `Payload` and the
24/// program ID stored in the Rule are used to derive the PDA from the `Payload`.
25pub struct PDAMatch<'a> {
26    /// The program used for the PDA derivation. If a zeroed (default) pubkey is used then
27    /// the account owner is used.
28    pub program: &'a Pubkey,
29    /// The field in the `Payload` to be compared when looking for the PDA.
30    pub pda_field: &'a Str32,
31    /// The field in the `Payload` to be compared when looking for the seeds.
32    pub seeds_field: &'a Str32,
33}
34
35impl<'a> PDAMatch<'a> {
36    /// Deserialize a constraint from a byte array.
37    pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, RuleSetError> {
38        let program = try_from_bytes::<Pubkey>(0, PUBKEY_BYTES, bytes)?;
39        let mut cursor = PUBKEY_BYTES;
40
41        let pda_field = try_from_bytes::<Str32>(cursor, Str32::SIZE, bytes)?;
42        cursor += Str32::SIZE;
43
44        let seeds_field = try_from_bytes::<Str32>(cursor, Str32::SIZE, bytes)?;
45
46        Ok(Self {
47            program,
48            pda_field,
49            seeds_field,
50        })
51    }
52
53    /// Serialize a constraint into a byte array.
54    pub fn serialize(
55        pda_field: String,
56        program: Option<Pubkey>,
57        seeds_field: String,
58    ) -> Result<Vec<u8>, RuleSetError> {
59        let length = (PUBKEY_BYTES + Str32::SIZE + Str32::SIZE) as u32;
60        let mut data = Vec::with_capacity(HEADER_SECTION + length as usize);
61
62        // Header
63        Header::serialize(ConstraintType::PDAMatch, length, &mut data);
64
65        // Constraint
66        // - program
67        let program = program.unwrap_or(DEFAULT_PUBKEY);
68        data.extend(program.as_ref());
69        // - pda_field
70        let mut field_bytes = [0u8; Str32::SIZE];
71        field_bytes[..pda_field.len()].copy_from_slice(pda_field.as_bytes());
72        data.extend(field_bytes);
73        // - seeds_field
74        let mut field_bytes = [0u8; Str32::SIZE];
75        field_bytes[..seeds_field.len()].copy_from_slice(seeds_field.as_bytes());
76        data.extend(field_bytes);
77
78        Ok(data)
79    }
80}
81
82impl<'a> Constraint<'a> for PDAMatch<'a> {
83    fn constraint_type(&self) -> ConstraintType {
84        ConstraintType::PDAMatch
85    }
86
87    fn validate(
88        &self,
89        accounts: &std::collections::HashMap<
90            solana_program::pubkey::Pubkey,
91            &solana_program::account_info::AccountInfo,
92        >,
93        payload: &crate::payload::Payload,
94        _update_rule_state: bool,
95        _rule_set_state_pda: &Option<&solana_program::account_info::AccountInfo>,
96        _rule_authority: &Option<&solana_program::account_info::AccountInfo>,
97    ) -> RuleResult {
98        msg!("Validating PDAMatch");
99
100        // Get the PDA from the payload.
101        let account = match payload.get_pubkey(&self.pda_field.to_string()) {
102            Some(pubkey) => pubkey,
103            _ => return RuleResult::Error(RuleSetError::MissingPayloadValue.into()),
104        };
105
106        // Get the derivation seeds from the payload.
107        let seeds = match payload.get_seeds(&self.seeds_field.to_string()) {
108            Some(seeds) => seeds,
109            _ => return RuleResult::Error(RuleSetError::MissingPayloadValue.into()),
110        };
111
112        // Get the program ID to use for the PDA derivation from the Rule.
113        let program = match self.program {
114            &DEFAULT_PUBKEY => {
115                // If the Pubkey is the default, then assume the program ID is the account owner.
116                match accounts.get(account) {
117                    Some(account) => account.owner,
118                    _ => return RuleResult::Error(RuleSetError::MissingAccount.into()),
119                }
120            }
121            // If the Pubkey is stored in the rule, use that value.
122            _ => self.program,
123        };
124
125        // Convert the Vec of Vec into Vec of u8 slices.
126        let vec_of_slices = seeds
127            .seeds
128            .iter()
129            .map(Vec::as_slice)
130            .collect::<Vec<&[u8]>>();
131
132        if let Ok(_bump) = assert_derivation(program, account, &vec_of_slices) {
133            RuleResult::Success(self.constraint_type().to_error())
134        } else {
135            RuleResult::Failure(self.constraint_type().to_error())
136        }
137    }
138}