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