use crate::error::TokenError;
use solana_sdk::{
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
pubkey::Pubkey,
};
use std::mem::size_of;
pub const MIN_SIGNERS: usize = 1;
pub const MAX_SIGNERS: usize = 11;
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub enum TokenInstruction {
InitializeMint {
amount: u64,
decimals: u8,
},
InitializeAccount,
InitializeMultisig {
m: u8,
},
Transfer {
amount: u64,
},
Approve {
amount: u64,
},
Revoke,
SetOwner,
MintTo {
amount: u64,
},
Burn {
amount: u64,
},
CloseAccount,
}
impl TokenInstruction {
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
if input.len() < size_of::<u8>() {
return Err(TokenError::InvalidInstruction.into());
}
Ok(match input[0] {
0 => {
if input.len() < size_of::<u8>() + size_of::<u64>() + size_of::<u8>() {
return Err(TokenError::InvalidInstruction.into());
}
#[allow(clippy::cast_ptr_alignment)]
let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
let decimals =
unsafe { *(&input[size_of::<u8>() + size_of::<u64>()] as *const u8) };
Self::InitializeMint { amount, decimals }
}
1 => Self::InitializeAccount,
2 => {
if input.len() < size_of::<u8>() + size_of::<u8>() {
return Err(TokenError::InvalidInstruction.into());
}
#[allow(clippy::cast_ptr_alignment)]
let m = unsafe { *(&input[1] as *const u8) };
Self::InitializeMultisig { m }
}
3 => {
if input.len() < size_of::<u8>() + size_of::<u64>() {
return Err(TokenError::InvalidInstruction.into());
}
#[allow(clippy::cast_ptr_alignment)]
let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
Self::Transfer { amount }
}
4 => {
if input.len() < size_of::<u8>() + size_of::<u64>() {
return Err(TokenError::InvalidInstruction.into());
}
#[allow(clippy::cast_ptr_alignment)]
let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
Self::Approve { amount }
}
5 => Self::Revoke,
6 => Self::SetOwner,
7 => {
if input.len() < size_of::<u8>() + size_of::<u64>() {
return Err(TokenError::InvalidInstruction.into());
}
#[allow(clippy::cast_ptr_alignment)]
let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
Self::MintTo { amount }
}
8 => {
if input.len() < size_of::<u8>() + size_of::<u64>() {
return Err(TokenError::InvalidInstruction.into());
}
#[allow(clippy::cast_ptr_alignment)]
let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
Self::Burn { amount }
}
9 => Self::CloseAccount,
_ => return Err(TokenError::InvalidInstruction.into()),
})
}
pub fn pack(self: &Self) -> Result<Vec<u8>, ProgramError> {
let mut output = vec![0u8; size_of::<TokenInstruction>()];
match self {
Self::InitializeMint { amount, decimals } => {
output[0] = 0;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut u64) };
*value = *amount;
let value =
unsafe { &mut *(&mut output[size_of::<u8>() + size_of::<u64>()] as *mut u8) };
*value = *decimals;
}
Self::InitializeAccount => output[0] = 1,
Self::InitializeMultisig { m } => {
output[0] = 2;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut u8) };
*value = *m;
}
Self::Transfer { amount } => {
output[0] = 3;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut u64) };
*value = *amount;
}
Self::Approve { amount } => {
output[0] = 4;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut u64) };
*value = *amount;
}
Self::Revoke => output[0] = 5,
Self::SetOwner => output[0] = 6,
Self::MintTo { amount } => {
output[0] = 7;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut u64) };
*value = *amount;
}
Self::Burn { amount } => {
output[0] = 8;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut u64) };
*value = *amount;
}
Self::CloseAccount => output[0] = 9,
}
Ok(output)
}
}
pub fn initialize_mint(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
account_pubkey: Option<&Pubkey>,
owner_pubkey: Option<&Pubkey>,
amount: u64,
decimals: u8,
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::InitializeMint { amount, decimals }.pack()?;
let mut accounts = vec![AccountMeta::new(*mint_pubkey, false)];
if amount != 0 {
match account_pubkey {
Some(pubkey) => accounts.push(AccountMeta::new(*pubkey, false)),
None => {
return Err(ProgramError::NotEnoughAccountKeys);
}
}
}
match owner_pubkey {
Some(pubkey) => accounts.push(AccountMeta::new_readonly(*pubkey, false)),
None => {
if amount == 0 {
return Err(TokenError::OwnerRequiredIfNoInitialSupply.into());
}
}
}
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> {
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),
];
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> {
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 + 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,
})
}
pub fn transfer(
token_program_id: &Pubkey,
source_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
) -> Result<Instruction, ProgramError> {
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(**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> {
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(**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> {
let data = TokenInstruction::Revoke.pack()?;
let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len());
accounts.push(AccountMeta::new_readonly(*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(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn set_owner(
token_program_id: &Pubkey,
owned_pubkey: &Pubkey,
new_owner_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::SetOwner.pack()?;
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*owned_pubkey, false));
accounts.push(AccountMeta::new_readonly(*new_owner_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*owner_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new(**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> {
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(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn burn(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
) -> Result<Instruction, ProgramError> {
let data = TokenInstruction::Burn { amount }.pack()?;
let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*account_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*authority_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn close_account(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
dest_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
) -> Result<Instruction, ProgramError> {
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(*dest_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*authority_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
pub fn is_valid_signer_index(index: usize) -> bool {
!(index < MIN_SIGNERS || index > MAX_SIGNERS)
}