use {
crate::pod::PodCOption,
bytemuck::{Pod, Zeroable},
num_enum::{IntoPrimitive, TryFromPrimitive},
solana_program::{
program_error::ProgramError,
pubkey::{Pubkey, PUBKEY_BYTES},
},
spl_pod::{
bytemuck::{pod_from_bytes, pod_get_packed_len},
primitives::PodU64,
},
};
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub(crate) struct InitializeMintData {
pub(crate) decimals: u8,
pub(crate) mint_authority: Pubkey,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub(crate) struct InitializeMultisigData {
pub(crate) m: u8,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub(crate) struct AmountData {
pub(crate) amount: PodU64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub(crate) struct AmountCheckedData {
pub(crate) amount: PodU64,
pub(crate) decimals: u8,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub(crate) struct SetAuthorityData {
pub(crate) authority_type: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub(crate) enum PodTokenInstruction {
InitializeMint, InitializeAccount,
InitializeMultisig, Transfer, Approve, Revoke,
SetAuthority, MintTo, Burn, CloseAccount,
FreezeAccount,
ThawAccount,
TransferChecked, ApproveChecked, MintToChecked, BurnChecked, InitializeAccount2, SyncNative,
InitializeAccount3, InitializeMultisig2, InitializeMint2, GetAccountDataSize, InitializeImmutableOwner,
AmountToUiAmount, UiAmountToAmount, InitializeMintCloseAuthority, TransferFeeExtension,
ConfidentialTransferExtension,
DefaultAccountStateExtension,
Reallocate, MemoTransferExtension,
CreateNativeMint,
InitializeNonTransferableMint,
InterestBearingMintExtension,
CpiGuardExtension,
InitializePermanentDelegate, TransferHookExtension,
ConfidentialTransferFeeExtension,
WithdrawExcessLamports,
MetadataPointerExtension,
GroupPointerExtension,
GroupMemberPointerExtension,
}
fn unpack_pubkey_option(input: &[u8]) -> Result<PodCOption<Pubkey>, ProgramError> {
match input.split_first() {
Option::Some((&0, _)) => Ok(PodCOption::none()),
Option::Some((&1, rest)) => {
let pk = rest
.get(..PUBKEY_BYTES)
.and_then(|x| Pubkey::try_from(x).ok())
.ok_or(ProgramError::InvalidInstructionData)?;
Ok(PodCOption::some(pk))
}
_ => Err(ProgramError::InvalidInstructionData),
}
}
pub(crate) fn decode_instruction_data_with_coption_pubkey<T: Pod>(
input_with_type: &[u8],
) -> Result<(&T, PodCOption<Pubkey>), ProgramError> {
let end_of_t = pod_get_packed_len::<T>().saturating_add(1);
let value = input_with_type
.get(1..end_of_t)
.ok_or(ProgramError::InvalidInstructionData)
.and_then(pod_from_bytes)?;
let pubkey = unpack_pubkey_option(&input_with_type[end_of_t..])?;
Ok((value, pubkey))
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
extension::ExtensionType,
instruction::{decode_instruction_data, decode_instruction_type},
},
proptest::prelude::*,
};
fn check_pod_instruction(input: &[u8]) -> Result<(), ProgramError> {
if let Ok(instruction_type) = decode_instruction_type(input) {
match instruction_type {
PodTokenInstruction::InitializeMint | PodTokenInstruction::InitializeMint2 => {
let _ =
decode_instruction_data_with_coption_pubkey::<InitializeMintData>(input)?;
}
PodTokenInstruction::InitializeAccount2
| PodTokenInstruction::InitializeAccount3
| PodTokenInstruction::InitializePermanentDelegate => {
let _ = decode_instruction_data::<Pubkey>(input)?;
}
PodTokenInstruction::InitializeMultisig
| PodTokenInstruction::InitializeMultisig2 => {
let _ = decode_instruction_data::<InitializeMultisigData>(input)?;
}
PodTokenInstruction::SetAuthority => {
let _ = decode_instruction_data_with_coption_pubkey::<SetAuthorityData>(input)?;
}
PodTokenInstruction::Transfer
| PodTokenInstruction::Approve
| PodTokenInstruction::MintTo
| PodTokenInstruction::Burn
| PodTokenInstruction::AmountToUiAmount => {
let _ = decode_instruction_data::<AmountData>(input)?;
}
PodTokenInstruction::TransferChecked
| PodTokenInstruction::ApproveChecked
| PodTokenInstruction::MintToChecked
| PodTokenInstruction::BurnChecked => {
let _ = decode_instruction_data::<AmountCheckedData>(input)?;
}
PodTokenInstruction::InitializeMintCloseAuthority => {
let _ = decode_instruction_data_with_coption_pubkey::<()>(input)?;
}
PodTokenInstruction::UiAmountToAmount => {
let _ = std::str::from_utf8(&input[1..])
.map_err(|_| ProgramError::InvalidInstructionData)?;
}
PodTokenInstruction::GetAccountDataSize | PodTokenInstruction::Reallocate => {
let _ = input[1..]
.chunks(std::mem::size_of::<ExtensionType>())
.map(ExtensionType::try_from)
.collect::<Result<Vec<_>, _>>()?;
}
_ => {
}
}
}
Ok(())
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1024))]
#[test]
fn test_instruction_unpack_proptest(
data in prop::collection::vec(any::<u8>(), 0..255)
) {
let _no_panic = check_pod_instruction(&data);
}
}
}