mpl_token_auth_rules/state/
mod.rs

1//! All structures and related functions representing a Rule Set on-chain.
2//!
3//! Key types include the main `RuleSetV1` type which keeps the the map of operations to `Rules`,
4//! `RuleSetV2` type which keep a list of `RuleV2`, as well as `RuleSetHeader` and `RuleSetRevisionMapV1`
5//! types used to manage data within the `RuleSet` PDA.
6//!
7//! Each time a `RuleSet` is updated, a new revision is added to the PDA, and previous revisions
8//! never deleted. The revision map is needed so that during `RuleSet` validation the desired
9//! revision can be selected by the user.
10//!
11//! Because the `RuleSet`s and the revision map are variable size, a fixed size header is stored
12//! at the beginning of the `RuleSet` PDA that allows new `RuleSets` and updated revision maps
13//! to be added to the PDA without moving the previous revision `RuleSets` and without losing the
14//! revision map's location.
15//!
16//! Also note there is a 1-byte version preceding each `RuleSetV1` revision (for `RuleSetV2` the
17//! first byte of the serialized rule set is the version) and the revision map.
18//!
19//! Approximate RuleSet PDA data layout
20//! ```text
21//! | Header  | RuleSetV1 version | RuleSetV1 Revision 0 | RuleSetV1 version | RuleSetV1 Revision 1 | RuleSetV2 Revision 2 | ... | RuleSetRevisionMap version | RuleSetRevisionMap |
22//! |---------|-------------------|----------------------|-------------------|----------------------|----------------------|-----|----------------------------|--------------------|
23//! | 9 bytes | 1 byte            | variable bytes       | 1 byte            | variable bytes       | variable bytes       | ... | 1 byte                     | variable bytes     |
24//!
25//! When `RuleSetV2` revisions are added, there migh be the need to add padding bytes to align the data
26//! to 8 bytes.
27//! ```
28use borsh::{BorshDeserialize, BorshSerialize};
29use num_derive::FromPrimitive;
30use num_traits::FromPrimitive;
31use solana_program::{
32    account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
33};
34
35mod frequency;
36mod rule_set;
37mod rules;
38mod v2;
39
40pub use frequency::*;
41pub use rule_set::*;
42pub use rules::*;
43pub use v2::*;
44
45use crate::{error::RuleSetError, utils::assert_owned_by};
46
47/// The maximum size that can be allocated at one time for a PDA.
48pub const CHUNK_SIZE: usize = 10_000;
49
50#[repr(C)]
51#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, Copy, FromPrimitive)]
52/// The key at the beginning of the serialized account that identifies the account type.
53/// NOTE: This is not used for the `RuleSet` account, which uses msgpack instead of Borsh for SerDes.
54pub enum Key {
55    /// An uninitialized account, which has all bytes set to zero by default.
56    Uninitialized,
57    /// An account containing a RuleSet.
58    RuleSet,
59    /// An account containing frequency state.
60    Frequency,
61}
62
63/// A trait implementing generic functions required by all accounts on Solana.
64pub trait SolanaAccount: BorshSerialize + BorshDeserialize {
65    /// Get the `Key` for this `Account`.  This key is to be stored in the first byte of the
66    /// `Account` data.
67    fn key() -> Key;
68
69    /// BorshDeserialize the `AccountInfo` into the Rust data structure.
70    fn from_account_info(account: &AccountInfo) -> Result<Self, ProgramError> {
71        let data = account
72            .data
73            .try_borrow()
74            .map_err(|_| ProgramError::AccountBorrowFailed)?;
75
76        if !Self::is_correct_account_type_and_size(&data, Self::key()) {
77            return Err(RuleSetError::DataTypeMismatch.into());
78        }
79
80        let data = Self::try_from_slice(&data)?;
81
82        // Check that this account is owned by this program.
83        assert_owned_by(account, &crate::ID)?;
84
85        Ok(data)
86    }
87
88    /// BorshSerialize the Rust data structure into the `Account` data.
89    fn to_account_data(&self, account: &AccountInfo) -> ProgramResult {
90        let mut data = account.try_borrow_mut_data()?;
91        self.serialize(&mut *data).map_err(Into::into)
92    }
93}
94
95trait PrivateSolanaAccountMethods: SolanaAccount {
96    const KEY_BYTE: usize = 0;
97
98    // Check the `Key` byte and the data size to determine if this data represents the correct
99    // account types.
100    fn is_correct_account_type_and_size(data: &[u8], data_type: Key) -> bool {
101        let key: Option<Key> = Key::from_u8(data[Self::KEY_BYTE]);
102        match key {
103            Some(key) => key == data_type,
104            None => false,
105        }
106    }
107}
108
109impl<T: SolanaAccount> PrivateSolanaAccountMethods for T {}