spl_token_2022/
pod.rs

1//! Rewrites of the base state types represented as Pods
2
3#[cfg(test)]
4use crate::state::{Account, Mint, Multisig};
5use {
6    crate::{
7        instruction::MAX_SIGNERS,
8        state::{AccountState, PackedSizeOf},
9    },
10    bytemuck::{Pod, Zeroable},
11    miraland_program::{
12        program_error::ProgramError, program_option::COption, program_pack::IsInitialized,
13        pubkey::Pubkey,
14    },
15    spl_pod::{
16        bytemuck::pod_get_packed_len,
17        optional_keys::OptionalNonZeroPubkey,
18        primitives::{PodBool, PodU64},
19    },
20};
21
22/// [Mint] data stored as a Pod type
23#[repr(C)]
24#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
25pub struct PodMint {
26    /// Optional authority used to mint new tokens. The mint authority may only
27    /// be provided during mint creation. If no mint authority is present
28    /// then the mint has a fixed supply and no further tokens may be
29    /// minted.
30    pub mint_authority: PodCOption<Pubkey>,
31    /// Total supply of tokens.
32    pub supply: PodU64,
33    /// Number of base 10 digits to the right of the decimal place.
34    pub decimals: u8,
35    /// If `true`, this structure has been initialized
36    pub is_initialized: PodBool,
37    /// Optional authority to freeze token accounts.
38    pub freeze_authority: PodCOption<Pubkey>,
39}
40impl IsInitialized for PodMint {
41    fn is_initialized(&self) -> bool {
42        self.is_initialized.into()
43    }
44}
45impl PackedSizeOf for PodMint {
46    const SIZE_OF: usize = pod_get_packed_len::<Self>();
47}
48#[cfg(test)]
49impl From<Mint> for PodMint {
50    fn from(mint: Mint) -> Self {
51        Self {
52            mint_authority: mint.mint_authority.into(),
53            supply: mint.supply.into(),
54            decimals: mint.decimals,
55            is_initialized: mint.is_initialized.into(),
56            freeze_authority: mint.freeze_authority.into(),
57        }
58    }
59}
60
61/// [Account] data stored as a Pod type
62#[repr(C)]
63#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
64pub struct PodAccount {
65    /// The mint associated with this account
66    pub mint: Pubkey,
67    /// The owner of this account.
68    pub owner: Pubkey,
69    /// The amount of tokens this account holds.
70    pub amount: PodU64,
71    /// If `delegate` is `Some` then `delegated_amount` represents
72    /// the amount authorized by the delegate
73    pub delegate: PodCOption<Pubkey>,
74    /// The account's [AccountState], stored as a u8
75    pub state: u8,
76    /// If is_some, this is a native token, and the value logs the rent-exempt
77    /// reserve. An Account is required to be rent-exempt, so the value is
78    /// used by the Processor to ensure that wrapped SOL accounts do not
79    /// drop below this threshold.
80    pub is_native: PodCOption<PodU64>,
81    /// The amount delegated
82    pub delegated_amount: PodU64,
83    /// Optional authority to close the account.
84    pub close_authority: PodCOption<Pubkey>,
85}
86impl PodAccount {
87    /// Checks if account is frozen
88    pub fn is_frozen(&self) -> bool {
89        self.state == AccountState::Frozen as u8
90    }
91    /// Checks if account is native
92    pub fn is_native(&self) -> bool {
93        self.is_native.is_some()
94    }
95    /// Checks if a token Account's owner is the system_program or the
96    /// incinerator
97    pub fn is_owned_by_system_program_or_incinerator(&self) -> bool {
98        miraland_program::system_program::check_id(&self.owner)
99            || miraland_program::incinerator::check_id(&self.owner)
100    }
101}
102impl IsInitialized for PodAccount {
103    fn is_initialized(&self) -> bool {
104        self.state != 0
105    }
106}
107impl PackedSizeOf for PodAccount {
108    const SIZE_OF: usize = pod_get_packed_len::<Self>();
109}
110#[cfg(test)]
111impl From<Account> for PodAccount {
112    fn from(account: Account) -> Self {
113        Self {
114            mint: account.mint,
115            owner: account.owner,
116            amount: account.amount.into(),
117            delegate: account.delegate.into(),
118            state: account.state.into(),
119            is_native: account.is_native.map(PodU64::from_primitive).into(),
120            delegated_amount: account.delegated_amount.into(),
121            close_authority: account.close_authority.into(),
122        }
123    }
124}
125
126/// [Multisig] data stored as a Pod type
127#[repr(C)]
128#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
129pub struct PodMultisig {
130    /// Number of signers required
131    pub m: u8,
132    /// Number of valid signers
133    pub n: u8,
134    /// If `true`, this structure has been initialized
135    pub is_initialized: PodBool,
136    /// Signer public keys
137    pub signers: [Pubkey; MAX_SIGNERS],
138}
139impl IsInitialized for PodMultisig {
140    fn is_initialized(&self) -> bool {
141        self.is_initialized.into()
142    }
143}
144impl PackedSizeOf for PodMultisig {
145    const SIZE_OF: usize = pod_get_packed_len::<Self>();
146}
147#[cfg(test)]
148impl From<Multisig> for PodMultisig {
149    fn from(multisig: Multisig) -> Self {
150        Self {
151            m: multisig.m,
152            n: multisig.n,
153            is_initialized: multisig.is_initialized.into(),
154            signers: multisig.signers,
155        }
156    }
157}
158
159/// COption<T> stored as a Pod type
160#[repr(C, packed)]
161#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
162pub struct PodCOption<T: Pod + Default> {
163    pub(crate) option: [u8; 4],
164    pub(crate) value: T,
165}
166impl<T: Pod + Default> PodCOption<T> {
167    /// Represents that no value is stored in the option, like `Option::None`
168    pub const NONE: [u8; 4] = [0; 4];
169    /// Represents that some value is stored in the option, like
170    /// `Option::Some(v)`
171    pub const SOME: [u8; 4] = [1, 0, 0, 0];
172
173    /// Create a PodCOption equivalent of `Option::None`
174    ///
175    /// This could be made `const` by using `std::mem::zeroed`, but that would
176    /// require `unsafe` code, which is prohibited at the crate level.
177    pub fn none() -> Self {
178        Self {
179            option: Self::NONE,
180            value: T::default(),
181        }
182    }
183
184    /// Create a PodCOption equivalent of `Option::Some(value)`
185    pub const fn some(value: T) -> Self {
186        Self {
187            option: Self::SOME,
188            value,
189        }
190    }
191
192    /// Get the underlying value or another provided value if it isn't set,
193    /// equivalent of `Option::unwrap_or`
194    pub fn unwrap_or(self, default: T) -> T {
195        if self.option == Self::NONE {
196            default
197        } else {
198            self.value
199        }
200    }
201
202    /// Checks to see if a value is set, equivalent of `Option::is_some`
203    pub fn is_some(&self) -> bool {
204        self.option == Self::SOME
205    }
206
207    /// Checks to see if no value is set, equivalent of `Option::is_none`
208    pub fn is_none(&self) -> bool {
209        self.option == Self::NONE
210    }
211
212    /// Converts the option into a Result, similar to `Option::ok_or`
213    pub fn ok_or<E>(self, error: E) -> Result<T, E> {
214        match self {
215            Self {
216                option: Self::SOME,
217                value,
218            } => Ok(value),
219            _ => Err(error),
220        }
221    }
222}
223impl<T: Pod + Default> From<COption<T>> for PodCOption<T> {
224    fn from(opt: COption<T>) -> Self {
225        match opt {
226            COption::None => Self {
227                option: Self::NONE,
228                value: T::default(),
229            },
230            COption::Some(v) => Self {
231                option: Self::SOME,
232                value: v,
233            },
234        }
235    }
236}
237impl TryFrom<PodCOption<Pubkey>> for OptionalNonZeroPubkey {
238    type Error = ProgramError;
239    fn try_from(p: PodCOption<Pubkey>) -> Result<Self, Self::Error> {
240        match p {
241            PodCOption {
242                option: PodCOption::<Pubkey>::SOME,
243                value,
244            } if value == Pubkey::default() => Err(ProgramError::InvalidArgument),
245            PodCOption {
246                option: PodCOption::<Pubkey>::SOME,
247                value,
248            } => Ok(Self(value)),
249            PodCOption {
250                option: PodCOption::<Pubkey>::NONE,
251                value: _,
252            } => Ok(Self(Pubkey::default())),
253            _ => unreachable!(),
254        }
255    }
256}
257
258#[cfg(test)]
259pub mod test {
260    use {
261        super::*,
262        crate::state::{
263            test::{
264                TEST_ACCOUNT, TEST_ACCOUNT_SLICE, TEST_MINT, TEST_MINT_SLICE, TEST_MULTISIG,
265                TEST_MULTISIG_SLICE,
266            },
267            AccountState,
268        },
269        spl_pod::bytemuck::pod_from_bytes,
270    };
271
272    pub const TEST_POD_MINT: PodMint = PodMint {
273        mint_authority: PodCOption::some(Pubkey::new_from_array([1; 32])),
274        supply: PodU64::from_primitive(42),
275        decimals: 7,
276        is_initialized: PodBool::from_bool(true),
277        freeze_authority: PodCOption::some(Pubkey::new_from_array([2; 32])),
278    };
279    pub const TEST_POD_ACCOUNT: PodAccount = PodAccount {
280        mint: Pubkey::new_from_array([1; 32]),
281        owner: Pubkey::new_from_array([2; 32]),
282        amount: PodU64::from_primitive(3),
283        delegate: PodCOption::some(Pubkey::new_from_array([4; 32])),
284        state: AccountState::Frozen as u8,
285        is_native: PodCOption::some(PodU64::from_primitive(5)),
286        delegated_amount: PodU64::from_primitive(6),
287        close_authority: PodCOption::some(Pubkey::new_from_array([7; 32])),
288    };
289
290    #[test]
291    fn pod_mint_to_mint_equality() {
292        let pod_mint = pod_from_bytes::<PodMint>(TEST_MINT_SLICE).unwrap();
293        assert_eq!(*pod_mint, PodMint::from(TEST_MINT));
294        assert_eq!(*pod_mint, TEST_POD_MINT);
295    }
296
297    #[test]
298    fn pod_account_to_account_equality() {
299        let pod_account = pod_from_bytes::<PodAccount>(TEST_ACCOUNT_SLICE).unwrap();
300        assert_eq!(*pod_account, PodAccount::from(TEST_ACCOUNT));
301        assert_eq!(*pod_account, TEST_POD_ACCOUNT);
302    }
303
304    #[test]
305    fn pod_multisig_to_multisig_equality() {
306        let pod_multisig = pod_from_bytes::<PodMultisig>(TEST_MULTISIG_SLICE).unwrap();
307        assert_eq!(*pod_multisig, PodMultisig::from(TEST_MULTISIG));
308    }
309}