Skip to main content

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