#![allow(deprecated)]
#[cfg(feature = "serde")]
use {
crate::serialization::{batch_fromstr, coption_fromstr, coption_u64_fromval},
serde::{Deserialize, Serialize},
serde_with::{As, DisplayFromStr},
};
use {
crate::{
check_program_account, check_spl_token_program_account, error::TokenError,
extension::ExtensionType,
},
alloc::{vec, vec::Vec},
bytemuck::Pod,
core::{
convert::{TryFrom, TryInto},
mem::size_of,
},
solana_address::{Address, ADDRESS_BYTES},
solana_instruction::{AccountMeta, Instruction},
solana_program_error::ProgramError,
solana_program_option::COption,
solana_sdk_ids::{system_program, sysvar},
};
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", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(rename_all_fields = "camelCase", rename_all = "camelCase")
)]
#[derive(Clone, Debug, PartialEq)]
pub enum TokenInstruction<'a> {
InitializeMint {
decimals: u8,
#[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
mint_authority: Address,
#[cfg_attr(feature = "serde", serde(with = "coption_fromstr"))]
freeze_authority: COption<Address>,
},
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", serde(with = "coption_fromstr"))]
new_authority: COption<Address>,
},
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", serde(with = "As::<DisplayFromStr>"))]
owner: Address,
},
SyncNative,
InitializeAccount3 {
#[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
owner: Address,
},
InitializeMultisig2 {
m: u8,
},
InitializeMint2 {
decimals: u8,
#[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
mint_authority: Address,
#[cfg_attr(feature = "serde", serde(with = "coption_fromstr"))]
freeze_authority: COption<Address>,
},
GetAccountDataSize {
extension_types: Vec<ExtensionType>,
},
InitializeImmutableOwner,
AmountToUiAmount {
amount: u64,
},
UiAmountToAmount {
ui_amount: &'a str,
},
InitializeMintCloseAuthority {
#[cfg_attr(feature = "serde", serde(with = "coption_fromstr"))]
close_authority: COption<Address>,
},
TransferFeeExtension,
ConfidentialTransferExtension,
DefaultAccountStateExtension,
Reallocate {
extension_types: Vec<ExtensionType>,
},
MemoTransferExtension,
CreateNativeMint,
InitializeNonTransferableMint,
InterestBearingMintExtension,
CpiGuardExtension,
InitializePermanentDelegate {
#[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
delegate: Address,
},
TransferHookExtension,
ConfidentialTransferFeeExtension,
WithdrawExcessLamports,
MetadataPointerExtension,
GroupPointerExtension,
GroupMemberPointerExtension,
ConfidentialMintBurnExtension,
ScaledUiAmountExtension,
PausableExtension,
UnwrapLamports {
#[cfg_attr(feature = "serde", serde(with = "coption_u64_fromval"))]
amount: COption<u64>,
},
PermissionedBurnExtension,
Batch {
#[cfg_attr(feature = "serde", serde(with = "batch_fromstr"))]
data: Vec<u8>,
},
}
impl<'a> TokenInstruction<'a> {
pub fn unpack(input: &'a [u8]) -> Result<Self, ProgramError> {
Self::unpack_with_rest(input).map(|(token_instruction, _)| token_instruction)
}
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);
}
&Self::ConfidentialMintBurnExtension => {
buf.push(42);
}
&Self::ScaledUiAmountExtension => {
buf.push(43);
}
&Self::PausableExtension => {
buf.push(44);
}
&Self::UnwrapLamports { amount } => {
buf.push(45);
Self::pack_u64_option(&amount, &mut buf);
}
&Self::PermissionedBurnExtension => {
buf.push(46);
}
Self::Batch { data } => {
buf.push(255);
buf.extend_from_slice(data);
}
};
buf
}
pub(crate) fn unpack_with_rest(input: &'a [u8]) -> Result<(Self, &'a [u8]), 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,
},
rest,
)
}
1 => (Self::InitializeAccount, rest),
2 => {
let (&m, rest) = rest.split_first().ok_or(InvalidInstruction)?;
(Self::InitializeMultisig { m }, rest)
}
3 | 4 | 7 | 8 => {
let (amount, rest) = rest
.split_at_checked(U64_BYTES)
.and_then(|(bytes, rest)| {
let amount = u64::from_le_bytes(bytes.try_into().ok()?);
Some((amount, rest))
})
.ok_or(InvalidInstruction)?;
(
match tag {
#[allow(deprecated)]
3 => Self::Transfer { amount },
4 => Self::Approve { amount },
7 => Self::MintTo { amount },
8 => Self::Burn { amount },
_ => unreachable!(),
},
rest,
)
}
5 => (Self::Revoke, rest),
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,
},
rest,
)
}
9 => (Self::CloseAccount, rest),
10 => (Self::FreezeAccount, rest),
11 => (Self::ThawAccount, rest),
12 => {
let (amount, decimals, rest) = Self::unpack_amount_decimals(rest)?;
(Self::TransferChecked { amount, decimals }, rest)
}
13 => {
let (amount, decimals, rest) = Self::unpack_amount_decimals(rest)?;
(Self::ApproveChecked { amount, decimals }, rest)
}
14 => {
let (amount, decimals, rest) = Self::unpack_amount_decimals(rest)?;
(Self::MintToChecked { amount, decimals }, rest)
}
15 => {
let (amount, decimals, rest) = Self::unpack_amount_decimals(rest)?;
(Self::BurnChecked { amount, decimals }, rest)
}
16 => {
let (owner, rest) = Self::unpack_pubkey(rest)?;
(Self::InitializeAccount2 { owner }, rest)
}
17 => (Self::SyncNative, rest),
18 => {
let (owner, rest) = Self::unpack_pubkey(rest)?;
(Self::InitializeAccount3 { owner }, rest)
}
19 => {
let (&m, rest) = rest.split_first().ok_or(InvalidInstruction)?;
(Self::InitializeMultisig2 { m }, rest)
}
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,
},
rest,
)
}
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, rest),
23 => {
let (amount, rest) = Self::unpack_u64(rest)?;
(Self::AmountToUiAmount { amount }, rest)
}
24 => {
let ui_amount = core::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 }, rest)
}
26 => (Self::TransferFeeExtension, rest),
27 => (Self::ConfidentialTransferExtension, rest),
28 => (Self::DefaultAccountStateExtension, rest),
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, rest),
31 => (Self::CreateNativeMint, rest),
32 => (Self::InitializeNonTransferableMint, rest),
33 => (Self::InterestBearingMintExtension, rest),
34 => (Self::CpiGuardExtension, rest),
35 => {
let (delegate, rest) = Self::unpack_pubkey(rest)?;
(Self::InitializePermanentDelegate { delegate }, rest)
}
36 => (Self::TransferHookExtension, rest),
37 => (Self::ConfidentialTransferFeeExtension, rest),
38 => (Self::WithdrawExcessLamports, rest),
39 => (Self::MetadataPointerExtension, rest),
40 => (Self::GroupPointerExtension, rest),
41 => (Self::GroupMemberPointerExtension, rest),
42 => (Self::ConfidentialMintBurnExtension, rest),
43 => (Self::ScaledUiAmountExtension, rest),
44 => (Self::PausableExtension, rest),
45 => {
let (amount, rest) = Self::unpack_u64_option(rest)?;
(Self::UnwrapLamports { amount }, rest)
}
46 => (Self::PermissionedBurnExtension, rest),
255 => (
Self::Batch {
data: rest.to_vec(),
},
&[],
),
_ => return Err(TokenError::InvalidInstruction.into()),
})
}
pub(crate) fn unpack_pubkey(input: &[u8]) -> Result<(Address, &[u8]), ProgramError> {
let pk = input
.get(..ADDRESS_BYTES)
.and_then(|x| Address::try_from(x).ok())
.ok_or(TokenError::InvalidInstruction)?;
Ok((pk, &input[ADDRESS_BYTES..]))
}
pub(crate) fn unpack_pubkey_option(
input: &[u8],
) -> Result<(COption<Address>, &[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<Address>, 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_u64_option(input: &[u8]) -> Result<(COption<u64>, &[u8]), ProgramError> {
match input.split_first() {
Option::Some((&0, rest)) => Ok((COption::None, rest)),
Option::Some((&1, rest)) => {
let (value, rest) = Self::unpack_u64(rest)?;
Ok((COption::Some(value), rest))
}
_ => Err(TokenError::InvalidInstruction.into()),
}
}
pub(crate) fn pack_u64_option(value: &COption<u64>, buf: &mut Vec<u8>) {
match *value {
COption::Some(ref amount) => {
buf.push(1);
buf.extend_from_slice(&amount.to_le_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", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", 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,
ScaledUiAmount,
Pause,
PermissionedBurn,
}
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,
AuthorityType::ScaledUiAmount => 15,
AuthorityType::Pause => 16,
AuthorityType::PermissionedBurn => 17,
}
}
pub 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),
15 => Ok(AuthorityType::ScaledUiAmount),
16 => Ok(AuthorityType::Pause),
17 => Ok(AuthorityType::PermissionedBurn),
_ => Err(TokenError::InvalidInstruction.into()),
}
}
}
pub fn initialize_mint(
token_program_id: &Address,
mint_pubkey: &Address,
mint_authority_pubkey: &Address,
freeze_authority_pubkey: Option<&Address>,
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: &Address,
mint_pubkey: &Address,
mint_authority_pubkey: &Address,
freeze_authority_pubkey: Option<&Address>,
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: &Address,
account_pubkey: &Address,
mint_pubkey: &Address,
owner_pubkey: &Address,
) -> 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: &Address,
account_pubkey: &Address,
mint_pubkey: &Address,
owner_pubkey: &Address,
) -> 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: &Address,
account_pubkey: &Address,
mint_pubkey: &Address,
owner_pubkey: &Address,
) -> 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: &Address,
multisig_pubkey: &Address,
signer_pubkeys: &[&Address],
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: &Address,
multisig_pubkey: &Address,
signer_pubkeys: &[&Address],
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: &Address,
source_pubkey: &Address,
destination_pubkey: &Address,
authority_pubkey: &Address,
signer_pubkeys: &[&Address],
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: &Address,
source_pubkey: &Address,
delegate_pubkey: &Address,
owner_pubkey: &Address,
signer_pubkeys: &[&Address],
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: &Address,
source_pubkey: &Address,
owner_pubkey: &Address,
signer_pubkeys: &[&Address],
) -> 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: &Address,
owned_pubkey: &Address,
new_authority_pubkey: Option<&Address>,
authority_type: AuthorityType,
owner_pubkey: &Address,
signer_pubkeys: &[&Address],
) -> 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: &Address,
mint_pubkey: &Address,
account_pubkey: &Address,
mint_authority_pubkey: &Address,
signer_pubkeys: &[&Address],
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(
*mint_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 burn(
token_program_id: &Address,
account_pubkey: &Address,
mint_pubkey: &Address,
authority_pubkey: &Address,
signer_pubkeys: &[&Address],
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: &Address,
account_pubkey: &Address,
destination_pubkey: &Address,
owner_pubkey: &Address,
signer_pubkeys: &[&Address],
) -> 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: &Address,
account_pubkey: &Address,
mint_pubkey: &Address,
freeze_authority_pubkey: &Address,
signer_pubkeys: &[&Address],
) -> 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(
*freeze_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 thaw_account(
token_program_id: &Address,
account_pubkey: &Address,
mint_pubkey: &Address,
freeze_authority_pubkey: &Address,
signer_pubkeys: &[&Address],
) -> 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(
*freeze_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 transfer_checked(
token_program_id: &Address,
source_pubkey: &Address,
mint_pubkey: &Address,
destination_pubkey: &Address,
authority_pubkey: &Address,
signer_pubkeys: &[&Address],
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: &Address,
source_pubkey: &Address,
mint_pubkey: &Address,
delegate_pubkey: &Address,
owner_pubkey: &Address,
signer_pubkeys: &[&Address],
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: &Address,
mint_pubkey: &Address,
account_pubkey: &Address,
mint_authority_pubkey: &Address,
signer_pubkeys: &[&Address],
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(
*mint_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 burn_checked(
token_program_id: &Address,
account_pubkey: &Address,
mint_pubkey: &Address,
authority_pubkey: &Address,
signer_pubkeys: &[&Address],
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: &Address,
account_pubkey: &Address,
) -> 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 sync_native_with_rent_sysvar(
token_program_id: &Address,
account_pubkey: &Address,
) -> Result<Instruction, ProgramError> {
let mut instruction = sync_native(token_program_id, account_pubkey)?;
instruction
.accounts
.push(AccountMeta::new_readonly(sysvar::rent::id(), false));
Ok(instruction)
}
pub fn get_account_data_size(
token_program_id: &Address,
mint_pubkey: &Address,
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: &Address,
mint_pubkey: &Address,
close_authority: Option<&Address>,
) -> 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: &Address,
token_account: &Address,
) -> 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: &Address,
mint_pubkey: &Address,
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: &Address,
mint_pubkey: &Address,
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: &Address,
account_pubkey: &Address,
payer: &Address,
owner_pubkey: &Address,
signer_pubkeys: &[&Address],
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: &Address,
payer: &Address,
) -> 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: &Address,
mint_pubkey: &Address,
) -> 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: &Address,
mint_pubkey: &Address,
delegate: &Address,
) -> 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() != size_of::<T>().saturating_add(1) {
Err(ProgramError::InvalidInstructionData)
} else {
bytemuck::try_from_bytes(&input_with_type[1..]).map_err(|_| ProgramError::InvalidArgument)
}
}
pub(crate) fn encode_instruction<T: Into<u8>, D: Pod>(
token_program_id: &Address,
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: &Address,
source_account: &Address,
destination_account: &Address,
authority: &Address,
signers: &[&Address],
) -> 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(),
})
}
pub fn unwrap_lamports(
token_program_id: &Address,
source_pubkey: &Address,
destination_pubkey: &Address,
authority_pubkey: &Address,
signer_pubkeys: &[&Address],
amount: Option<u64>,
) -> Result<Instruction, ProgramError> {
check_spl_token_program_account(token_program_id)?;
let amount = amount.into();
let data = TokenInstruction::UnwrapLamports { 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,
})
}
#[cfg(test)]
mod test {
use {super::*, proptest::prelude::*};
#[test]
fn test_initialize_mint_packing() {
let decimals = 2;
let mint_authority = Address::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 mint_authority = Address::new_from_array([2u8; 32]);
let freeze_authority = COption::Some(Address::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);
}
#[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);
}
#[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);
}
#[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);
}
#[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);
}
#[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);
}
#[test]
fn test_set_authority_packing() {
let authority_type = AuthorityType::FreezeAccount;
let new_authority = COption::Some(Address::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);
}
#[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);
}
#[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);
}
#[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);
}
#[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);
}
#[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);
}
#[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);
}
#[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);
}
#[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);
}
#[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);
}
#[test]
fn test_initialize_account2_packing() {
let owner = Address::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);
}
#[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);
}
#[test]
fn test_initialize_account3_packing() {
let owner = Address::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);
}
#[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);
}
#[test]
fn test_initialize_mint2_packing() {
let decimals = 2;
let mint_authority = Address::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 decimals = 2;
let mint_authority = Address::new_from_array([2u8; 32]);
let freeze_authority = COption::Some(Address::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);
}
#[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 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);
}
#[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);
}
#[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);
}
#[test]
fn test_initialize_mint_close_authority_packing() {
let close_authority = COption::Some(Address::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);
}
#[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);
}
#[test]
fn test_initialize_permanent_delegate_packing() {
let delegate = Address::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);
}
#[test]
fn test_unwrap_lamports_packing() {
let amount = COption::None;
let check = TokenInstruction::UnwrapLamports { amount };
let packed = check.pack();
let expect = Vec::from([45u8, 0]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let amount = COption::Some(1);
let check = TokenInstruction::UnwrapLamports { amount };
let packed = check.pack();
let expect = Vec::from([45u8, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
}
macro_rules! test_instruction {
($a:ident($($b:tt)*)) => {
let instruction_v3 = spl_token_interface::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_interface::id();
let mint_pubkey = Address::new_unique();
let mint_authority_pubkey = Address::new_unique();
let freeze_authority_pubkey = Address::new_unique();
let decimals = 9u8;
let account_pubkey = Address::new_unique();
let owner_pubkey = Address::new_unique();
let multisig_pubkey = Address::new_unique();
let signer_pubkeys_vec = vec![Address::new_unique(); MAX_SIGNERS];
let signer_pubkeys = signer_pubkeys_vec.iter().collect::<Vec<_>>();
let m = 10u8;
let source_pubkey = Address::new_unique();
let destination_pubkey = Address::new_unique();
let authority_pubkey = Address::new_unique();
let amount = 1_000_000_000_000;
let delegate_pubkey = Address::new_unique();
let owned_pubkey = Address::new_unique();
let new_authority_pubkey = Address::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_interface::instruction::set_authority(
&token_program_id,
&owned_pubkey,
Some(&new_authority_pubkey),
spl_token_interface::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,
&freeze_authority_pubkey,
&signer_pubkeys,
));
test_instruction!(thaw_account(
&token_program_id,
&account_pubkey,
&mint_pubkey,
&freeze_authority_pubkey,
&signer_pubkeys,
));
test_instruction!(sync_native(&token_program_id, &account_pubkey,));
{
let instruction_v3 = spl_token_interface::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);
}
}
}