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    solana_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        solana_program::system_program::check_id(&self.owner)
99            || solana_program::incinerator::check_id(&self.owner)
100    }
101}
102impl IsInitialized for PodAccount {
103    fn is_initialized(&self) -> bool {
104        self.state == AccountState::Initialized as u8 || self.state == AccountState::Frozen as u8
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>
163where
164    T: Pod + Default,
165{
166    pub(crate) option: [u8; 4],
167    pub(crate) value: T,
168}
169impl<T> PodCOption<T>
170where
171    T: Pod + Default,
172{
173    /// Represents that no value is stored in the option, like `Option::None`
174    pub const NONE: [u8; 4] = [0; 4];
175    /// Represents that some value is stored in the option, like
176    /// `Option::Some(v)`
177    pub const SOME: [u8; 4] = [1, 0, 0, 0];
178
179    /// Create a `PodCOption` equivalent of `Option::None`
180    ///
181    /// This could be made `const` by using `std::mem::zeroed`, but that would
182    /// require `unsafe` code, which is prohibited at the crate level.
183    pub fn none() -> Self {
184        Self {
185            option: Self::NONE,
186            value: T::default(),
187        }
188    }
189
190    /// Create a `PodCOption` equivalent of `Option::Some(value)`
191    pub const fn some(value: T) -> Self {
192        Self {
193            option: Self::SOME,
194            value,
195        }
196    }
197
198    /// Get the underlying value or another provided value if it isn't set,
199    /// equivalent of `Option::unwrap_or`
200    pub fn unwrap_or(self, default: T) -> T {
201        if self.option == Self::NONE {
202            default
203        } else {
204            self.value
205        }
206    }
207
208    /// Checks to see if a value is set, equivalent of `Option::is_some`
209    pub fn is_some(&self) -> bool {
210        self.option == Self::SOME
211    }
212
213    /// Checks to see if no value is set, equivalent of `Option::is_none`
214    pub fn is_none(&self) -> bool {
215        self.option == Self::NONE
216    }
217
218    /// Converts the option into a Result, similar to `Option::ok_or`
219    pub fn ok_or<E>(self, error: E) -> Result<T, E> {
220        match self {
221            Self {
222                option: Self::SOME,
223                value,
224            } => Ok(value),
225            _ => Err(error),
226        }
227    }
228}
229impl<T: Pod + Default> From<COption<T>> for PodCOption<T> {
230    fn from(opt: COption<T>) -> Self {
231        match opt {
232            COption::None => Self {
233                option: Self::NONE,
234                value: T::default(),
235            },
236            COption::Some(v) => Self {
237                option: Self::SOME,
238                value: v,
239            },
240        }
241    }
242}
243impl TryFrom<PodCOption<Pubkey>> for OptionalNonZeroPubkey {
244    type Error = ProgramError;
245    fn try_from(p: PodCOption<Pubkey>) -> Result<Self, Self::Error> {
246        match p {
247            PodCOption {
248                option: PodCOption::<Pubkey>::SOME,
249                value,
250            } if value == Pubkey::default() => Err(ProgramError::InvalidArgument),
251            PodCOption {
252                option: PodCOption::<Pubkey>::SOME,
253                value,
254            } => Ok(Self(value)),
255            PodCOption {
256                option: PodCOption::<Pubkey>::NONE,
257                value: _,
258            } => Ok(Self(Pubkey::default())),
259            _ => unreachable!(),
260        }
261    }
262}
263
264#[cfg(test)]
265pub(crate) mod test {
266    use {
267        super::*,
268        crate::state::{
269            test::{
270                TEST_ACCOUNT, TEST_ACCOUNT_SLICE, TEST_MINT, TEST_MINT_SLICE, TEST_MULTISIG,
271                TEST_MULTISIG_SLICE,
272            },
273            AccountState,
274        },
275        spl_pod::bytemuck::pod_from_bytes,
276    };
277
278    pub const TEST_POD_MINT: PodMint = PodMint {
279        mint_authority: PodCOption::some(Pubkey::new_from_array([1; 32])),
280        supply: PodU64::from_primitive(42),
281        decimals: 7,
282        is_initialized: PodBool::from_bool(true),
283        freeze_authority: PodCOption::some(Pubkey::new_from_array([2; 32])),
284    };
285    pub const TEST_POD_ACCOUNT: PodAccount = PodAccount {
286        mint: Pubkey::new_from_array([1; 32]),
287        owner: Pubkey::new_from_array([2; 32]),
288        amount: PodU64::from_primitive(3),
289        delegate: PodCOption::some(Pubkey::new_from_array([4; 32])),
290        state: AccountState::Frozen as u8,
291        is_native: PodCOption::some(PodU64::from_primitive(5)),
292        delegated_amount: PodU64::from_primitive(6),
293        close_authority: PodCOption::some(Pubkey::new_from_array([7; 32])),
294    };
295
296    #[test]
297    fn pod_mint_to_mint_equality() {
298        let pod_mint = pod_from_bytes::<PodMint>(TEST_MINT_SLICE).unwrap();
299        assert_eq!(*pod_mint, PodMint::from(TEST_MINT));
300        assert_eq!(*pod_mint, TEST_POD_MINT);
301    }
302
303    #[test]
304    fn pod_account_to_account_equality() {
305        let pod_account = pod_from_bytes::<PodAccount>(TEST_ACCOUNT_SLICE).unwrap();
306        assert_eq!(*pod_account, PodAccount::from(TEST_ACCOUNT));
307        assert_eq!(*pod_account, TEST_POD_ACCOUNT);
308    }
309
310    #[test]
311    fn pod_multisig_to_multisig_equality() {
312        let pod_multisig = pod_from_bytes::<PodMultisig>(TEST_MULTISIG_SLICE).unwrap();
313        assert_eq!(*pod_multisig, PodMultisig::from(TEST_MULTISIG));
314    }
315}