mpl_token_auth_rules/state/v2/constraint/
program_owned_tree.rs

1use solana_program::{msg, program_error::ProgramError, pubkey::PUBKEY_BYTES};
2
3use crate::{
4    error::RuleSetError,
5    state::RuleResult,
6    state::{
7        try_from_bytes,
8        v2::{Constraint, ConstraintType, Str32, HEADER_SECTION},
9        Header,
10    },
11    utils::{compute_merkle_root, is_zeroed},
12};
13
14/// Constraint representing a test where the `Pubkey` must be owned by a member of the Merkle
15/// tree in the rule.
16///
17/// This constraint requires `PayloadType` values of `PayloadType::Pubkey` and
18/// `PayloadType::MerkleProof`. The `field` values in the Rule are used to locate them in the
19/// `Payload`. Note this same `Pubkey` account must also be provided to `Validate` via the
20/// `additional_rule_accounts` argument. This is so that the `Pubkey`'s owner can be found
21/// from its `AccountInfo` struct. The owner and the proof are then used to calculate a Merkle
22/// root, which is compared against the root stored in the rule.
23pub struct ProgramOwnedTree<'a> {
24    /// The field in the `Payload` to be compared when looking for the Merkle proof.
25    pub pubkey_field: &'a Str32,
26    /// The field in the `Payload` to be compared when looking for the `Pubkey`.
27    pub proof_field: &'a Str32,
28    /// The root of the Merkle tree.
29    pub root: &'a [u8; PUBKEY_BYTES],
30}
31
32impl<'a> ProgramOwnedTree<'a> {
33    /// Deserialize a constraint from a byte array.
34    pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, RuleSetError> {
35        let pubkey_field = try_from_bytes::<Str32>(0, Str32::SIZE, bytes)?;
36        let mut cursor = Str32::SIZE;
37
38        let proof_field = try_from_bytes::<Str32>(cursor, Str32::SIZE, bytes)?;
39        cursor += Str32::SIZE;
40
41        let root = try_from_bytes::<[u8; 32]>(cursor, PUBKEY_BYTES, bytes)?;
42
43        Ok(Self {
44            pubkey_field,
45            proof_field,
46            root,
47        })
48    }
49
50    /// Serialize a constraint into a byte array.
51    pub fn serialize(
52        pubkey_field: String,
53        proof_field: String,
54        root: &[u8; PUBKEY_BYTES],
55    ) -> Result<Vec<u8>, RuleSetError> {
56        let length = (Str32::SIZE + Str32::SIZE + PUBKEY_BYTES) as u32;
57        let mut data = Vec::with_capacity(HEADER_SECTION + length as usize);
58
59        // Header
60        Header::serialize(ConstraintType::ProgramOwnedTree, length, &mut data);
61
62        // Constraint
63        // - pubkey_field
64        let mut field_bytes = [0u8; Str32::SIZE];
65        field_bytes[..pubkey_field.len()].copy_from_slice(pubkey_field.as_bytes());
66        data.extend(field_bytes);
67        // - proof_field
68        let mut field_bytes = [0u8; Str32::SIZE];
69        field_bytes[..proof_field.len()].copy_from_slice(proof_field.as_bytes());
70        data.extend(field_bytes);
71        // - root
72        data.extend_from_slice(root);
73
74        Ok(data)
75    }
76}
77
78impl<'a> Constraint<'a> for ProgramOwnedTree<'a> {
79    fn constraint_type(&self) -> ConstraintType {
80        ConstraintType::ProgramOwnedTree
81    }
82
83    fn validate(
84        &self,
85        accounts: &std::collections::HashMap<
86            solana_program::pubkey::Pubkey,
87            &solana_program::account_info::AccountInfo,
88        >,
89        payload: &crate::payload::Payload,
90        _update_rule_state: bool,
91        _rule_set_state_pda: &Option<&solana_program::account_info::AccountInfo>,
92        _rule_authority: &Option<&solana_program::account_info::AccountInfo>,
93    ) -> RuleResult {
94        msg!("Validating ProgramOwnedTree");
95
96        // Get the `Pubkey` we are checking from the payload.
97        let key = match payload.get_pubkey(&self.pubkey_field.to_string()) {
98            Some(pubkey) => pubkey,
99            _ => return RuleResult::Error(RuleSetError::MissingPayloadValue.into()),
100        };
101
102        // Get the `AccountInfo` struct for the `Pubkey`.
103        let account = match accounts.get(key) {
104            Some(account) => account,
105            _ => return RuleResult::Error(RuleSetError::MissingAccount.into()),
106        };
107
108        let data = match account.data.try_borrow() {
109            Ok(data) => data,
110            Err(_) => return RuleResult::Error(ProgramError::AccountBorrowFailed),
111        };
112
113        // Account must have nonzero data to count as program-owned.
114        if is_zeroed(&data) {
115            // Print helpful errors.
116            if data.len() == 0 {
117                msg!("Account data is empty");
118            } else {
119                msg!("Account data is zeroed");
120            }
121
122            return RuleResult::Error(RuleSetError::DataIsEmpty.into());
123        }
124
125        // The account owner is the leaf.
126        let leaf = account.owner;
127
128        // Get the Merkle proof from the payload.
129        let merkle_proof = match payload.get_merkle_proof(&self.proof_field.to_string()) {
130            Some(merkle_proof) => merkle_proof,
131            _ => return RuleResult::Error(RuleSetError::MissingPayloadValue.into()),
132        };
133
134        // Check if the computed hash (root) is equal to the root in the rule.
135        let computed_root = compute_merkle_root(leaf, merkle_proof);
136        if computed_root == *self.root {
137            RuleResult::Success(self.constraint_type().to_error())
138        } else {
139            RuleResult::Failure(self.constraint_type().to_error())
140        }
141    }
142}