spl_token_2022/
pod_instruction.rs

1//! Rewrites of the instruction data types represented as Pods
2
3use {
4    crate::pod::PodCOption,
5    bytemuck::{Pod, Zeroable},
6    num_enum::{IntoPrimitive, TryFromPrimitive},
7    solana_program::{
8        program_error::ProgramError,
9        pubkey::{Pubkey, PUBKEY_BYTES},
10    },
11    spl_pod::{
12        bytemuck::{pod_from_bytes, pod_get_packed_len},
13        primitives::PodU64,
14    },
15};
16
17#[repr(C)]
18#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
19pub(crate) struct InitializeMintData {
20    /// Number of base 10 digits to the right of the decimal place.
21    pub(crate) decimals: u8,
22    /// The authority/multisignature to mint tokens.
23    pub(crate) mint_authority: Pubkey,
24    // The freeze authority option comes later, but cannot be included as
25    // plain old data in this struct
26}
27#[repr(C)]
28#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
29pub(crate) struct InitializeMultisigData {
30    /// The number of signers (M) required to validate this multisignature
31    /// account.
32    pub(crate) m: u8,
33}
34#[repr(C)]
35#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
36pub(crate) struct AmountData {
37    /// The amount of tokens to transfer.
38    pub(crate) amount: PodU64,
39}
40#[repr(C)]
41#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
42pub(crate) struct AmountCheckedData {
43    /// The amount of tokens to transfer.
44    pub(crate) amount: PodU64,
45    /// Decimals of the mint
46    pub(crate) decimals: u8,
47}
48#[repr(C)]
49#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
50pub(crate) struct SetAuthorityData {
51    /// The type of authority to update.
52    pub(crate) authority_type: u8,
53    // The new authority option comes later, but cannot be included as
54    // plain old data in this struct
55}
56
57/// All of the base instructions in Token-2022, reduced down to their one-byte
58/// discriminant.
59///
60/// All instructions that expect data afterwards include a comment with the data
61/// type expected. For example, `PodTokenInstruction::InitializeMint` expects
62/// `InitializeMintData`.
63#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
64#[repr(u8)]
65pub(crate) enum PodTokenInstruction {
66    // 0
67    InitializeMint, // InitializeMintData
68    InitializeAccount,
69    InitializeMultisig, // InitializeMultisigData
70    Transfer,           // AmountData
71    Approve,            // AmountData
72    // 5
73    Revoke,
74    SetAuthority, // SetAuthorityData
75    MintTo,       // AmountData
76    Burn,         // AmountData
77    CloseAccount,
78    // 10
79    FreezeAccount,
80    ThawAccount,
81    TransferChecked, // AmountCheckedData
82    ApproveChecked,  // AmountCheckedData
83    MintToChecked,   // AmountCheckedData
84    // 15
85    BurnChecked,        // AmountCheckedData
86    InitializeAccount2, // Pubkey
87    SyncNative,
88    InitializeAccount3,  // Pubkey
89    InitializeMultisig2, // InitializeMultisigData
90    // 20
91    InitializeMint2,    // InitializeMintData
92    GetAccountDataSize, // &[ExtensionType]
93    InitializeImmutableOwner,
94    AmountToUiAmount, // AmountData
95    UiAmountToAmount, // &str
96    // 25
97    InitializeMintCloseAuthority, // COption<Pubkey>
98    TransferFeeExtension,
99    ConfidentialTransferExtension,
100    DefaultAccountStateExtension,
101    Reallocate, // &[ExtensionType]
102    // 30
103    MemoTransferExtension,
104    CreateNativeMint,
105    InitializeNonTransferableMint,
106    InterestBearingMintExtension,
107    CpiGuardExtension,
108    // 35
109    InitializePermanentDelegate, // Pubkey
110    TransferHookExtension,
111    ConfidentialTransferFeeExtension,
112    WithdrawExcessLamports,
113    MetadataPointerExtension,
114    // 40
115    GroupPointerExtension,
116    GroupMemberPointerExtension,
117    ConfidentialMintBurnExtension,
118    ScaledUiAmountExtension,
119    PausableExtension,
120}
121
122fn unpack_pubkey_option(input: &[u8]) -> Result<PodCOption<Pubkey>, ProgramError> {
123    match input.split_first() {
124        Option::Some((&0, _)) => Ok(PodCOption::none()),
125        Option::Some((&1, rest)) => {
126            let pk = rest
127                .get(..PUBKEY_BYTES)
128                .and_then(|x| Pubkey::try_from(x).ok())
129                .ok_or(ProgramError::InvalidInstructionData)?;
130            Ok(PodCOption::some(pk))
131        }
132        _ => Err(ProgramError::InvalidInstructionData),
133    }
134}
135
136/// Specialty function for deserializing `Pod` data and a `COption<Pubkey>`
137///
138/// `COption<T>` is not `Pod` compatible when serialized in an instruction, but
139/// since it is always at the end of an instruction, so we can do this safely
140pub(crate) fn decode_instruction_data_with_coption_pubkey<T: Pod>(
141    input_with_type: &[u8],
142) -> Result<(&T, PodCOption<Pubkey>), ProgramError> {
143    let end_of_t = pod_get_packed_len::<T>().saturating_add(1);
144    let value = input_with_type
145        .get(1..end_of_t)
146        .ok_or(ProgramError::InvalidInstructionData)
147        .and_then(pod_from_bytes)?;
148    let pubkey = unpack_pubkey_option(&input_with_type[end_of_t..])?;
149    Ok((value, pubkey))
150}
151
152#[cfg(test)]
153mod tests {
154    use {
155        super::*,
156        crate::{
157            extension::ExtensionType,
158            instruction::{decode_instruction_data, decode_instruction_type},
159        },
160        proptest::prelude::*,
161    };
162
163    // Test function that mimics the "unpacking" in `Processor::process` by
164    // trying to deserialize the relevant type data after the instruction type
165    fn check_pod_instruction(input: &[u8]) -> Result<(), ProgramError> {
166        if let Ok(instruction_type) = decode_instruction_type(input) {
167            match instruction_type {
168                PodTokenInstruction::InitializeMint | PodTokenInstruction::InitializeMint2 => {
169                    let _ =
170                        decode_instruction_data_with_coption_pubkey::<InitializeMintData>(input)?;
171                }
172                PodTokenInstruction::InitializeAccount2
173                | PodTokenInstruction::InitializeAccount3
174                | PodTokenInstruction::InitializePermanentDelegate => {
175                    let _ = decode_instruction_data::<Pubkey>(input)?;
176                }
177                PodTokenInstruction::InitializeMultisig
178                | PodTokenInstruction::InitializeMultisig2 => {
179                    let _ = decode_instruction_data::<InitializeMultisigData>(input)?;
180                }
181                PodTokenInstruction::SetAuthority => {
182                    let _ = decode_instruction_data_with_coption_pubkey::<SetAuthorityData>(input)?;
183                }
184                PodTokenInstruction::Transfer
185                | PodTokenInstruction::Approve
186                | PodTokenInstruction::MintTo
187                | PodTokenInstruction::Burn
188                | PodTokenInstruction::AmountToUiAmount => {
189                    let _ = decode_instruction_data::<AmountData>(input)?;
190                }
191                PodTokenInstruction::TransferChecked
192                | PodTokenInstruction::ApproveChecked
193                | PodTokenInstruction::MintToChecked
194                | PodTokenInstruction::BurnChecked => {
195                    let _ = decode_instruction_data::<AmountCheckedData>(input)?;
196                }
197                PodTokenInstruction::InitializeMintCloseAuthority => {
198                    let _ = decode_instruction_data_with_coption_pubkey::<()>(input)?;
199                }
200                PodTokenInstruction::UiAmountToAmount => {
201                    let _ = std::str::from_utf8(&input[1..])
202                        .map_err(|_| ProgramError::InvalidInstructionData)?;
203                }
204                PodTokenInstruction::GetAccountDataSize | PodTokenInstruction::Reallocate => {
205                    let _ = input[1..]
206                        .chunks(std::mem::size_of::<ExtensionType>())
207                        .map(ExtensionType::try_from)
208                        .collect::<Result<Vec<_>, _>>()?;
209                }
210                _ => {
211                    // no extra data to deserialize
212                }
213            }
214        }
215        Ok(())
216    }
217
218    proptest! {
219        #![proptest_config(ProptestConfig::with_cases(1024))]
220        #[test]
221        fn test_instruction_unpack_proptest(
222            data in prop::collection::vec(any::<u8>(), 0..255)
223        ) {
224            let _no_panic = check_pod_instruction(&data);
225        }
226    }
227}