1use {
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 pub(crate) decimals: u8,
22 pub(crate) mint_authority: Pubkey,
24 }
27#[repr(C)]
28#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
29pub(crate) struct InitializeMultisigData {
30 pub(crate) m: u8,
33}
34#[repr(C)]
35#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
36pub(crate) struct AmountData {
37 pub(crate) amount: PodU64,
39}
40#[repr(C)]
41#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
42pub(crate) struct AmountCheckedData {
43 pub(crate) amount: PodU64,
45 pub(crate) decimals: u8,
47}
48#[repr(C)]
49#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
50pub(crate) struct SetAuthorityData {
51 pub(crate) authority_type: u8,
53 }
56
57#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
64#[repr(u8)]
65pub(crate) enum PodTokenInstruction {
66 InitializeMint, InitializeAccount,
69 InitializeMultisig, Transfer, Approve, Revoke,
74 SetAuthority, MintTo, Burn, CloseAccount,
78 FreezeAccount,
80 ThawAccount,
81 TransferChecked, ApproveChecked, MintToChecked, BurnChecked, InitializeAccount2, SyncNative,
88 InitializeAccount3, InitializeMultisig2, InitializeMint2, GetAccountDataSize, InitializeImmutableOwner,
94 AmountToUiAmount, UiAmountToAmount, InitializeMintCloseAuthority, TransferFeeExtension,
99 ConfidentialTransferExtension,
100 DefaultAccountStateExtension,
101 Reallocate, MemoTransferExtension,
104 CreateNativeMint,
105 InitializeNonTransferableMint,
106 InterestBearingMintExtension,
107 CpiGuardExtension,
108 InitializePermanentDelegate, TransferHookExtension,
111 ConfidentialTransferFeeExtension,
112 WithdrawExcessLamports,
113 MetadataPointerExtension,
114 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
136pub(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 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 }
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}