Browse Source

Add Transfer2/Approve2/MintTo2/Burn2 instructions for improved hardware wallet support

Michael Vines 5 years ago
parent
commit
84c8391282
3 changed files with 647 additions and 14 deletions
  1. 3 0
      program/src/error.rs
  2. 384 1
      program/src/instruction.rs
  3. 260 13
      program/src/processor.rs

+ 3 - 0
program/src/error.rs

@@ -61,6 +61,9 @@ pub enum TokenError {
     /// Account is frozen; all account operations will fail
     #[error("Account is frozen")]
     AccountFrozen,
+    /// Mint decimals mismatch between the client and mint
+    #[error("The provided decimals value different from the Mint decimals")]
+    MintDecimalsMismatch,
 }
 impl From<TokenError> for ProgramError {
     fn from(e: TokenError) -> Self {

+ 384 - 1
program/src/instruction.rs

@@ -94,7 +94,7 @@ pub enum TokenInstruction {
     },
     /// 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
@@ -225,6 +225,109 @@ pub enum TokenInstruction {
     ///   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).
@@ -323,6 +426,67 @@ impl TokenInstruction {
             9 => Self::CloseAccount,
             10 => Self::FreezeAccount,
             11 => Self::ThawAccount,
+            12 => {
+                if input.len() < size_of::<u8>() + size_of::<u64>() + size_of::<u8>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                let mut input_len = 0;
+                input_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) };
+                input_len += size_of::<u64>();
+
+                let decimals = unsafe { *(&input[input_len] as *const u8) };
+
+                Self::Transfer2 { amount, decimals }
+            }
+            13 => {
+                if input.len() < size_of::<u8>() + size_of::<u64>() + size_of::<u8>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                let mut input_len = 0;
+                input_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) };
+                input_len += size_of::<u64>();
+
+                let decimals = unsafe { *(&input[input_len] as *const u8) };
+
+                Self::Approve2 { amount, decimals }
+            }
+            14 => {
+                if input.len() < size_of::<u8>() + size_of::<u64>() + size_of::<u8>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                let mut input_len = 0;
+                input_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) };
+                input_len += size_of::<u64>();
+
+                let decimals = unsafe { *(&input[input_len] as *const u8) };
+
+                Self::MintTo2 { amount, decimals }
+            }
+            15 => {
+                if input.len() < size_of::<u8>() + size_of::<u64>() + size_of::<u8>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                let mut input_len = 0;
+                input_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let amount = unsafe { *(&input[input_len] as *const u8 as *const u64) };
+                input_len += size_of::<u64>();
+
+                let decimals = unsafe { *(&input[input_len] as *const u8) };
+
+                Self::Burn2 { amount, decimals }
+            }
+
             _ => return Err(TokenError::InvalidInstruction.into()),
         })
     }
@@ -428,6 +592,59 @@ impl TokenInstruction {
                 output[output_len] = 11;
                 output_len += size_of::<u8>();
             }
+            Self::Transfer2 { amount, decimals } => {
+                output[output_len] = 12;
+                output_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) };
+                *value = *amount;
+                output_len += size_of::<u64>();
+
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8) };
+                *value = *decimals;
+                output_len += size_of::<u8>();
+            }
+            Self::Approve2 { amount, decimals } => {
+                output[output_len] = 13;
+                output_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) };
+                *value = *amount;
+                output_len += size_of::<u64>();
+
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8) };
+                *value = *decimals;
+                output_len += size_of::<u8>();
+            }
+            Self::MintTo2 { amount, decimals } => {
+                output[output_len] = 14;
+                output_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) };
+                *value = *amount;
+                output_len += size_of::<u64>();
+
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8) };
+                *value = *decimals;
+                output_len += size_of::<u8>();
+            }
+
+            Self::Burn2 { amount, decimals } => {
+                output[output_len] = 15;
+                output_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) };
+                *value = *amount;
+                output_len += size_of::<u64>();
+
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8) };
+                *value = *decimals;
+                output_len += size_of::<u8>();
+            }
         }
 
         output.truncate(output_len);
@@ -809,6 +1026,132 @@ pub fn thaw_account(
     })
 }
 
+/// 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<Instruction, ProgramError> {
+    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(**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<Instruction, ProgramError> {
+    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(**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<Instruction, ProgramError> {
+    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(**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<Instruction, ProgramError> {
+    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(**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)
@@ -928,5 +1271,45 @@ mod test {
         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().unwrap();
+        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().unwrap();
+        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().unwrap();
+        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().unwrap();
+        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);
     }
 }

+ 260 - 13
program/src/processor.rs

@@ -137,9 +137,18 @@ impl Processor {
         program_id: &Pubkey,
         accounts: &[AccountInfo],
         amount: u64,
+        expected_decimals: Option<u8>,
     ) -> ProgramResult {
         let account_info_iter = &mut accounts.iter();
+
         let source_account_info = next_account_info(account_info_iter)?;
+
+        let expected_mint_info = if let Some(expected_decimals) = expected_decimals {
+            Some((next_account_info(account_info_iter)?, expected_decimals))
+        } else {
+            None
+        };
+
         let dest_account_info = next_account_info(account_info_iter)?;
         let authority_info = next_account_info(account_info_iter)?;
 
@@ -162,6 +171,19 @@ impl Processor {
             return Err(TokenError::AccountFrozen.into());
         }
 
+        if let Some((mint_account_info, expected_decimals)) = expected_mint_info {
+            if source_account.mint != *mint_account_info.key {
+                return Err(TokenError::MintMismatch.into());
+            }
+
+            let mut mint_info_data = mint_account_info.data.borrow_mut();
+            let mint: &Mint = state::unpack_unchecked(&mut mint_info_data)?;
+
+            if expected_decimals != mint.decimals {
+                return Err(TokenError::MintDecimalsMismatch.into());
+            }
+        }
+
         match source_account.delegate {
             COption::Some(ref delegate) if authority_info.key == delegate => {
                 Self::validate_owner(
@@ -218,19 +240,39 @@ impl Processor {
         program_id: &Pubkey,
         accounts: &[AccountInfo],
         amount: u64,
+        expected_decimals: Option<u8>,
     ) -> ProgramResult {
         let account_info_iter = &mut accounts.iter();
+
         let source_account_info = next_account_info(account_info_iter)?;
+        let expected_mint_info = if let Some(expected_decimals) = expected_decimals {
+            Some((next_account_info(account_info_iter)?, expected_decimals))
+        } else {
+            None
+        };
+        let delegate_info = next_account_info(account_info_iter)?;
+        let owner_info = next_account_info(account_info_iter)?;
 
         let mut source_data = source_account_info.data.borrow_mut();
         let mut source_account: &mut Account = state::unpack(&mut source_data)?;
-        let delegate_info = next_account_info(account_info_iter)?;
-        let owner_info = next_account_info(account_info_iter)?;
 
         if source_account.is_frozen() {
             return Err(TokenError::AccountFrozen.into());
         }
 
+        if let Some((mint_account_info, expected_decimals)) = expected_mint_info {
+            if source_account.mint != *mint_account_info.key {
+                return Err(TokenError::MintMismatch.into());
+            }
+
+            let mut mint_info_data = mint_account_info.data.borrow_mut();
+            let mint: &Mint = state::unpack_unchecked(&mut mint_info_data)?;
+
+            if expected_decimals != mint.decimals {
+                return Err(TokenError::MintDecimalsMismatch.into());
+            }
+        }
+
         Self::validate_owner(
             program_id,
             &source_account.owner,
@@ -367,6 +409,7 @@ impl Processor {
         program_id: &Pubkey,
         accounts: &[AccountInfo],
         amount: u64,
+        expected_decimals: Option<u8>,
     ) -> ProgramResult {
         let account_info_iter = &mut accounts.iter();
         let mint_info = next_account_info(account_info_iter)?;
@@ -390,6 +433,12 @@ impl Processor {
         let mut mint_info_data = mint_info.data.borrow_mut();
         let mint: &mut Mint = state::unpack(&mut mint_info_data)?;
 
+        if let Some(expected_decimals) = expected_decimals {
+            if expected_decimals != mint.decimals {
+                return Err(TokenError::MintDecimalsMismatch.into());
+            }
+        }
+
         match mint.mint_authority {
             COption::Some(mint_authority) => {
                 Self::validate_owner(
@@ -422,8 +471,10 @@ impl Processor {
         program_id: &Pubkey,
         accounts: &[AccountInfo],
         amount: u64,
+        expected_decimals: Option<u8>,
     ) -> ProgramResult {
         let account_info_iter = &mut accounts.iter();
+
         let source_account_info = next_account_info(account_info_iter)?;
         let mint_info = next_account_info(account_info_iter)?;
         let authority_info = next_account_info(account_info_iter)?;
@@ -447,6 +498,12 @@ impl Processor {
             return Err(TokenError::AccountFrozen.into());
         }
 
+        if let Some(expected_decimals) = expected_decimals {
+            if expected_decimals != mint.decimals {
+                return Err(TokenError::MintDecimalsMismatch.into());
+            }
+        }
+
         match source_account.delegate {
             COption::Some(ref delegate) if authority_info.key == delegate => {
                 Self::validate_owner(
@@ -596,11 +653,11 @@ impl Processor {
             }
             TokenInstruction::Transfer { amount } => {
                 info!("Instruction: Transfer");
-                Self::process_transfer(program_id, accounts, amount)
+                Self::process_transfer(program_id, accounts, amount, None)
             }
             TokenInstruction::Approve { amount } => {
                 info!("Instruction: Approve");
-                Self::process_approve(program_id, accounts, amount)
+                Self::process_approve(program_id, accounts, amount, None)
             }
             TokenInstruction::Revoke => {
                 info!("Instruction: Revoke");
@@ -615,11 +672,11 @@ impl Processor {
             }
             TokenInstruction::MintTo { amount } => {
                 info!("Instruction: MintTo");
-                Self::process_mint_to(program_id, accounts, amount)
+                Self::process_mint_to(program_id, accounts, amount, None)
             }
             TokenInstruction::Burn { amount } => {
                 info!("Instruction: Burn");
-                Self::process_burn(program_id, accounts, amount)
+                Self::process_burn(program_id, accounts, amount, None)
             }
             TokenInstruction::CloseAccount => {
                 info!("Instruction: CloseAccount");
@@ -633,6 +690,22 @@ impl Processor {
                 info!("Instruction: FreezeAccount");
                 Self::process_toggle_freeze_account(program_id, accounts, false)
             }
+            TokenInstruction::Transfer2 { amount, decimals } => {
+                info!("Instruction: Transfer");
+                Self::process_transfer(program_id, accounts, amount, Some(decimals))
+            }
+            TokenInstruction::Approve2 { amount, decimals } => {
+                info!("Instruction: Approve");
+                Self::process_approve(program_id, accounts, amount, Some(decimals))
+            }
+            TokenInstruction::MintTo2 { amount, decimals } => {
+                info!("Instruction: MintTo");
+                Self::process_mint_to(program_id, accounts, amount, Some(decimals))
+            }
+            TokenInstruction::Burn2 { amount, decimals } => {
+                info!("Instruction: Burn");
+                Self::process_burn(program_id, accounts, amount, Some(decimals))
+            }
         }
     }
 
@@ -706,6 +779,9 @@ impl PrintProgramError for TokenError {
             }
             TokenError::MintCannotFreeze => info!("Error: This token mint cannot freeze accounts"),
             TokenError::AccountFrozen => info!("Error: Account is frozen"),
+            TokenError::MintDecimalsMismatch => {
+                info!("Error: decimals different from the Mint decimals")
+            }
         }
     }
 }
@@ -717,10 +793,7 @@ solana_sdk::program_stubs!();
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::instruction::{
-        approve, burn, close_account, freeze_account, initialize_account, initialize_mint,
-        initialize_multisig, mint_to, revoke, set_authority, thaw_account, transfer, MAX_SIGNERS,
-    };
+    use crate::instruction::*;
     use solana_sdk::{
         account::Account as SolanaAccount, account_info::create_is_signer_account_infos,
         clock::Epoch, instruction::Instruction, sysvar::rent,
@@ -1118,19 +1191,69 @@ mod tests {
         )
         .unwrap();
 
-        // transfer rest
+        // incorrect decimals
+        assert_eq!(
+            Err(TokenError::MintDecimalsMismatch.into()),
+            do_process_instruction(
+                transfer2(
+                    &program_id,
+                    &account2_key,
+                    &mint_key,
+                    &account_key,
+                    &owner_key,
+                    &[],
+                    1,
+                    10 // <-- incorrect decimals
+                )
+                .unwrap(),
+                vec![
+                    &mut account2_account,
+                    &mut mint_account,
+                    &mut account_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // incorrect mint
+        assert_eq!(
+            Err(TokenError::MintMismatch.into()),
+            do_process_instruction(
+                transfer2(
+                    &program_id,
+                    &account2_key,
+                    &account3_key, // <-- incorrect mint
+                    &account_key,
+                    &owner_key,
+                    &[],
+                    1,
+                    2
+                )
+                .unwrap(),
+                vec![
+                    &mut account2_account,
+                    &mut account3_account, // <-- incorrect mint
+                    &mut account_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+        // transfer rest with explicit decimals
         do_process_instruction(
-            transfer(
+            transfer2(
                 &program_id,
                 &account2_key,
+                &mint_key,
                 &account_key,
                 &owner_key,
                 &[],
                 500,
+                2,
             )
             .unwrap(),
             vec![
                 &mut account2_account,
+                &mut mint_account,
                 &mut account_account,
                 &mut owner_account,
             ],
@@ -1355,9 +1478,47 @@ mod tests {
         )
         .unwrap();
 
+        // mint to 2, with incorrect decimals
+        assert_eq!(
+            Err(TokenError::MintDecimalsMismatch.into()),
+            do_process_instruction(
+                mint_to2(
+                    &program_id,
+                    &mint_key,
+                    &account_key,
+                    &owner_key,
+                    &[],
+                    42,
+                    decimals + 1
+                )
+                .unwrap(),
+                vec![&mut mint_account, &mut account_account, &mut owner_account],
+            )
+        );
+
         let _: &mut Mint = state::unpack(&mut mint_account.data).unwrap();
         let dest_account: &mut Account = state::unpack(&mut account_account.data).unwrap();
         assert_eq!(dest_account.amount, 42);
+
+        // mint to 2
+        do_process_instruction(
+            mint_to2(
+                &program_id,
+                &mint_key,
+                &account_key,
+                &owner_key,
+                &[],
+                42,
+                decimals,
+            )
+            .unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        let _: &mut Mint = state::unpack(&mut mint_account.data).unwrap();
+        let dest_account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(dest_account.amount, 84);
     }
 
     #[test]
@@ -1481,6 +1642,76 @@ mod tests {
         )
         .unwrap();
 
+        // approve delegate 2, with incorrect decimals
+        assert_eq!(
+            Err(TokenError::MintDecimalsMismatch.into()),
+            do_process_instruction(
+                approve2(
+                    &program_id,
+                    &account_key,
+                    &mint_key,
+                    &delegate_key,
+                    &owner_key,
+                    &[],
+                    100,
+                    0 // <-- incorrect decimals
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut mint_account,
+                    &mut delegate_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // approve delegate 2, with incorrect mint
+        assert_eq!(
+            Err(TokenError::MintMismatch.into()),
+            do_process_instruction(
+                approve2(
+                    &program_id,
+                    &account_key,
+                    &account2_key, // <-- bad mint
+                    &delegate_key,
+                    &owner_key,
+                    &[],
+                    100,
+                    0
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account2_account, // <-- bad mint
+                    &mut delegate_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // approve delegate 2
+        do_process_instruction(
+            approve2(
+                &program_id,
+                &account_key,
+                &mint_key,
+                &delegate_key,
+                &owner_key,
+                &[],
+                100,
+                2,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut mint_account,
+                &mut delegate_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
         // revoke delegate
         do_process_instruction(
             revoke(&program_id, &account_key, &owner_key, &[]).unwrap(),
@@ -2136,7 +2367,23 @@ mod tests {
 
         // burn
         do_process_instruction(
-            burn(&program_id, &account_key, &mint_key, &owner_key, &[], 42).unwrap(),
+            burn(&program_id, &account_key, &mint_key, &owner_key, &[], 21).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // burn2, with incorrect decimals
+        assert_eq!(
+            Err(TokenError::MintDecimalsMismatch.into()),
+            do_process_instruction(
+                burn2(&program_id, &account_key, &mint_key, &owner_key, &[], 21, 3).unwrap(),
+                vec![&mut account_account, &mut mint_account, &mut owner_account],
+            )
+        );
+
+        // burn2
+        do_process_instruction(
+            burn2(&program_id, &account_key, &mint_key, &owner_key, &[], 21, 2).unwrap(),
             vec![&mut account_account, &mut mint_account, &mut owner_account],
         )
         .unwrap();