Add Transfer2/Approve2/MintTo2/Burn2 instructions for improved hardware wallet support
This commit is contained in:
parent
2e6162abbd
commit
b60eb8fbee
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
transfer(
|
||||
transfer2(
|
||||
&program_id,
|
||||
&account2_key,
|
||||
&mint_key,
|
||||
&account_key,
|
||||
&owner_key,
|
||||
&[],
|
||||
500,
|
||||
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(
|
||||
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();
|
||||
|
|
Loading…
Reference in New Issue