#![allow(deprecated)]
#[cfg(feature = "serde-traits")]
use {
crate::serialization::coption_fromstr,
serde::{Deserialize, Serialize},
serde_with::{As, DisplayFromStr},
};
use {
crate::{
check_program_account, check_spl_token_program_account, error::TokenError,
extension::ExtensionType,
},
bytemuck::Pod,
solana_program::{
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
program_option::COption,
pubkey::{Pubkey, PUBKEY_BYTES},
system_program, sysvar,
},
spl_pod::bytemuck::{pod_from_bytes, pod_get_packed_len},
std::{
convert::{TryFrom, TryInto},
mem::size_of,
},
};
pub const MIN_SIGNERS: usize = 1;
pub const MAX_SIGNERS: usize = 11;
const U16_BYTES: usize = 2;
const U64_BYTES: usize = 8;
#[repr(C)]
#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde-traits",
serde(rename_all_fields = "camelCase", rename_all = "camelCase")
)]
#[derive(Clone, Debug, PartialEq)]
pub enum TokenInstruction<'a> {
InitializeMint {
decimals: u8,
#[cfg_attr(feature = "serde-traits", serde(with = "As::<DisplayFromStr>"))]
mint_authority: Pubkey,
#[cfg_attr(feature = "serde-traits", serde(with = "coption_fromstr"))]
freeze_authority: COption<Pubkey>,
},
InitializeAccount,
InitializeMultisig {
m: u8,
},
#[deprecated(
since = "4.0.0",
note = "please use `TransferChecked` or `TransferCheckedWithFee` instead"
)]
Transfer {
amount: u64,
},
Approve {
amount: u64,
},
Revoke,
SetAuthority {
authority_type: AuthorityType,
#[cfg_attr(feature = "serde-traits", serde(with = "coption_fromstr"))]
new_authority: COption<Pubkey>,
},
MintTo {
amount: u64,
},
Burn {
amount: u64,
},
CloseAccount,
FreezeAccount,
ThawAccount,
TransferChecked {
amount: u64,
decimals: u8,
},
ApproveChecked {
amount: u64,
decimals: u8,
},
MintToChecked {
amount: u64,
decimals: u8,
},
BurnChecked {
amount: u64,
decimals: u8,
},
InitializeAccount2 {
#[cfg_attr(feature = "serde-traits", serde(with = "As::<DisplayFromStr>"))]
owner: Pubkey,
},
SyncNative,
InitializeAccount3 {
#[cfg_attr(feature = "serde-traits", serde(with = "As::<DisplayFromStr>"))]
owner: Pubkey,
},
InitializeMultisig2 {
m: u8,
},
InitializeMint2 {
decimals: u8,
#[cfg_attr(feature = "serde-traits", serde(with = "As::<DisplayFromStr>"))]
mint_authority: Pubkey,
#[cfg_attr(feature = "serde-traits", serde(with = "coption_fromstr"))]
freeze_authority: COption<Pubkey>,
},
GetAccountDataSize {
extension_types: Vec<ExtensionType>,
},
InitializeImmutableOwner,
AmountToUiAmount {
amount: u64,
},
UiAmountToAmount {
ui_amount: &'a str,
},
InitializeMintCloseAuthority {
#[cfg_attr(feature = "serde-traits", serde(with = "coption_fromstr"))]
close_authority: COption<Pubkey>,
},
TransferFeeExtension,
ConfidentialTransferExtension,
DefaultAccountStateExtension,
Reallocate {
extension_types: Vec<ExtensionType>,
},
MemoTransferExtension,
CreateNativeMint,
InitializeNonTransferableMint,
InterestBearingMintExtension,
CpiGuardExtension,
InitializePermanentDelegate {
#[cfg_attr(feature = "serde-traits", serde(with = "As::<DisplayFromStr>"))]
delegate: Pubkey,
},
TransferHookExtension,
ConfidentialTransferFeeExtension,
WithdrawExcessLamports,
MetadataPointerExtension,
GroupPointerExtension,
GroupMemberPointerExtension,
}
impl<'a> TokenInstruction<'a> {
pub fn unpack(input: &'a [u8]) -> Result<Self, ProgramError> {
use TokenError::InvalidInstruction;
let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?;
Ok(match tag {
0 => {
let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?;
let (mint_authority, rest) = Self::unpack_pubkey(rest)?;
let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?;
Self::InitializeMint {
mint_authority,
freeze_authority,
decimals,
}
}
1 => Self::InitializeAccount,
2 => {
let &m = rest.first().ok_or(InvalidInstruction)?;
Self::InitializeMultisig { m }
}
3 | 4 | 7 | 8 => {
let amount = rest
.get(..U64_BYTES)
.and_then(|slice| slice.try_into().ok())
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
match tag {
#[allow(deprecated)]
3 => Self::Transfer { amount },
4 => Self::Approve { amount },
7 => Self::MintTo { amount },
8 => Self::Burn { amount },
_ => unreachable!(),
}
}
5 => Self::Revoke,
6 => {
let (authority_type, rest) = rest
.split_first()
.ok_or_else(|| ProgramError::from(InvalidInstruction))
.and_then(|(&t, rest)| Ok((AuthorityType::from(t)?, rest)))?;
let (new_authority, _rest) = Self::unpack_pubkey_option(rest)?;
Self::SetAuthority {
authority_type,
new_authority,
}
}
9 => Self::CloseAccount,
10 => Self::FreezeAccount,
11 => Self::ThawAccount,
12 => {
let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
Self::TransferChecked { amount, decimals }
}
13 => {
let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
Self::ApproveChecked { amount, decimals }
}
14 => {
let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
Self::MintToChecked { amount, decimals }
}
15 => {
let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
Self::BurnChecked { amount, decimals }
}
16 => {
let (owner, _rest) = Self::unpack_pubkey(rest)?;
Self::InitializeAccount2 { owner }
}
17 => Self::SyncNative,
18 => {
let (owner, _rest) = Self::unpack_pubkey(rest)?;
Self::InitializeAccount3 { owner }
}
19 => {
let &m = rest.first().ok_or(InvalidInstruction)?;
Self::InitializeMultisig2 { m }
}
20 => {
let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?;
let (mint_authority, rest) = Self::unpack_pubkey(rest)?;
let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?;
Self::InitializeMint2 {
mint_authority,
freeze_authority,
decimals,
}
}
21 => {
let mut extension_types = vec![];
for chunk in rest.chunks(size_of::<ExtensionType>()) {
extension_types.push(chunk.try_into()?);
}
Self::GetAccountDataSize { extension_types }
}
22 => Self::InitializeImmutableOwner,
23 => {
let (amount, _rest) = Self::unpack_u64(rest)?;
Self::AmountToUiAmount { amount }
}
24 => {
let ui_amount = std::str::from_utf8(rest).map_err(|_| InvalidInstruction)?;
Self::UiAmountToAmount { ui_amount }
}
25 => {
let (close_authority, _rest) = Self::unpack_pubkey_option(rest)?;
Self::InitializeMintCloseAuthority { close_authority }
}
26 => Self::TransferFeeExtension,
27 => Self::ConfidentialTransferExtension,
28 => Self::DefaultAccountStateExtension,
29 => {
let mut extension_types = vec![];
for chunk in rest.chunks(size_of::<ExtensionType>()) {
extension_types.push(chunk.try_into()?);
}
Self::Reallocate { extension_types }
}
30 => Self::MemoTransferExtension,
31 => Self::CreateNativeMint,
32 => Self::InitializeNonTransferableMint,
33 => Self::InterestBearingMintExtension,
34 => Self::CpiGuardExtension,
35 => {
let (delegate, _rest) = Self::unpack_pubkey(rest)?;
Self::InitializePermanentDelegate { delegate }
}
36 => Self::TransferHookExtension,
37 => Self::ConfidentialTransferFeeExtension,
38 => Self::WithdrawExcessLamports,
39 => Self::MetadataPointerExtension,
40 => Self::GroupPointerExtension,
41 => Self::GroupMemberPointerExtension,
_ => return Err(TokenError::InvalidInstruction.into()),
})
}
pub fn pack(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(size_of::<Self>());
match self {
&Self::InitializeMint {
ref mint_authority,
ref freeze_authority,
decimals,
} => {
buf.push(0);
buf.push(decimals);
buf.extend_from_slice(mint_authority.as_ref());
Self::pack_pubkey_option(freeze_authority, &mut buf);
}
Self::InitializeAccount => buf.push(1),
&Self::InitializeMultisig { m } => {
buf.push(2);
buf.push(m);
}
#[allow(deprecated)]
&Self::Transfer { amount } => {
buf.push(3);
buf.extend_from_slice(&amount.to_le_bytes());
}
&Self::Approve { amount } => {
buf.push(4);
buf.extend_from_slice(&amount.to_le_bytes());
}
&Self::MintTo { amount } => {
buf.push(7);
buf.extend_from_slice(&amount.to_le_bytes());
}
&Self::Burn { amount } => {
buf.push(8);
buf.extend_from_slice(&amount.to_le_bytes());
}
Self::Revoke => buf.push(5),
Self::SetAuthority {
authority_type,
ref new_authority,
} => {
buf.push(6);
buf.push(authority_type.into());
Self::pack_pubkey_option(new_authority, &mut buf);
}
Self::CloseAccount => buf.push(9),
Self::FreezeAccount => buf.push(10),
Self::ThawAccount => buf.push(11),
&Self::TransferChecked { amount, decimals } => {
buf.push(12);
buf.extend_from_slice(&amount.to_le_bytes());
buf.push(decimals);
}
&Self::ApproveChecked { amount, decimals } => {
buf.push(13);
buf.extend_from_slice(&amount.to_le_bytes());
buf.push(decimals);
}
&Self::MintToChecked { amount, decimals } => {
buf.push(14);
buf.extend_from_slice(&amount.to_le_bytes());
buf.push(decimals);
}
&Self::BurnChecked { amount, decimals } => {
buf.push(15);
buf.extend_from_slice(&amount.to_le_bytes());
buf.push(decimals);
}
&Self::InitializeAccount2 { owner } => {
buf.push(16);
buf.extend_from_slice(owner.as_ref());
}
&Self::SyncNative => {
buf.push(17);
}
&Self::InitializeAccount3 { owner } => {
buf.push(18);
buf.extend_from_slice(owner.as_ref());
}
&Self::InitializeMultisig2 { m } => {
buf.push(19);
buf.push(m);
}
&Self::InitializeMint2 {
ref mint_authority,
ref freeze_authority,
decimals,
} => {
buf.push(20);
buf.push(decimals);
buf.extend_from_slice(mint_authority.as_ref());
Self::pack_pubkey_option(freeze_authority, &mut buf);
}
Self::GetAccountDataSize { extension_types } => {
buf.push(21);
for extension_type in extension_types {
buf.extend_from_slice(&<[u8; 2]>::from(*extension_type));
}
}
&Self::InitializeImmutableOwner => {
buf.push(22);
}
&Self::AmountToUiAmount { amount } => {
buf.push(23);
buf.extend_from_slice(&amount.to_le_bytes());
}
Self::UiAmountToAmount { ui_amount } => {
buf.push(24);
buf.extend_from_slice(ui_amount.as_bytes());
}
Self::InitializeMintCloseAuthority { close_authority } => {
buf.push(25);
Self::pack_pubkey_option(close_authority, &mut buf);
}
Self::TransferFeeExtension => {
buf.push(26);
}
&Self::ConfidentialTransferExtension => {
buf.push(27);
}
&Self::DefaultAccountStateExtension => {
buf.push(28);
}
Self::Reallocate { extension_types } => {
buf.push(29);
for extension_type in extension_types {
buf.extend_from_slice(&<[u8; 2]>::from(*extension_type));
}
}
&Self::MemoTransferExtension => {
buf.push(30);
}
&Self::CreateNativeMint => {
buf.push(31);
}
&Self::InitializeNonTransferableMint => {
buf.push(32);
}
&Self::InterestBearingMintExtension => {
buf.push(33);
}
&Self::CpiGuardExtension => {
buf.push(34);
}
Self::InitializePermanentDelegate { delegate } => {
buf.push(35);
buf.extend_from_slice(delegate.as_ref());
}
&Self::TransferHookExtension => {
buf.push(36);
}
&Self::ConfidentialTransferFeeExtension => {
buf.push(37);
}
&Self::WithdrawExcessLamports => {
buf.push(38);
}
&Self::MetadataPointerExtension => {
buf.push(39);
}
&Self::GroupPointerExtension => {
buf.push(40);
}
&Self::GroupMemberPointerExtension => {
buf.push(41);
}
};
buf
}
pub(crate) fn unpack_pubkey(input: &[u8]) -> Result<(Pubkey, &[u8]), ProgramError> {
let pk = input
.get(..PUBKEY_BYTES)
.and_then(|x| Pubkey::try_from(x).ok())
.ok_or(TokenError::InvalidInstruction)?;
Ok((pk, &input[PUBKEY_BYTES..]))
}
pub(crate) fn unpack_pubkey_option(
input: &[u8],
) -> Result<(COption<Pubkey>, &[u8]), ProgramError> {
match input.split_first() {
Option::Some((&0, rest)) => Ok((COption::None, rest)),
Option::Some((&1, rest)) => {
let (pk, rest) = Self::unpack_pubkey(rest)?;
Ok((COption::Some(pk), rest))
}
_ => Err(TokenError::InvalidInstruction.into()),
}
}
pub(crate) fn pack_pubkey_option(value: &COption<Pubkey>, buf: &mut Vec<u8>) {
match *value {
COption::Some(ref key) => {
buf.push(1);
buf.extend_from_slice(&key.to_bytes());
}
COption::None => buf.push(0),
}
}
pub(crate) fn unpack_u16(input: &[u8]) -> Result<(u16, &[u8]), ProgramError> {
let value = input
.get(..U16_BYTES)
.and_then(|slice| slice.try_into().ok())
.map(u16::from_le_bytes)
.ok_or(TokenError::InvalidInstruction)?;
Ok((value, &input[U16_BYTES..]))
}
pub(crate) fn unpack_u64(input: &[u8]) -> Result<(u64, &[u8]), ProgramError> {
let value = input
.get(..U64_BYTES)
.and_then(|slice| slice.try_into().ok())
.map(u64::from_le_bytes)
.ok_or(TokenError::InvalidInstruction)?;
Ok((value, &input[U64_BYTES..]))
}
pub(crate) fn unpack_amount_decimals(input: &[u8]) -> Result<(u64, u8, &[u8]), ProgramError> {
let (amount, rest) = Self::unpack_u64(input)?;
let (&decimals, rest) = rest.split_first().ok_or(TokenError::InvalidInstruction)?;
Ok((amount, decimals, rest))
}
}
#[repr(u8)]
#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
#[derive(Clone, Debug, PartialEq)]
pub enum AuthorityType {
MintTokens,
FreezeAccount,
AccountOwner,
CloseAccount,
TransferFeeConfig,
WithheldWithdraw,
CloseMint,
InterestRate,
PermanentDelegate,
ConfidentialTransferMint,
TransferHookProgramId,
ConfidentialTransferFeeConfig,
MetadataPointer,
GroupPointer,
GroupMemberPointer,
}
impl AuthorityType {
fn into(&self) -> u8 {
match self {
AuthorityType::MintTokens => 0,
AuthorityType::FreezeAccount => 1,
AuthorityType::AccountOwner => 2,
AuthorityType::CloseAccount => 3,
AuthorityType::TransferFeeConfig => 4,
AuthorityType::WithheldWithdraw => 5,
AuthorityType::CloseMint => 6,
AuthorityType::InterestRate => 7,
AuthorityType::PermanentDelegate => 8,
AuthorityType::ConfidentialTransferMint => 9,
AuthorityType::TransferHookProgramId => 10,
AuthorityType::ConfidentialTransferFeeConfig => 11,
AuthorityType::MetadataPointer => 12,
AuthorityType::GroupPointer => 13,
AuthorityType::GroupMemberPointer => 14,
}
}
pub(crate) fn from(index: u8) -> Result<Self, ProgramError> {
match index {
0 => Ok(AuthorityType::MintTokens),
1 => Ok(AuthorityType::FreezeAccount),
2 => Ok(AuthorityType::AccountOwner),
3 => Ok(AuthorityType::CloseAccount),
4 => Ok(AuthorityType::TransferFeeConfig),
5 => Ok(AuthorityType::WithheldWithdraw),
6 => Ok(AuthorityType::CloseMint),
7 => Ok(AuthorityType::InterestRate),
8 => Ok(AuthorityType::PermanentDelegate),
9 => Ok(AuthorityType::ConfidentialTransferMint),
10 => Ok(AuthorityType::TransferHookProgramId),
11 => Ok(AuthorityType::ConfidentialTransferFeeConfig),
12 => Ok(AuthorityType::MetadataPointer),
13 => Ok(AuthorityType::GroupPointer),
14 => Ok(AuthorityType::GroupMemberPointer),
_ => Err(TokenError::InvalidInstruction.into()),
}
}
}
pub fn initialize_mint(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
mint_authority_pubkey: &Pubkey,
freeze_authority_pubkey: Option<&Pubkey>,
decimals: u8,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let freeze_authority = freeze_authority_pubkey.cloned().into();
let data = TokenInstruction::InitializeMint {
mint_authority: *mint_authority_pubkey,
freeze_authority,
decimals,
}
.pack();
let accounts = vec![
AccountMeta::new(*mint_pubkey, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
];
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn initialize_mint2(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
mint_authority_pubkey: &Pubkey,
freeze_authority_pubkey: Option<&Pubkey>,
decimals: u8,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let freeze_authority = freeze_authority_pubkey.cloned().into();
let data = TokenInstruction::InitializeMint2 {
mint_authority: *mint_authority_pubkey,
freeze_authority,
decimals,
}
.pack();
let accounts = vec![AccountMeta::new(*mint_pubkey, false)];
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn initialize_account(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::InitializeAccount.pack();
let accounts = vec![
AccountMeta::new(*account_pubkey, false),
AccountMeta::new_readonly(*mint_pubkey, false),
AccountMeta::new_readonly(*owner_pubkey, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
];
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn initialize_account2(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::InitializeAccount2 {
owner: *owner_pubkey,
}
.pack();
let accounts = vec![
AccountMeta::new(*account_pubkey, false),
AccountMeta::new_readonly(*mint_pubkey, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
];
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn initialize_account3(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::InitializeAccount3 {
owner: *owner_pubkey,
}
.pack();
let accounts = vec![
AccountMeta::new(*account_pubkey, false),
AccountMeta::new_readonly(*mint_pubkey, false),
];
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn initialize_multisig(
token_program_id: &Pubkey,
multisig_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
m: u8,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
if !is_valid_signer_index(m as usize)
|| !is_valid_signer_index(signer_pubkeys.len())
|| m as usize > signer_pubkeys.len()
{
return Err(ProgramError::MissingRequiredSignature);
}
let data = TokenInstruction::InitializeMultisig { m }.pack();
let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*multisig_pubkey, false));
accounts.push(AccountMeta::new_readonly(sysvar::rent::id(), false));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, false));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn initialize_multisig2(
token_program_id: &Pubkey,
multisig_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
m: u8,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
if !is_valid_signer_index(m as usize)
|| !is_valid_signer_index(signer_pubkeys.len())
|| m as usize > signer_pubkeys.len()
{
return Err(ProgramError::MissingRequiredSignature);
}
let data = TokenInstruction::InitializeMultisig2 { m }.pack();
let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*multisig_pubkey, false));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, false));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
#[deprecated(
since = "4.0.0",
note = "please use `transfer_checked` or `transfer_checked_with_fee` instead"
)]
pub fn transfer(
token_program_id: &Pubkey,
source_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
#[allow(deprecated)]
let data = TokenInstruction::Transfer { amount }.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*source_pubkey, false));
accounts.push(AccountMeta::new(*destination_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*authority_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn approve(
token_program_id: &Pubkey,
source_pubkey: &Pubkey,
delegate_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::Approve { amount }.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*source_pubkey, false));
accounts.push(AccountMeta::new_readonly(*delegate_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn revoke(
token_program_id: &Pubkey,
source_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::Revoke.pack();
let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*source_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn set_authority(
token_program_id: &Pubkey,
owned_pubkey: &Pubkey,
new_authority_pubkey: Option<&Pubkey>,
authority_type: AuthorityType,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let new_authority = new_authority_pubkey.cloned().into();
let data = TokenInstruction::SetAuthority {
authority_type,
new_authority,
}
.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*owned_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn mint_to(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
account_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::MintTo { amount }.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*mint_pubkey, false));
accounts.push(AccountMeta::new(*account_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn burn(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::Burn { amount }.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*account_pubkey, false));
accounts.push(AccountMeta::new(*mint_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*authority_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn close_account(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::CloseAccount.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*account_pubkey, false));
accounts.push(AccountMeta::new(*destination_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn freeze_account(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::FreezeAccount.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*account_pubkey, false));
accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn thaw_account(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::ThawAccount.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*account_pubkey, false));
accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
#[allow(clippy::too_many_arguments)]
pub fn transfer_checked(
token_program_id: &Pubkey,
source_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
decimals: u8,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::TransferChecked { amount, decimals }.pack();
let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*source_pubkey, false));
accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
accounts.push(AccountMeta::new(*destination_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*authority_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
#[allow(clippy::too_many_arguments)]
pub fn approve_checked(
token_program_id: &Pubkey,
source_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
delegate_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
decimals: u8,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::ApproveChecked { amount, decimals }.pack();
let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*source_pubkey, false));
accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
accounts.push(AccountMeta::new_readonly(*delegate_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn mint_to_checked(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
account_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
decimals: u8,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::MintToChecked { amount, decimals }.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*mint_pubkey, false));
accounts.push(AccountMeta::new(*account_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn burn_checked(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
decimals: u8,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let data = TokenInstruction::BurnChecked { amount, decimals }.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*account_pubkey, false));
accounts.push(AccountMeta::new(*mint_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*authority_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn sync_native(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
Ok(Instruction {
program_id: *token_program_id,
accounts: vec![AccountMeta::new(*account_pubkey, false)],
data: TokenInstruction::SyncNative.pack(),
})
}
pub fn get_account_data_size(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
extension_types: &[ExtensionType],
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
Ok(Instruction {
program_id: *token_program_id,
accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
data: TokenInstruction::GetAccountDataSize {
extension_types: extension_types.to_vec(),
}
.pack(),
})
}
pub fn initialize_mint_close_authority(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
close_authority: Option<&Pubkey>,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let close_authority = close_authority.cloned().into();
Ok(Instruction {
program_id: *token_program_id,
accounts: vec![AccountMeta::new(*mint_pubkey, false)],
data: TokenInstruction::InitializeMintCloseAuthority { close_authority }.pack(),
})
}
pub fn initialize_immutable_owner(
token_program_id: &Pubkey,
token_account: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
Ok(Instruction {
program_id: *token_program_id,
accounts: vec![AccountMeta::new(*token_account, false)],
data: TokenInstruction::InitializeImmutableOwner.pack(),
})
}
pub fn amount_to_ui_amount(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
amount: u64,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
Ok(Instruction {
program_id: *token_program_id,
accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
data: TokenInstruction::AmountToUiAmount { amount }.pack(),
})
}
pub fn ui_amount_to_amount(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
ui_amount: &str,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
Ok(Instruction {
program_id: *token_program_id,
accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
data: TokenInstruction::UiAmountToAmount { ui_amount }.pack(),
})
}
pub fn reallocate(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
payer: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
extension_types: &[ExtensionType],
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*account_pubkey, false));
accounts.push(AccountMeta::new(*payer, true));
accounts.push(AccountMeta::new_readonly(system_program::id(), false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data: TokenInstruction::Reallocate {
extension_types: extension_types.to_vec(),
}
.pack(),
})
}
pub fn create_native_mint(
token_program_id: &Pubkey,
payer: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
Ok(Instruction {
program_id: *token_program_id,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new(crate::native_mint::id(), false),
AccountMeta::new_readonly(system_program::id(), false),
],
data: TokenInstruction::CreateNativeMint.pack(),
})
}
pub fn initialize_non_transferable_mint(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
Ok(Instruction {
program_id: *token_program_id,
accounts: vec![AccountMeta::new(*mint_pubkey, false)],
data: TokenInstruction::InitializeNonTransferableMint.pack(),
})
}
pub fn initialize_permanent_delegate(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
delegate: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
Ok(Instruction {
program_id: *token_program_id,
accounts: vec![AccountMeta::new(*mint_pubkey, false)],
data: TokenInstruction::InitializePermanentDelegate {
delegate: *delegate,
}
.pack(),
})
}
pub fn is_valid_signer_index(index: usize) -> bool {
(MIN_SIGNERS..=MAX_SIGNERS).contains(&index)
}
pub fn decode_instruction_type<T: TryFrom<u8>>(input: &[u8]) -> Result<T, ProgramError> {
if input.is_empty() {
Err(ProgramError::InvalidInstructionData)
} else {
T::try_from(input[0]).map_err(|_| TokenError::InvalidInstruction.into())
}
}
pub fn decode_instruction_data<T: Pod>(input_with_type: &[u8]) -> Result<&T, ProgramError> {
if input_with_type.len() != pod_get_packed_len::<T>().saturating_add(1) {
Err(ProgramError::InvalidInstructionData)
} else {
pod_from_bytes(&input_with_type[1..])
}
}
pub(crate) fn encode_instruction<T: Into<u8>, D: Pod>(
token_program_id: &Pubkey,
accounts: Vec<AccountMeta>,
token_instruction_type: TokenInstruction,
instruction_type: T,
instruction_data: &D,
) -> Instruction {
let mut data = token_instruction_type.pack();
data.push(T::into(instruction_type));
data.extend_from_slice(bytemuck::bytes_of(instruction_data));
Instruction {
program_id: *token_program_id,
accounts,
data,
}
}
pub fn withdraw_excess_lamports(
token_program_id: &Pubkey,
source_account: &Pubkey,
destination_account: &Pubkey,
authority: &Pubkey,
signers: &[&Pubkey],
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let mut accounts = vec![
AccountMeta::new(*source_account, false),
AccountMeta::new(*destination_account, false),
AccountMeta::new_readonly(*authority, signers.is_empty()),
];
for signer in signers {
accounts.push(AccountMeta::new_readonly(**signer, true))
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data: TokenInstruction::WithdrawExcessLamports.pack(),
})
}
#[cfg(test)]
mod test {
use {super::*, crate::pod_instruction::*, proptest::prelude::*};
#[test]
fn test_initialize_mint_packing() {
let decimals = 2;
let mint_authority = Pubkey::new_from_array([1u8; 32]);
let freeze_authority = COption::None;
let check = TokenInstruction::InitializeMint {
decimals,
mint_authority,
freeze_authority,
};
let packed = check.pack();
let mut expect = Vec::from([0u8, 2]);
expect.extend_from_slice(&[1u8; 32]);
expect.extend_from_slice(&[0]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::InitializeMint);
let (pod, pod_freeze_authority) =
decode_instruction_data_with_coption_pubkey::<InitializeMintData>(&packed).unwrap();
assert_eq!(pod.decimals, decimals);
assert_eq!(pod.mint_authority, mint_authority);
assert_eq!(pod_freeze_authority, freeze_authority.into());
let mint_authority = Pubkey::new_from_array([2u8; 32]);
let freeze_authority = COption::Some(Pubkey::new_from_array([3u8; 32]));
let check = TokenInstruction::InitializeMint {
decimals,
mint_authority,
freeze_authority,
};
let packed = check.pack();
let mut expect = vec![0u8, 2];
expect.extend_from_slice(&[2u8; 32]);
expect.extend_from_slice(&[1]);
expect.extend_from_slice(&[3u8; 32]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::InitializeMint);
let (pod, pod_freeze_authority) =
decode_instruction_data_with_coption_pubkey::<InitializeMintData>(&packed).unwrap();
assert_eq!(pod.decimals, decimals);
assert_eq!(pod.mint_authority, mint_authority);
assert_eq!(pod_freeze_authority, freeze_authority.into());
}
#[test]
fn test_initialize_account_packing() {
let check = TokenInstruction::InitializeAccount;
let packed = check.pack();
let expect = Vec::from([1u8]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::InitializeAccount);
}
#[test]
fn test_initialize_multisig_packing() {
let m = 1;
let check = TokenInstruction::InitializeMultisig { m };
let packed = check.pack();
let expect = Vec::from([2u8, 1]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::InitializeMultisig);
let pod = decode_instruction_data::<InitializeMultisigData>(&packed).unwrap();
assert_eq!(pod.m, m);
}
#[test]
fn test_transfer_packing() {
let amount = 1;
#[allow(deprecated)]
let check = TokenInstruction::Transfer { amount };
let packed = check.pack();
let expect = Vec::from([3u8, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::Transfer);
let pod = decode_instruction_data::<AmountData>(&packed).unwrap();
assert_eq!(pod.amount, amount.into());
}
#[test]
fn test_approve_packing() {
let amount = 1;
let check = TokenInstruction::Approve { amount };
let packed = check.pack();
let expect = Vec::from([4u8, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::Approve);
let pod = decode_instruction_data::<AmountData>(&packed).unwrap();
assert_eq!(pod.amount, amount.into());
}
#[test]
fn test_revoke_packing() {
let check = TokenInstruction::Revoke;
let packed = check.pack();
let expect = Vec::from([5u8]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::Revoke);
}
#[test]
fn test_set_authority_packing() {
let authority_type = AuthorityType::FreezeAccount;
let new_authority = COption::Some(Pubkey::new_from_array([4u8; 32]));
let check = TokenInstruction::SetAuthority {
authority_type: authority_type.clone(),
new_authority,
};
let packed = check.pack();
let mut expect = Vec::from([6u8, 1]);
expect.extend_from_slice(&[1]);
expect.extend_from_slice(&[4u8; 32]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::SetAuthority);
let (pod, pod_new_authority) =
decode_instruction_data_with_coption_pubkey::<SetAuthorityData>(&packed).unwrap();
assert_eq!(
AuthorityType::from(pod.authority_type).unwrap(),
authority_type
);
assert_eq!(pod_new_authority, new_authority.into());
}
#[test]
fn test_mint_to_packing() {
let amount = 1;
let check = TokenInstruction::MintTo { amount };
let packed = check.pack();
let expect = Vec::from([7u8, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::MintTo);
let pod = decode_instruction_data::<AmountData>(&packed).unwrap();
assert_eq!(pod.amount, amount.into());
}
#[test]
fn test_burn_packing() {
let amount = 1;
let check = TokenInstruction::Burn { amount };
let packed = check.pack();
let expect = Vec::from([8u8, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::Burn);
let pod = decode_instruction_data::<AmountData>(&packed).unwrap();
assert_eq!(pod.amount, amount.into());
}
#[test]
fn test_close_account_packing() {
let check = TokenInstruction::CloseAccount;
let packed = check.pack();
let expect = Vec::from([9u8]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::CloseAccount);
}
#[test]
fn test_freeze_account_packing() {
let check = TokenInstruction::FreezeAccount;
let packed = check.pack();
let expect = Vec::from([10u8]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::FreezeAccount);
}
#[test]
fn test_thaw_account_packing() {
let check = TokenInstruction::ThawAccount;
let packed = check.pack();
let expect = Vec::from([11u8]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::ThawAccount);
}
#[test]
fn test_transfer_checked_packing() {
let amount = 1;
let decimals = 2;
let check = TokenInstruction::TransferChecked { amount, decimals };
let packed = check.pack();
let expect = Vec::from([12u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::TransferChecked);
let pod = decode_instruction_data::<AmountCheckedData>(&packed).unwrap();
assert_eq!(pod.amount, amount.into());
assert_eq!(pod.decimals, decimals);
}
#[test]
fn test_approve_checked_packing() {
let amount = 1;
let decimals = 2;
let check = TokenInstruction::ApproveChecked { amount, decimals };
let packed = check.pack();
let expect = Vec::from([13u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::ApproveChecked);
let pod = decode_instruction_data::<AmountCheckedData>(&packed).unwrap();
assert_eq!(pod.amount, amount.into());
assert_eq!(pod.decimals, decimals);
}
#[test]
fn test_mint_to_checked_packing() {
let amount = 1;
let decimals = 2;
let check = TokenInstruction::MintToChecked { amount, decimals };
let packed = check.pack();
let expect = Vec::from([14u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::MintToChecked);
let pod = decode_instruction_data::<AmountCheckedData>(&packed).unwrap();
assert_eq!(pod.amount, amount.into());
assert_eq!(pod.decimals, decimals);
}
#[test]
fn test_burn_checked_packing() {
let amount = 1;
let decimals = 2;
let check = TokenInstruction::BurnChecked { amount, decimals };
let packed = check.pack();
let expect = Vec::from([15u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::BurnChecked);
let pod = decode_instruction_data::<AmountCheckedData>(&packed).unwrap();
assert_eq!(pod.amount, amount.into());
assert_eq!(pod.decimals, decimals);
}
#[test]
fn test_initialize_account2_packing() {
let owner = Pubkey::new_from_array([2u8; 32]);
let check = TokenInstruction::InitializeAccount2 { owner };
let packed = check.pack();
let mut expect = vec![16u8];
expect.extend_from_slice(&[2u8; 32]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::InitializeAccount2);
let pod_owner = decode_instruction_data::<Pubkey>(&packed).unwrap();
assert_eq!(*pod_owner, owner);
}
#[test]
fn test_sync_native_packing() {
let check = TokenInstruction::SyncNative;
let packed = check.pack();
let expect = vec![17u8];
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::SyncNative);
}
#[test]
fn test_initialize_account3_packing() {
let owner = Pubkey::new_from_array([2u8; 32]);
let check = TokenInstruction::InitializeAccount3 { owner };
let packed = check.pack();
let mut expect = vec![18u8];
expect.extend_from_slice(&[2u8; 32]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::InitializeAccount3);
let pod_owner = decode_instruction_data::<Pubkey>(&packed).unwrap();
assert_eq!(*pod_owner, owner);
}
#[test]
fn test_initialize_multisig2_packing() {
let m = 1;
let check = TokenInstruction::InitializeMultisig2 { m };
let packed = check.pack();
let expect = Vec::from([19u8, 1]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::InitializeMultisig2);
let pod = decode_instruction_data::<InitializeMultisigData>(&packed).unwrap();
assert_eq!(pod.m, m);
}
#[test]
fn test_initialize_mint2_packing() {
let decimals = 2;
let mint_authority = Pubkey::new_from_array([1u8; 32]);
let freeze_authority = COption::None;
let check = TokenInstruction::InitializeMint2 {
decimals,
mint_authority,
freeze_authority,
};
let packed = check.pack();
let mut expect = Vec::from([20u8, 2]);
expect.extend_from_slice(&[1u8; 32]);
expect.extend_from_slice(&[0]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::InitializeMint2);
let (pod, pod_freeze_authority) =
decode_instruction_data_with_coption_pubkey::<InitializeMintData>(&packed).unwrap();
assert_eq!(pod.decimals, decimals);
assert_eq!(pod.mint_authority, mint_authority);
assert_eq!(pod_freeze_authority, freeze_authority.into());
let decimals = 2;
let mint_authority = Pubkey::new_from_array([2u8; 32]);
let freeze_authority = COption::Some(Pubkey::new_from_array([3u8; 32]));
let check = TokenInstruction::InitializeMint2 {
decimals,
mint_authority,
freeze_authority,
};
let packed = check.pack();
let mut expect = vec![20u8, 2];
expect.extend_from_slice(&[2u8; 32]);
expect.extend_from_slice(&[1]);
expect.extend_from_slice(&[3u8; 32]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::InitializeMint2);
let (pod, pod_freeze_authority) =
decode_instruction_data_with_coption_pubkey::<InitializeMintData>(&packed).unwrap();
assert_eq!(pod.decimals, decimals);
assert_eq!(pod.mint_authority, mint_authority);
assert_eq!(pod_freeze_authority, freeze_authority.into());
}
#[test]
fn test_get_account_data_size_packing() {
let extension_types = vec![];
let check = TokenInstruction::GetAccountDataSize {
extension_types: extension_types.clone(),
};
let packed = check.pack();
let expect = [21u8];
assert_eq!(packed, &[21u8]);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::GetAccountDataSize);
let pod_extension_types = packed[1..]
.chunks(std::mem::size_of::<ExtensionType>())
.map(ExtensionType::try_from)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(pod_extension_types, extension_types);
let extension_types = vec![
ExtensionType::TransferFeeConfig,
ExtensionType::TransferFeeAmount,
];
let check = TokenInstruction::GetAccountDataSize {
extension_types: extension_types.clone(),
};
let packed = check.pack();
let expect = [21u8, 1, 0, 2, 0];
assert_eq!(packed, &[21u8, 1, 0, 2, 0]);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::GetAccountDataSize);
let pod_extension_types = packed[1..]
.chunks(std::mem::size_of::<ExtensionType>())
.map(ExtensionType::try_from)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(pod_extension_types, extension_types);
}
#[test]
fn test_amount_to_ui_amount_packing() {
let amount = 42;
let check = TokenInstruction::AmountToUiAmount { amount };
let packed = check.pack();
let expect = vec![23u8, 42, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::AmountToUiAmount);
let data = decode_instruction_data::<AmountData>(&packed).unwrap();
assert_eq!(data.amount, amount.into());
}
#[test]
fn test_ui_amount_to_amount_packing() {
let ui_amount = "0.42";
let check = TokenInstruction::UiAmountToAmount { ui_amount };
let packed = check.pack();
let expect = vec![24u8, 48, 46, 52, 50];
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::UiAmountToAmount);
let pod_ui_amount = std::str::from_utf8(&packed[1..]).unwrap();
assert_eq!(pod_ui_amount, ui_amount);
}
#[test]
fn test_initialize_mint_close_authority_packing() {
let close_authority = COption::Some(Pubkey::new_from_array([10u8; 32]));
let check = TokenInstruction::InitializeMintCloseAuthority { close_authority };
let packed = check.pack();
let mut expect = vec![25u8, 1];
expect.extend_from_slice(&[10u8; 32]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(
instruction_type,
PodTokenInstruction::InitializeMintCloseAuthority
);
let (_, pod_close_authority) =
decode_instruction_data_with_coption_pubkey::<()>(&packed).unwrap();
assert_eq!(pod_close_authority, close_authority.into());
}
#[test]
fn test_create_native_mint_packing() {
let check = TokenInstruction::CreateNativeMint;
let packed = check.pack();
let expect = vec![31u8];
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(instruction_type, PodTokenInstruction::CreateNativeMint);
}
#[test]
fn test_initialize_permanent_delegate_packing() {
let delegate = Pubkey::new_from_array([11u8; 32]);
let check = TokenInstruction::InitializePermanentDelegate { delegate };
let packed = check.pack();
let mut expect = vec![35u8];
expect.extend_from_slice(&[11u8; 32]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let instruction_type = decode_instruction_type::<PodTokenInstruction>(&packed).unwrap();
assert_eq!(
instruction_type,
PodTokenInstruction::InitializePermanentDelegate
);
let pod_delegate = decode_instruction_data::<Pubkey>(&packed).unwrap();
assert_eq!(*pod_delegate, delegate);
}
macro_rules! test_instruction {
($a:ident($($b:tt)*)) => {
let instruction_v3 = spl_token::instruction::$a($($b)*).unwrap();
let instruction_2022 = $a($($b)*).unwrap();
assert_eq!(instruction_v3, instruction_2022);
}
}
#[test]
fn test_v3_compatibility() {
let token_program_id = spl_token::id();
let mint_pubkey = Pubkey::new_unique();
let mint_authority_pubkey = Pubkey::new_unique();
let freeze_authority_pubkey = Pubkey::new_unique();
let decimals = 9u8;
let account_pubkey = Pubkey::new_unique();
let owner_pubkey = Pubkey::new_unique();
let multisig_pubkey = Pubkey::new_unique();
let signer_pubkeys_vec = vec![Pubkey::new_unique(); MAX_SIGNERS];
let signer_pubkeys = signer_pubkeys_vec.iter().collect::<Vec<_>>();
let m = 10u8;
let source_pubkey = Pubkey::new_unique();
let destination_pubkey = Pubkey::new_unique();
let authority_pubkey = Pubkey::new_unique();
let amount = 1_000_000_000_000;
let delegate_pubkey = Pubkey::new_unique();
let owned_pubkey = Pubkey::new_unique();
let new_authority_pubkey = Pubkey::new_unique();
let ui_amount = "100000.00";
test_instruction!(initialize_mint(
&token_program_id,
&mint_pubkey,
&mint_authority_pubkey,
None,
decimals,
));
test_instruction!(initialize_mint2(
&token_program_id,
&mint_pubkey,
&mint_authority_pubkey,
Some(&freeze_authority_pubkey),
decimals,
));
test_instruction!(initialize_account(
&token_program_id,
&account_pubkey,
&mint_pubkey,
&owner_pubkey,
));
test_instruction!(initialize_account2(
&token_program_id,
&account_pubkey,
&mint_pubkey,
&owner_pubkey,
));
test_instruction!(initialize_account3(
&token_program_id,
&account_pubkey,
&mint_pubkey,
&owner_pubkey,
));
test_instruction!(initialize_multisig(
&token_program_id,
&multisig_pubkey,
&signer_pubkeys,
m,
));
test_instruction!(initialize_multisig2(
&token_program_id,
&multisig_pubkey,
&signer_pubkeys,
m,
));
#[allow(deprecated)]
{
test_instruction!(transfer(
&token_program_id,
&source_pubkey,
&destination_pubkey,
&authority_pubkey,
&signer_pubkeys,
amount
));
}
test_instruction!(transfer_checked(
&token_program_id,
&source_pubkey,
&mint_pubkey,
&destination_pubkey,
&authority_pubkey,
&signer_pubkeys,
amount,
decimals,
));
test_instruction!(approve(
&token_program_id,
&source_pubkey,
&delegate_pubkey,
&owner_pubkey,
&signer_pubkeys,
amount
));
test_instruction!(approve_checked(
&token_program_id,
&source_pubkey,
&mint_pubkey,
&delegate_pubkey,
&owner_pubkey,
&signer_pubkeys,
amount,
decimals
));
test_instruction!(revoke(
&token_program_id,
&source_pubkey,
&owner_pubkey,
&signer_pubkeys,
));
{
let instruction_v3 = spl_token::instruction::set_authority(
&token_program_id,
&owned_pubkey,
Some(&new_authority_pubkey),
spl_token::instruction::AuthorityType::AccountOwner,
&owner_pubkey,
&signer_pubkeys,
)
.unwrap();
let instruction_2022 = set_authority(
&token_program_id,
&owned_pubkey,
Some(&new_authority_pubkey),
AuthorityType::AccountOwner,
&owner_pubkey,
&signer_pubkeys,
)
.unwrap();
assert_eq!(instruction_v3, instruction_2022);
}
test_instruction!(mint_to(
&token_program_id,
&mint_pubkey,
&account_pubkey,
&owner_pubkey,
&signer_pubkeys,
amount,
));
test_instruction!(mint_to_checked(
&token_program_id,
&mint_pubkey,
&account_pubkey,
&owner_pubkey,
&signer_pubkeys,
amount,
decimals,
));
test_instruction!(burn(
&token_program_id,
&account_pubkey,
&mint_pubkey,
&authority_pubkey,
&signer_pubkeys,
amount,
));
test_instruction!(burn_checked(
&token_program_id,
&account_pubkey,
&mint_pubkey,
&authority_pubkey,
&signer_pubkeys,
amount,
decimals,
));
test_instruction!(close_account(
&token_program_id,
&account_pubkey,
&destination_pubkey,
&owner_pubkey,
&signer_pubkeys,
));
test_instruction!(freeze_account(
&token_program_id,
&account_pubkey,
&mint_pubkey,
&owner_pubkey,
&signer_pubkeys,
));
test_instruction!(thaw_account(
&token_program_id,
&account_pubkey,
&mint_pubkey,
&owner_pubkey,
&signer_pubkeys,
));
test_instruction!(sync_native(&token_program_id, &account_pubkey,));
{
let instruction_v3 =
spl_token::instruction::get_account_data_size(&token_program_id, &mint_pubkey)
.unwrap();
let instruction_2022 =
get_account_data_size(&token_program_id, &mint_pubkey, &[]).unwrap();
assert_eq!(instruction_v3, instruction_2022);
}
test_instruction!(initialize_immutable_owner(
&token_program_id,
&account_pubkey,
));
test_instruction!(amount_to_ui_amount(&token_program_id, &mint_pubkey, amount,));
test_instruction!(ui_amount_to_amount(
&token_program_id,
&mint_pubkey,
ui_amount,
));
}
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 = TokenInstruction::unpack(&data);
}
}
}