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

This commit is contained in:
Michael Vines 2020-08-27 11:12:49 -07:00 committed by mergify[bot]
parent 2e6162abbd
commit b60eb8fbee
3 changed files with 648 additions and 15 deletions

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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 mut source_data = source_account_info.data.borrow_mut();
let mut source_account: &mut Account = state::unpack(&mut source_data)?;
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)?;
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();