1use {
4 bytemuck::{Pod, Zeroable},
5 num_enum::{IntoPrimitive, TryFromPrimitive},
6 solana_program_error::ProgramError,
7 solana_pubkey::{Pubkey, PUBKEY_BYTES},
8 spl_pod::{
9 bytemuck::{pod_from_bytes, pod_get_packed_len},
10 primitives::PodU64,
11 },
12 spl_token_2022_interface::pod::PodCOption,
13};
14
15#[repr(C)]
16#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
17pub(crate) struct InitializeMintData {
18 pub(crate) decimals: u8,
20 pub(crate) mint_authority: Pubkey,
22 }
25#[repr(C)]
26#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
27pub(crate) struct InitializeMultisigData {
28 pub(crate) m: u8,
31}
32#[repr(C)]
33#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
34pub(crate) struct AmountData {
35 pub(crate) amount: PodU64,
37}
38#[repr(C)]
39#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
40pub(crate) struct AmountCheckedData {
41 pub(crate) amount: PodU64,
43 pub(crate) decimals: u8,
45}
46#[repr(C)]
47#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
48pub(crate) struct SetAuthorityData {
49 pub(crate) authority_type: u8,
51 }
54
55#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
62#[repr(u8)]
63pub(crate) enum PodTokenInstruction {
64 InitializeMint, InitializeAccount,
67 InitializeMultisig, Transfer, Approve, Revoke,
72 SetAuthority, MintTo, Burn, CloseAccount,
76 FreezeAccount,
78 ThawAccount,
79 TransferChecked, ApproveChecked, MintToChecked, BurnChecked, InitializeAccount2, SyncNative,
86 InitializeAccount3, InitializeMultisig2, InitializeMint2, GetAccountDataSize, InitializeImmutableOwner,
92 AmountToUiAmount, UiAmountToAmount, InitializeMintCloseAuthority, TransferFeeExtension,
97 ConfidentialTransferExtension,
98 DefaultAccountStateExtension,
99 Reallocate, MemoTransferExtension,
102 CreateNativeMint,
103 InitializeNonTransferableMint,
104 InterestBearingMintExtension,
105 CpiGuardExtension,
106 InitializePermanentDelegate, TransferHookExtension,
109 ConfidentialTransferFeeExtension,
110 WithdrawExcessLamports,
111 MetadataPointerExtension,
112 GroupPointerExtension,
114 GroupMemberPointerExtension,
115 ConfidentialMintBurnExtension,
116 ScaledUiAmountExtension,
117 PausableExtension,
118}
119
120fn unpack_pubkey_option(input: &[u8]) -> Result<PodCOption<Pubkey>, ProgramError> {
121 match input.split_first() {
122 Option::Some((&0, _)) => Ok(PodCOption::none()),
123 Option::Some((&1, rest)) => {
124 let pk = rest
125 .get(..PUBKEY_BYTES)
126 .and_then(|x| Pubkey::try_from(x).ok())
127 .ok_or(ProgramError::InvalidInstructionData)?;
128 Ok(PodCOption::some(pk))
129 }
130 _ => Err(ProgramError::InvalidInstructionData),
131 }
132}
133
134pub(crate) fn decode_instruction_data_with_coption_pubkey<T: Pod>(
139 input_with_type: &[u8],
140) -> Result<(&T, PodCOption<Pubkey>), ProgramError> {
141 let end_of_t = pod_get_packed_len::<T>().saturating_add(1);
142 let value = input_with_type
143 .get(1..end_of_t)
144 .ok_or(ProgramError::InvalidInstructionData)
145 .and_then(pod_from_bytes)?;
146 let pubkey = unpack_pubkey_option(&input_with_type[end_of_t..])?;
147 Ok((value, pubkey))
148}
149
150#[cfg(test)]
151mod tests {
152 use {
153 super::*,
154 crate::{
155 extension::ExtensionType,
156 instruction::{
157 decode_instruction_data, decode_instruction_type, AuthorityType, TokenInstruction,
158 },
159 },
160 proptest::prelude::*,
161 solana_program_option::COption,
162 };
163
164 fn check_pod_instruction(input: &[u8]) -> Result<(), ProgramError> {
167 if let Ok(instruction_type) = decode_instruction_type(input) {
168 match instruction_type {
169 PodTokenInstruction::InitializeMint | PodTokenInstruction::InitializeMint2 => {
170 let _ =
171 decode_instruction_data_with_coption_pubkey::<InitializeMintData>(input)?;
172 }
173 PodTokenInstruction::InitializeAccount2
174 | PodTokenInstruction::InitializeAccount3
175 | PodTokenInstruction::InitializePermanentDelegate => {
176 let _ = decode_instruction_data::<Pubkey>(input)?;
177 }
178 PodTokenInstruction::InitializeMultisig
179 | PodTokenInstruction::InitializeMultisig2 => {
180 let _ = decode_instruction_data::<InitializeMultisigData>(input)?;
181 }
182 PodTokenInstruction::SetAuthority => {
183 let _ = decode_instruction_data_with_coption_pubkey::<SetAuthorityData>(input)?;
184 }
185 PodTokenInstruction::Transfer
186 | PodTokenInstruction::Approve
187 | PodTokenInstruction::MintTo
188 | PodTokenInstruction::Burn
189 | PodTokenInstruction::AmountToUiAmount => {
190 let _ = decode_instruction_data::<AmountData>(input)?;
191 }
192 PodTokenInstruction::TransferChecked
193 | PodTokenInstruction::ApproveChecked
194 | PodTokenInstruction::MintToChecked
195 | PodTokenInstruction::BurnChecked => {
196 let _ = decode_instruction_data::<AmountCheckedData>(input)?;
197 }
198 PodTokenInstruction::InitializeMintCloseAuthority => {
199 let _ = decode_instruction_data_with_coption_pubkey::<()>(input)?;
200 }
201 PodTokenInstruction::UiAmountToAmount => {
202 let _ = std::str::from_utf8(&input[1..])
203 .map_err(|_| ProgramError::InvalidInstructionData)?;
204 }
205 PodTokenInstruction::GetAccountDataSize | PodTokenInstruction::Reallocate => {
206 let _ = input[1..]
207 .chunks(std::mem::size_of::<ExtensionType>())
208 .map(ExtensionType::try_from)
209 .collect::<Result<Vec<_>, _>>()?;
210 }
211 _ => {
212 }
214 }
215 }
216 Ok(())
217 }
218
219 proptest! {
220 #![proptest_config(ProptestConfig::with_cases(1024))]
221 #[test]
222 fn test_instruction_unpack_proptest(
223 data in prop::collection::vec(any::<u8>(), 0..255)
224 ) {
225 let _no_panic = check_pod_instruction(&data);
226 }
227 }
228
229 #[test]
230 fn test_initialize_mint_packing() {
231 let decimals = 2;
232 let mint_authority = Pubkey::new_from_array([1u8; 32]);
233 let freeze_authority = COption::None;
234 let check = TokenInstruction::InitializeMint {
235 decimals,
236 mint_authority,
237 freeze_authority,
238 };
239 let packed = check.pack();
240 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
241 assert_eq!(instruction_type, PodTokenInstruction::InitializeMint);
242 let (pod, pod_freeze_authority) =
243 decode_instruction_data_with_coption_pubkey::<InitializeMintData>(&packed).unwrap();
244 assert_eq!(pod.decimals, decimals);
245 assert_eq!(pod.mint_authority, mint_authority);
246 assert_eq!(pod_freeze_authority, freeze_authority.into());
247
248 let mint_authority = Pubkey::new_from_array([2u8; 32]);
249 let freeze_authority = COption::Some(Pubkey::new_from_array([3u8; 32]));
250 let check = TokenInstruction::InitializeMint {
251 decimals,
252 mint_authority,
253 freeze_authority,
254 };
255 let packed = check.pack();
256
257 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
258 assert_eq!(instruction_type, PodTokenInstruction::InitializeMint);
259 let (pod, pod_freeze_authority) =
260 decode_instruction_data_with_coption_pubkey::<InitializeMintData>(&packed).unwrap();
261 assert_eq!(pod.decimals, decimals);
262 assert_eq!(pod.mint_authority, mint_authority);
263 assert_eq!(pod_freeze_authority, freeze_authority.into());
264 }
265
266 #[test]
267 fn test_initialize_account_packing() {
268 let check = TokenInstruction::InitializeAccount;
269 let packed = check.pack();
270 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
271 assert_eq!(instruction_type, PodTokenInstruction::InitializeAccount);
272 }
273
274 #[test]
275 fn test_initialize_multisig_packing() {
276 let m = 1;
277 let check = TokenInstruction::InitializeMultisig { m };
278 let packed = check.pack();
279 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
280 assert_eq!(instruction_type, PodTokenInstruction::InitializeMultisig);
281 let pod = decode_instruction_data::<InitializeMultisigData>(&packed).unwrap();
282 assert_eq!(pod.m, m);
283 }
284
285 #[test]
286 fn test_transfer_packing() {
287 let amount = 1;
288 #[allow(deprecated)]
289 let check = TokenInstruction::Transfer { amount };
290 let packed = check.pack();
291
292 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
293 assert_eq!(instruction_type, PodTokenInstruction::Transfer);
294 let pod = decode_instruction_data::<AmountData>(&packed).unwrap();
295 assert_eq!(pod.amount, amount.into());
296 }
297
298 #[test]
299 fn test_approve_packing() {
300 let amount = 1;
301 let check = TokenInstruction::Approve { amount };
302 let packed = check.pack();
303
304 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
305 assert_eq!(instruction_type, PodTokenInstruction::Approve);
306 let pod = decode_instruction_data::<AmountData>(&packed).unwrap();
307 assert_eq!(pod.amount, amount.into());
308 }
309
310 #[test]
311 fn test_revoke_packing() {
312 let check = TokenInstruction::Revoke;
313 let packed = check.pack();
314 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
315 assert_eq!(instruction_type, PodTokenInstruction::Revoke);
316 }
317
318 #[test]
319 fn test_set_authority_packing() {
320 let authority_type = AuthorityType::FreezeAccount;
321 let new_authority = COption::Some(Pubkey::new_from_array([4u8; 32]));
322 let check = TokenInstruction::SetAuthority {
323 authority_type: authority_type.clone(),
324 new_authority,
325 };
326 let packed = check.pack();
327
328 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
329 assert_eq!(instruction_type, PodTokenInstruction::SetAuthority);
330 let (pod, pod_new_authority) =
331 decode_instruction_data_with_coption_pubkey::<SetAuthorityData>(&packed).unwrap();
332 assert_eq!(
333 AuthorityType::from(pod.authority_type).unwrap(),
334 authority_type
335 );
336 assert_eq!(pod_new_authority, new_authority.into());
337 }
338
339 #[test]
340 fn test_mint_to_packing() {
341 let amount = 1;
342 let check = TokenInstruction::MintTo { amount };
343 let packed = check.pack();
344
345 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
346 assert_eq!(instruction_type, PodTokenInstruction::MintTo);
347 let pod = decode_instruction_data::<AmountData>(&packed).unwrap();
348 assert_eq!(pod.amount, amount.into());
349 }
350
351 #[test]
352 fn test_burn_packing() {
353 let amount = 1;
354 let check = TokenInstruction::Burn { amount };
355 let packed = check.pack();
356
357 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
358 assert_eq!(instruction_type, PodTokenInstruction::Burn);
359 let pod = decode_instruction_data::<AmountData>(&packed).unwrap();
360 assert_eq!(pod.amount, amount.into());
361 }
362
363 #[test]
364 fn test_close_account_packing() {
365 let check = TokenInstruction::CloseAccount;
366 let packed = check.pack();
367 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
368 assert_eq!(instruction_type, PodTokenInstruction::CloseAccount);
369 }
370
371 #[test]
372 fn test_freeze_account_packing() {
373 let check = TokenInstruction::FreezeAccount;
374 let packed = check.pack();
375 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
376 assert_eq!(instruction_type, PodTokenInstruction::FreezeAccount);
377 }
378
379 #[test]
380 fn test_thaw_account_packing() {
381 let check = TokenInstruction::ThawAccount;
382 let packed = check.pack();
383 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
384 assert_eq!(instruction_type, PodTokenInstruction::ThawAccount);
385 }
386
387 #[test]
388 fn test_transfer_checked_packing() {
389 let amount = 1;
390 let decimals = 2;
391 let check = TokenInstruction::TransferChecked { amount, decimals };
392 let packed = check.pack();
393
394 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
395 assert_eq!(instruction_type, PodTokenInstruction::TransferChecked);
396 let pod = decode_instruction_data::<AmountCheckedData>(&packed).unwrap();
397 assert_eq!(pod.amount, amount.into());
398 assert_eq!(pod.decimals, decimals);
399 }
400
401 #[test]
402 fn test_approve_checked_packing() {
403 let amount = 1;
404 let decimals = 2;
405
406 let check = TokenInstruction::ApproveChecked { amount, decimals };
407 let packed = check.pack();
408
409 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
410 assert_eq!(instruction_type, PodTokenInstruction::ApproveChecked);
411 let pod = decode_instruction_data::<AmountCheckedData>(&packed).unwrap();
412 assert_eq!(pod.amount, amount.into());
413 assert_eq!(pod.decimals, decimals);
414 }
415
416 #[test]
417 fn test_mint_to_checked_packing() {
418 let amount = 1;
419 let decimals = 2;
420 let check = TokenInstruction::MintToChecked { amount, decimals };
421 let packed = check.pack();
422 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
423 assert_eq!(instruction_type, PodTokenInstruction::MintToChecked);
424 let pod = decode_instruction_data::<AmountCheckedData>(&packed).unwrap();
425 assert_eq!(pod.amount, amount.into());
426 assert_eq!(pod.decimals, decimals);
427 }
428
429 #[test]
430 fn test_burn_checked_packing() {
431 let amount = 1;
432 let decimals = 2;
433 let check = TokenInstruction::BurnChecked { amount, decimals };
434 let packed = check.pack();
435
436 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
437 assert_eq!(instruction_type, PodTokenInstruction::BurnChecked);
438 let pod = decode_instruction_data::<AmountCheckedData>(&packed).unwrap();
439 assert_eq!(pod.amount, amount.into());
440 assert_eq!(pod.decimals, decimals);
441 }
442
443 #[test]
444 fn test_initialize_account2_packing() {
445 let owner = Pubkey::new_from_array([2u8; 32]);
446 let check = TokenInstruction::InitializeAccount2 { owner };
447 let packed = check.pack();
448
449 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
450 assert_eq!(instruction_type, PodTokenInstruction::InitializeAccount2);
451 let pod_owner = decode_instruction_data::<Pubkey>(&packed).unwrap();
452 assert_eq!(*pod_owner, owner);
453 }
454
455 #[test]
456 fn test_sync_native_packing() {
457 let check = TokenInstruction::SyncNative;
458 let packed = check.pack();
459
460 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
461 assert_eq!(instruction_type, PodTokenInstruction::SyncNative);
462 }
463
464 #[test]
465 fn test_initialize_account3_packing() {
466 let owner = Pubkey::new_from_array([2u8; 32]);
467 let check = TokenInstruction::InitializeAccount3 { owner };
468 let packed = check.pack();
469
470 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
471 assert_eq!(instruction_type, PodTokenInstruction::InitializeAccount3);
472 let pod_owner = decode_instruction_data::<Pubkey>(&packed).unwrap();
473 assert_eq!(*pod_owner, owner);
474 }
475
476 #[test]
477 fn test_initialize_multisig2_packing() {
478 let m = 1;
479 let check = TokenInstruction::InitializeMultisig2 { m };
480 let packed = check.pack();
481
482 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
483 assert_eq!(instruction_type, PodTokenInstruction::InitializeMultisig2);
484 let pod = decode_instruction_data::<InitializeMultisigData>(&packed).unwrap();
485 assert_eq!(pod.m, m);
486 }
487
488 #[test]
489 fn test_initialize_mint2_packing() {
490 let decimals = 2;
491 let mint_authority = Pubkey::new_from_array([1u8; 32]);
492 let freeze_authority = COption::None;
493 let check = TokenInstruction::InitializeMint2 {
494 decimals,
495 mint_authority,
496 freeze_authority,
497 };
498 let packed = check.pack();
499
500 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
501 assert_eq!(instruction_type, PodTokenInstruction::InitializeMint2);
502 let (pod, pod_freeze_authority) =
503 decode_instruction_data_with_coption_pubkey::<InitializeMintData>(&packed).unwrap();
504 assert_eq!(pod.decimals, decimals);
505 assert_eq!(pod.mint_authority, mint_authority);
506 assert_eq!(pod_freeze_authority, freeze_authority.into());
507
508 let decimals = 2;
509 let mint_authority = Pubkey::new_from_array([2u8; 32]);
510 let freeze_authority = COption::Some(Pubkey::new_from_array([3u8; 32]));
511 let check = TokenInstruction::InitializeMint2 {
512 decimals,
513 mint_authority,
514 freeze_authority,
515 };
516 let packed = check.pack();
517
518 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
519 assert_eq!(instruction_type, PodTokenInstruction::InitializeMint2);
520 let (pod, pod_freeze_authority) =
521 decode_instruction_data_with_coption_pubkey::<InitializeMintData>(&packed).unwrap();
522 assert_eq!(pod.decimals, decimals);
523 assert_eq!(pod.mint_authority, mint_authority);
524 assert_eq!(pod_freeze_authority, freeze_authority.into());
525 }
526
527 #[test]
528 fn test_get_account_data_size_packing() {
529 let extension_types = vec![];
530 let check = TokenInstruction::GetAccountDataSize {
531 extension_types: extension_types.clone(),
532 };
533 let packed = check.pack();
534
535 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
536 assert_eq!(instruction_type, PodTokenInstruction::GetAccountDataSize);
537 let pod_extension_types = packed[1..]
538 .chunks(std::mem::size_of::<ExtensionType>())
539 .map(ExtensionType::try_from)
540 .collect::<Result<Vec<_>, _>>()
541 .unwrap();
542 assert_eq!(pod_extension_types, extension_types);
543
544 let extension_types = vec![
545 ExtensionType::TransferFeeConfig,
546 ExtensionType::TransferFeeAmount,
547 ];
548 let check = TokenInstruction::GetAccountDataSize {
549 extension_types: extension_types.clone(),
550 };
551 let packed = check.pack();
552
553 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
554 assert_eq!(instruction_type, PodTokenInstruction::GetAccountDataSize);
555 let pod_extension_types = packed[1..]
556 .chunks(std::mem::size_of::<ExtensionType>())
557 .map(ExtensionType::try_from)
558 .collect::<Result<Vec<_>, _>>()
559 .unwrap();
560 assert_eq!(pod_extension_types, extension_types);
561 }
562
563 #[test]
564 fn test_amount_to_ui_amount_packing() {
565 let amount = 42;
566 let check = TokenInstruction::AmountToUiAmount { amount };
567 let packed = check.pack();
568
569 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
570 assert_eq!(instruction_type, PodTokenInstruction::AmountToUiAmount);
571 let data = decode_instruction_data::<AmountData>(&packed).unwrap();
572 assert_eq!(data.amount, amount.into());
573 }
574
575 #[test]
576 fn test_ui_amount_to_amount_packing() {
577 let ui_amount = "0.42";
578 let check = TokenInstruction::UiAmountToAmount { ui_amount };
579 let packed = check.pack();
580
581 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
582 assert_eq!(instruction_type, PodTokenInstruction::UiAmountToAmount);
583 let pod_ui_amount = std::str::from_utf8(&packed[1..]).unwrap();
584 assert_eq!(pod_ui_amount, ui_amount);
585 }
586
587 #[test]
588 fn test_initialize_mint_close_authority_packing() {
589 let close_authority = COption::Some(Pubkey::new_from_array([10u8; 32]));
590 let check = TokenInstruction::InitializeMintCloseAuthority { close_authority };
591 let packed = check.pack();
592
593 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
594 assert_eq!(
595 instruction_type,
596 PodTokenInstruction::InitializeMintCloseAuthority
597 );
598 let (_, pod_close_authority) =
599 decode_instruction_data_with_coption_pubkey::<()>(&packed).unwrap();
600 assert_eq!(pod_close_authority, close_authority.into());
601 }
602
603 #[test]
604 fn test_create_native_mint_packing() {
605 let check = TokenInstruction::CreateNativeMint;
606 let packed = check.pack();
607
608 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
609 assert_eq!(instruction_type, PodTokenInstruction::CreateNativeMint);
610 }
611
612 #[test]
613 fn test_initialize_permanent_delegate_packing() {
614 let delegate = Pubkey::new_from_array([11u8; 32]);
615 let check = TokenInstruction::InitializePermanentDelegate { delegate };
616 let packed = check.pack();
617
618 let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
619 assert_eq!(
620 instruction_type,
621 PodTokenInstruction::InitializePermanentDelegate
622 );
623 let pod_delegate = decode_instruction_data::<Pubkey>(&packed).unwrap();
624 assert_eq!(*pod_delegate, delegate);
625 }
626}