//! Instruction types use crate::{error::TokenError, option::COption}; use solana_sdk::{ instruction::{AccountMeta, Instruction}, program_error::ProgramError, pubkey::Pubkey, sysvar, }; use std::convert::TryInto; use std::mem::size_of; /// Minimum number of multisignature signers (min N) pub const MIN_SIGNERS: usize = 1; /// Maximum number of multisignature signers (max N) pub const MAX_SIGNERS: usize = 11; /// Instructions supported by the token program. #[repr(C)] #[derive(Clone, Debug, PartialEq)] pub enum TokenInstruction { /// Initializes a new mint and optionally deposits all the newly minted tokens in an account. /// /// The `InitializeMint` instruction requires no signers and MUST be included within /// the same Transaction as the system program's `CreateInstruction` that creates the account /// being initialized. Otherwise another party can acquire ownership of the uninitialized account. /// /// Accounts expected by this instruction: /// /// 0. `[writable]` The mint to initialize. /// 1. `[]` Rent sysvar /// InitializeMint { /// Number of base 10 digits to the right of the decimal place. decimals: u8, /// The authority/multisignature to mint tokens. mint_authority: Pubkey, /// The freeze authority/multisignature of the mint. freeze_authority: COption, }, /// Initializes a new account to hold tokens. If this account is associated with the native /// mint then the token balance of the initialized account will be equal to the amount of SOL /// in the account. If this account is associated with another mint, that mint must be /// initialized before this command can succeed. /// /// The `InitializeAccount` instruction requires no signers and MUST be included within /// the same Transaction as the system program's `CreateInstruction` that creates the account /// being initialized. Otherwise another party can acquire ownership of the uninitialized account. /// /// Accounts expected by this instruction: /// /// 0. `[writable]` The account to initialize. /// 1. `[]` The mint this account will be associated with. /// 2. `[]` The new account's owner/multisignature. /// 3. `[]` Rent sysvar InitializeAccount, /// Initializes a multisignature account with N provided signers. /// /// Multisignature accounts can used in place of any single owner/delegate accounts in any /// token instruction that require an owner/delegate to be present. The variant field represents the /// number of signers (M) required to validate this multisignature account. /// /// The `InitializeMultisig` instruction requires no signers and MUST be included within /// the same Transaction as the system program's `CreateInstruction` that creates the account /// being initialized. Otherwise another party can acquire ownership of the uninitialized account. /// /// Accounts expected by this instruction: /// /// 0. `[writable]` The multisignature account to initialize. /// 1. `[]` Rent sysvar /// 2. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= 11. InitializeMultisig { /// The number of signers (M) required to validate this multisignature account. m: u8, }, /// Transfers tokens from one account to another either directly or via a delegate. If this /// account is associated with the native mint then equal amounts of SOL and Tokens will be /// transferred to the destination account. /// /// Accounts expected by this instruction: /// /// * Single owner/delegate /// 0. `[writable]` The source account. /// 1. `[writable]` The destination account. /// 2. `[signer]` The source account's owner/delegate. /// /// * Multisignature owner/delegate /// 0. `[writable]` The source account. /// 1. `[writable]` The destination account. /// 2. `[]` The source account's multisignature owner/delegate. /// 3. ..3+M `[signer]` M signer accounts. Transfer { /// The amount of tokens to transfer. amount: u64, }, /// Approves a delegate. A delegate is given the authority over /// tokens on behalf of the source account's owner. /// /// Accounts expected by this instruction: /// /// * Single owner /// 0. `[writable]` The source account. /// 1. `[]` The delegate. /// 2. `[signer]` The source account owner. /// /// * Multisignature owner /// 0. `[writable]` The source account. /// 1. `[]` The delegate. /// 2. `[]` The source account's multisignature owner. /// 3. ..3+M `[signer]` M signer accounts Approve { /// The amount of tokens the delegate is approved for. amount: u64, }, /// Revokes the delegate's authority. /// /// Accounts expected by this instruction: /// /// * Single owner /// 0. `[writable]` The source account. /// 1. `[signer]` The source account owner. /// /// * Multisignature owner /// 0. `[writable]` The source account. /// 1. `[]` The source account's multisignature owner. /// 2. ..2+M `[signer]` M signer accounts Revoke, /// Sets a new authority of a mint or account. /// /// Accounts expected by this instruction: /// /// * Single authority /// 0. `[writable]` The mint or account to change the authority of. /// 1. `[signer]` The current authority of the mint or account. /// /// * Multisignature authority /// 0. `[writable]` The mint or account to change the authority of. /// 1. `[]` The mint's or account's multisignature authority. /// 2. ..2+M `[signer]` M signer accounts SetAuthority { /// The type of authority to update. authority_type: AuthorityType, /// The new authority new_authority: COption, }, /// Mints new tokens to an account. The native mint does not support minting. /// /// Accounts expected by this instruction: /// /// * Single authority /// 0. `[writable]` The mint. /// 1. `[writable]` The account to mint tokens to. /// 2. `[signer]` The mint's minting authority. /// /// * Multisignature authority /// 0. `[writable]` The mint. /// 1. `[writable]` The account to mint tokens to. /// 2. `[]` The mint's multisignature mint-tokens authority. /// 3. ..3+M `[signer]` M signer accounts. MintTo { /// The amount of new tokens to mint. amount: u64, }, /// Burns tokens by removing them from an account. `Burn` does not support accounts /// associated with the native mint, use `CloseAccount` instead. /// /// Accounts expected by this instruction: /// /// * Single owner/delegate /// 0. `[writable]` The account to burn from. /// 1. `[writable]` The token mint. /// 2. `[signer]` The account's owner/delegate. /// /// * Multisignature owner/delegate /// 0. `[writable]` The account to burn from. /// 1. '[writable]' The token mint. /// 2. `[]` The account's multisignature owner/delegate. /// 3. ..3+M '[signer]' M signer accounts. Burn { /// The amount of tokens to burn. amount: u64, }, /// Close an account by transferring all its SOL to the destination account. /// Non-native accounts may only be closed if its token amount is zero. /// /// Accounts expected by this instruction: /// /// * Single owner /// 0. `[writable]` The account to close. /// 1. `[writable]` The destination account. /// 2. `[signer]` The account's owner. /// /// * Multisignature owner /// 0. `[writable]` The account to close. /// 1. `[writable]` The destination account. /// 2. `[]` The account's multisignature owner. /// 3. ..3+M `[signer]` M signer accounts. CloseAccount, /// Freeze an Initialized account using the Mint's freeze_authority (if set). /// /// Accounts expected by this instruction: /// /// * Single owner /// 0. `[writable]` The account to freeze. /// 1. `[]` The token mint. /// 2. `[signer]` The mint freeze authority. /// /// * Multisignature owner /// 0. `[writable]` The account to freeze. /// 1. `[]` The token mint. /// 2. `[]` The mint's multisignature freeze authority. /// 3. ..3+M `[signer]` M signer accounts. FreezeAccount, /// Thaw a Frozen account using the Mint's freeze_authority (if set). /// /// Accounts expected by this instruction: /// /// * Single owner /// 0. `[writable]` The account to freeze. /// 1. `[]` The token mint. /// 2. `[signer]` The mint freeze authority. /// /// * Multisignature owner /// 0. `[writable]` The account to freeze. /// 1. `[]` The token mint. /// 2. `[]` The mint's multisignature freeze authority. /// 3. ..3+M `[signer]` M signer accounts. ThawAccount, /// Transfers tokens from one account to another either directly or via a delegate. If this /// account is associated with the native mint then equal amounts of SOL and Tokens will be /// transferred to the destination account. /// /// This instruction differs from Transfer in that the token mint and decimals value is /// asserted by the caller. This may be useful when creating transactions offline or within a /// hardware wallet. /// /// Accounts expected by this instruction: /// /// * Single owner/delegate /// 0. `[writable]` The source account. /// 1. `[]` The token mint. /// 2. `[writable]` The destination account. /// 3. `[signer]` The source account's owner/delegate. /// /// * Multisignature owner/delegate /// 0. `[writable]` The source account. /// 1. `[]` The token mint. /// 2. `[writable]` The destination account. /// 3. `[]` The source account's multisignature owner/delegate. /// 4. ..4+M `[signer]` M signer accounts. Transfer2 { /// The amount of tokens to transfer. amount: u64, /// Expected number of base 10 digits to the right of the decimal place. decimals: u8, }, /// Approves a delegate. A delegate is given the authority over /// tokens on behalf of the source account's owner. /// /// This instruction differs from Approve in that the token mint and decimals value is asserted /// by the caller. This may be useful when creating transactions offline or within a hardware /// wallet. /// /// Accounts expected by this instruction: /// /// * Single owner /// 0. `[writable]` The source account. /// 1. `[]` The token mint. /// 2. `[]` The delegate. /// 3. `[signer]` The source account owner. /// /// * Multisignature owner /// 0. `[writable]` The source account. /// 1. `[]` The token mint. /// 2. `[]` The delegate. /// 3. `[]` The source account's multisignature owner. /// 4. ..4+M `[signer]` M signer accounts Approve2 { /// The amount of tokens the delegate is approved for. amount: u64, /// Expected number of base 10 digits to the right of the decimal place. decimals: u8, }, /// Mints new tokens to an account. The native mint does not support minting. /// /// This instruction differs from MintTo in that the decimals value is asserted by the /// caller. This may be useful when creating transactions offline or within a hardware wallet. /// /// Accounts expected by this instruction: /// /// * Single authority /// 0. `[writable]` The mint. /// 1. `[writable]` The account to mint tokens to. /// 2. `[signer]` The mint's minting authority. /// /// * Multisignature authority /// 0. `[writable]` The mint. /// 1. `[writable]` The account to mint tokens to. /// 2. `[]` The mint's multisignature mint-tokens authority. /// 3. ..3+M `[signer]` M signer accounts. MintTo2 { /// The amount of new tokens to mint. amount: u64, /// Expected number of base 10 digits to the right of the decimal place. decimals: u8, }, /// Burns tokens by removing them from an account. `Burn2` does not support accounts /// associated with the native mint, use `CloseAccount` instead. /// /// This instruction differs from Burn in that the decimals value is asserted by the caller. /// This may be useful when creating transactions offline or within a hardware wallet. /// /// Accounts expected by this instruction: /// /// * Single owner/delegate /// 0. `[writable]` The account to burn from. /// 1. `[writable]` The token mint. /// 2. `[signer]` The account's owner/delegate. /// /// * Multisignature owner/delegate /// 0. `[writable]` The account to burn from. /// 1. `[writable]` The token mint. /// 2. `[]` The account's multisignature owner/delegate. /// 3. ..3+M `[signer]` M signer accounts. Burn2 { /// The amount of tokens to burn. amount: u64, /// Expected number of base 10 digits to the right of the decimal place. decimals: u8, }, } impl TokenInstruction { /// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html). pub fn unpack(input: &[u8]) -> Result { 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.get(0).ok_or(InvalidInstruction)?; Self::InitializeMultisig { m } } 3 | 4 | 7 | 8 => { let amount = rest .get(..8) .and_then(|slice| slice.try_into().ok()) .map(u64::from_le_bytes) .ok_or(InvalidInstruction)?; match tag { 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, rest) = rest.split_at(8); let amount = amount .try_into() .ok() .map(u64::from_le_bytes) .ok_or(InvalidInstruction)?; let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; Self::Transfer2 { amount, decimals } } 13 => { let (amount, rest) = rest.split_at(8); let amount = amount .try_into() .ok() .map(u64::from_le_bytes) .ok_or(InvalidInstruction)?; let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; Self::Approve2 { amount, decimals } } 14 => { let (amount, rest) = rest.split_at(8); let amount = amount .try_into() .ok() .map(u64::from_le_bytes) .ok_or(InvalidInstruction)?; let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; Self::MintTo2 { amount, decimals } } 15 => { let (amount, rest) = rest.split_at(8); let amount = amount .try_into() .ok() .map(u64::from_le_bytes) .ok_or(InvalidInstruction)?; let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; Self::Burn2 { amount, decimals } } _ => return Err(TokenError::InvalidInstruction.into()), }) } /// Packs a [TokenInstruction](enum.TokenInstruction.html) into a byte buffer. pub fn pack(&self) -> Vec { let mut buf = Vec::with_capacity(size_of::()); 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); } &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::Transfer2 { amount, decimals } => { buf.push(12); buf.extend_from_slice(&amount.to_le_bytes()); buf.push(decimals); } &Self::Approve2 { amount, decimals } => { buf.push(13); buf.extend_from_slice(&amount.to_le_bytes()); buf.push(decimals); } &Self::MintTo2 { amount, decimals } => { buf.push(14); buf.extend_from_slice(&amount.to_le_bytes()); buf.push(decimals); } &Self::Burn2 { amount, decimals } => { buf.push(15); buf.extend_from_slice(&amount.to_le_bytes()); buf.push(decimals); } }; buf } fn unpack_pubkey(input: &[u8]) -> Result<(Pubkey, &[u8]), ProgramError> { if input.len() >= 32 { let (key, rest) = input.split_at(32); let pk = Pubkey::new(key); Ok((pk, rest)) } else { Err(TokenError::InvalidInstruction.into()) } } fn unpack_pubkey_option(input: &[u8]) -> Result<(COption, &[u8]), ProgramError> { match input.split_first() { Option::Some((&0, rest)) => Ok((COption::None, rest)), Option::Some((&1, rest)) if rest.len() >= 32 => { let (key, rest) = rest.split_at(32); let pk = Pubkey::new(key); Ok((COption::Some(pk), rest)) } _ => Err(TokenError::InvalidInstruction.into()), } } fn pack_pubkey_option(value: &COption, buf: &mut Vec) { match *value { COption::Some(ref key) => { buf.push(1); buf.extend_from_slice(&key.to_bytes()); } COption::None => buf.push(0), } } } /// Specifies the authority type for SetAuthority instructions #[repr(u8)] #[derive(Clone, Debug, PartialEq)] pub enum AuthorityType { /// Authority to mint new tokens MintTokens, /// Authority to freeze any account associated with the Mint FreezeAccount, /// Holder of a given token account AccountHolder, /// Authority to close a token account CloseAccount, } impl AuthorityType { fn into(&self) -> u8 { match self { AuthorityType::MintTokens => 0, AuthorityType::FreezeAccount => 1, AuthorityType::AccountHolder => 2, AuthorityType::CloseAccount => 3, } } fn from(index: u8) -> Result { match index { 0 => Ok(AuthorityType::MintTokens), 1 => Ok(AuthorityType::FreezeAccount), 2 => Ok(AuthorityType::AccountHolder), 3 => Ok(AuthorityType::CloseAccount), _ => Err(TokenError::InvalidInstruction.into()), } } } /// Creates a `InitializeMint` instruction. pub fn initialize_mint( token_program_id: &Pubkey, mint_pubkey: &Pubkey, mint_authority_pubkey: &Pubkey, freeze_authority_pubkey: Option<&Pubkey>, decimals: u8, ) -> Result { 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, }) } /// Creates a `InitializeAccount` instruction. pub fn initialize_account( token_program_id: &Pubkey, account_pubkey: &Pubkey, mint_pubkey: &Pubkey, owner_pubkey: &Pubkey, ) -> Result { let data = TokenInstruction::InitializeAccount.pack(); // TODO do we need to return result? 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, }) } /// Creates a `InitializeMultisig` instruction. pub fn initialize_multisig( token_program_id: &Pubkey, multisig_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], m: u8, ) -> Result { 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, }) } /// Creates a `Transfer` instruction. pub fn transfer( token_program_id: &Pubkey, source_pubkey: &Pubkey, destination_pubkey: &Pubkey, authority_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], amount: u64, ) -> Result { 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, }) } /// Creates an `Approve` instruction. pub fn approve( token_program_id: &Pubkey, source_pubkey: &Pubkey, delegate_pubkey: &Pubkey, owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], amount: u64, ) -> Result { 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, }) } /// Creates a `Revoke` instruction. pub fn revoke( token_program_id: &Pubkey, source_pubkey: &Pubkey, owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], ) -> Result { 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, }) } /// Creates a `SetAuthority` instruction. 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 { 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, }) } /// Creates a `MintTo` instruction. pub fn mint_to( token_program_id: &Pubkey, mint_pubkey: &Pubkey, account_pubkey: &Pubkey, owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], amount: u64, ) -> Result { 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, }) } /// Creates a `Burn` instruction. pub fn burn( token_program_id: &Pubkey, account_pubkey: &Pubkey, mint_pubkey: &Pubkey, authority_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], amount: u64, ) -> Result { 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, }) } /// Creates a `CloseAccount` instruction. pub fn close_account( token_program_id: &Pubkey, account_pubkey: &Pubkey, destination_pubkey: &Pubkey, owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], ) -> Result { 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, }) } /// Creates a `FreezeAccount` instruction. pub fn freeze_account( token_program_id: &Pubkey, account_pubkey: &Pubkey, mint_pubkey: &Pubkey, owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], ) -> Result { 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, }) } /// Creates a `ThawAccount` instruction. pub fn thaw_account( token_program_id: &Pubkey, account_pubkey: &Pubkey, mint_pubkey: &Pubkey, owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], ) -> Result { 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, }) } /// Creates a `Transfer2` instruction. #[allow(clippy::too_many_arguments)] pub fn transfer2( token_program_id: &Pubkey, source_pubkey: &Pubkey, mint_pubkey: &Pubkey, destination_pubkey: &Pubkey, authority_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], amount: u64, decimals: u8, ) -> Result { let data = TokenInstruction::Transfer2 { 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, }) } /// Creates an `Approve2` instruction. #[allow(clippy::too_many_arguments)] pub fn approve2( token_program_id: &Pubkey, source_pubkey: &Pubkey, mint_pubkey: &Pubkey, delegate_pubkey: &Pubkey, owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], amount: u64, decimals: u8, ) -> Result { let data = TokenInstruction::Approve2 { 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, }) } /// Creates a `MintTo2` instruction. pub fn mint_to2( token_program_id: &Pubkey, mint_pubkey: &Pubkey, account_pubkey: &Pubkey, owner_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], amount: u64, decimals: u8, ) -> Result { let data = TokenInstruction::MintTo2 { 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, }) } /// Creates a `Burn2` instruction. pub fn burn2( token_program_id: &Pubkey, account_pubkey: &Pubkey, mint_pubkey: &Pubkey, authority_pubkey: &Pubkey, signer_pubkeys: &[&Pubkey], amount: u64, decimals: u8, ) -> Result { let data = TokenInstruction::Burn2 { 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, }) } /// Utility function that checks index is between MIN_SIGNERS and MAX_SIGNERS pub fn is_valid_signer_index(index: usize) -> bool { !(index < MIN_SIGNERS || index > MAX_SIGNERS) } #[cfg(test)] mod test { use super::*; #[test] fn test_instruction_packing() { let check = TokenInstruction::InitializeMint { decimals: 2, mint_authority: Pubkey::new(&[1u8; 32]), freeze_authority: COption::None, }; 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 check = TokenInstruction::InitializeMint { decimals: 2, mint_authority: Pubkey::new(&[2u8; 32]), freeze_authority: COption::Some(Pubkey::new(&[3u8; 32])), }; 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 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 check = TokenInstruction::InitializeMultisig { m: 1 }; 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 check = TokenInstruction::Transfer { amount: 1 }; 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 check = TokenInstruction::Approve { amount: 1 }; 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 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 check = TokenInstruction::SetAuthority { authority_type: AuthorityType::FreezeAccount, new_authority: COption::Some(Pubkey::new(&[4u8; 32])), }; 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 check = TokenInstruction::MintTo { amount: 1 }; 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 check = TokenInstruction::Burn { amount: 1 }; 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 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 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 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 check = TokenInstruction::Transfer2 { amount: 1, decimals: 2, }; 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 check = TokenInstruction::Approve2 { amount: 1, decimals: 2, }; 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 check = TokenInstruction::MintTo2 { amount: 1, decimals: 2, }; 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 check = TokenInstruction::Burn2 { amount: 1, decimals: 2, }; 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); } }