Add InitializeMint2/InitializeMultisig2/InitializeAccount3 instructions

This commit is contained in:
Michael Vines 2021-08-13 11:59:38 -07:00
parent b4b9763a00
commit 44ad2ff5b4
3 changed files with 390 additions and 13 deletions

View File

@ -373,6 +373,42 @@ pub enum TokenInstruction {
///
/// 0. `[writable]` The native token account to sync with its underlying lamports.
SyncNative,
/// Like InitializeAccount2, but does not require the Rent sysvar to be provided
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The account to initialize.
/// 1. `[]` The mint this account will be associated with.
InitializeAccount3 {
/// The new account's owner/multisignature.
owner: Pubkey,
},
/// Like InitializeMultisig, but does not require the Rent sysvar to be provided
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The multisignature account to initialize.
/// 1. ..1+N. `[]` The signer accounts, must equal to N where 1 <= N <=
/// 11.
InitializeMultisig2 {
/// The number of signers (M) required to validate this multisignature
/// account.
m: u8,
},
/// Like InitializeMint, but does not require the Rent sysvar to be provided
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The mint to initialize.
///
InitializeMint2 {
/// 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<Pubkey>,
},
}
impl TokenInstruction {
/// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html).
@ -475,7 +511,24 @@ impl TokenInstruction {
Self::InitializeAccount2 { owner }
}
17 => Self::SyncNative,
18 => {
let (owner, _rest) = Self::unpack_pubkey(rest)?;
Self::InitializeAccount3 { owner }
}
19 => {
let &m = rest.get(0).ok_or(InvalidInstruction)?;
Self::InitializeMultisig2 { m }
}
20 => {
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::InitializeMint2 {
mint_authority,
freeze_authority,
decimals,
}
}
_ => return Err(TokenError::InvalidInstruction.into()),
})
}
@ -554,6 +607,24 @@ impl TokenInstruction {
&Self::SyncNative => {
buf.push(17);
}
&Self::InitializeAccount3 { owner } => {
buf.push(18);
buf.extend_from_slice(owner.as_ref());
}
&Self::InitializeMultisig2 { m } => {
buf.push(19);
buf.push(m);
}
&Self::InitializeMint2 {
ref mint_authority,
ref freeze_authority,
decimals,
} => {
buf.push(20);
buf.push(decimals);
buf.extend_from_slice(mint_authority.as_ref());
Self::pack_pubkey_option(freeze_authority, &mut buf);
}
};
buf
}
@ -655,6 +726,32 @@ pub fn initialize_mint(
})
}
/// Creates a `InitializeMint2` instruction.
pub fn initialize_mint2(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
mint_authority_pubkey: &Pubkey,
freeze_authority_pubkey: Option<&Pubkey>,
decimals: u8,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let freeze_authority = freeze_authority_pubkey.cloned().into();
let data = TokenInstruction::InitializeMint2 {
mint_authority: *mint_authority_pubkey,
freeze_authority,
decimals,
}
.pack();
let accounts = vec![AccountMeta::new(*mint_pubkey, false)];
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
/// Creates a `InitializeAccount` instruction.
pub fn initialize_account(
token_program_id: &Pubkey,
@ -705,6 +802,31 @@ pub fn initialize_account2(
})
}
/// Creates a `InitializeAccount3` instruction.
pub fn initialize_account3(
token_program_id: &Pubkey,
account_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let data = TokenInstruction::InitializeAccount3 {
owner: *owner_pubkey,
}
.pack();
let accounts = vec![
AccountMeta::new(*account_pubkey, false),
AccountMeta::new_readonly(*mint_pubkey, false),
];
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
/// Creates a `InitializeMultisig` instruction.
pub fn initialize_multisig(
token_program_id: &Pubkey,
@ -735,6 +857,35 @@ pub fn initialize_multisig(
})
}
/// Creates a `InitializeMultisig2` instruction.
pub fn initialize_multisig2(
token_program_id: &Pubkey,
multisig_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
m: u8,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
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::InitializeMultisig2 { m }.pack();
let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*multisig_pubkey, false));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, false));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}
/// Creates a `Transfer` instruction.
pub fn transfer(
token_program_id: &Pubkey,
@ -1323,5 +1474,49 @@ mod test {
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let check = TokenInstruction::InitializeAccount3 {
owner: Pubkey::new(&[2u8; 32]),
};
let packed = check.pack();
let mut expect = vec![18u8];
expect.extend_from_slice(&[2u8; 32]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let check = TokenInstruction::InitializeMultisig2 { m: 1 };
let packed = check.pack();
let expect = Vec::from([19u8, 1]);
assert_eq!(packed, expect);
let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
let check = TokenInstruction::InitializeMint2 {
decimals: 2,
mint_authority: Pubkey::new(&[1u8; 32]),
freeze_authority: COption::None,
};
let packed = check.pack();
let mut expect = Vec::from([20u8, 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::InitializeMint2 {
decimals: 2,
mint_authority: Pubkey::new(&[2u8; 32]),
freeze_authority: COption::Some(Pubkey::new(&[3u8; 32])),
};
let packed = check.pack();
let mut expect = vec![20u8, 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);
}
}

View File

@ -1,5 +1,5 @@
#![deny(missing_docs)]
#![forbid(unsafe_code)]
#![cfg_attr(not(test), forbid(unsafe_code))]
//! An ERC20-like Token program for the Solana blockchain

View File

@ -21,17 +21,21 @@ use solana_program::{
/// Program state handler.
pub struct Processor {}
impl Processor {
/// Processes an [InitializeMint](enum.TokenInstruction.html) instruction.
pub fn process_initialize_mint(
fn _process_initialize_mint(
accounts: &[AccountInfo],
decimals: u8,
mint_authority: Pubkey,
freeze_authority: COption<Pubkey>,
rent_sysvar_account: bool,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let mint_info = next_account_info(account_info_iter)?;
let mint_data_len = mint_info.data_len();
let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
let rent = if rent_sysvar_account {
Rent::from_account_info(next_account_info(account_info_iter)?)?
} else {
Rent::get()?
};
let mut mint = Mint::unpack_unchecked(&mint_info.data.borrow())?;
if mint.is_initialized {
@ -52,9 +56,30 @@ impl Processor {
Ok(())
}
/// Processes an [InitializeMint](enum.TokenInstruction.html) instruction.
pub fn process_initialize_mint(
accounts: &[AccountInfo],
decimals: u8,
mint_authority: Pubkey,
freeze_authority: COption<Pubkey>,
) -> ProgramResult {
Self::_process_initialize_mint(accounts, decimals, mint_authority, freeze_authority, true)
}
/// Processes an [InitializeMint2](enum.TokenInstruction.html) instruction.
pub fn process_initialize_mint2(
accounts: &[AccountInfo],
decimals: u8,
mint_authority: Pubkey,
freeze_authority: COption<Pubkey>,
) -> ProgramResult {
Self::_process_initialize_mint(accounts, decimals, mint_authority, freeze_authority, false)
}
fn _process_initialize_account(
accounts: &[AccountInfo],
owner: Option<&Pubkey>,
rent_sysvar_account: bool,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let new_account_info = next_account_info(account_info_iter)?;
@ -65,7 +90,11 @@ impl Processor {
next_account_info(account_info_iter)?.key
};
let new_account_info_data_len = new_account_info.data_len();
let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
let rent = if rent_sysvar_account {
Rent::from_account_info(next_account_info(account_info_iter)?)?
} else {
Rent::get()?
};
let mut account = Account::unpack_unchecked(&new_account_info.data.borrow())?;
if account.is_initialized() {
@ -105,20 +134,32 @@ impl Processor {
/// Processes an [InitializeAccount](enum.TokenInstruction.html) instruction.
pub fn process_initialize_account(accounts: &[AccountInfo]) -> ProgramResult {
Self::_process_initialize_account(accounts, None)
Self::_process_initialize_account(accounts, None, true)
}
/// Processes an [InitializeAccount2](enum.TokenInstruction.html) instruction.
pub fn process_initialize_account2(accounts: &[AccountInfo], owner: Pubkey) -> ProgramResult {
Self::_process_initialize_account(accounts, Some(&owner))
Self::_process_initialize_account(accounts, Some(&owner), true)
}
/// Processes a [InitializeMultisig](enum.TokenInstruction.html) instruction.
pub fn process_initialize_multisig(accounts: &[AccountInfo], m: u8) -> ProgramResult {
/// Processes an [InitializeAccount3](enum.TokenInstruction.html) instruction.
pub fn process_initialize_account3(accounts: &[AccountInfo], owner: Pubkey) -> ProgramResult {
Self::_process_initialize_account(accounts, Some(&owner), false)
}
fn _process_initialize_multisig(
accounts: &[AccountInfo],
m: u8,
rent_sysvar_account: bool,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let multisig_info = next_account_info(account_info_iter)?;
let multisig_info_data_len = multisig_info.data_len();
let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
let rent = if rent_sysvar_account {
Rent::from_account_info(next_account_info(account_info_iter)?)?
} else {
Rent::get()?
};
let mut multisig = Multisig::unpack_unchecked(&multisig_info.data.borrow())?;
if multisig.is_initialized {
@ -148,6 +189,16 @@ impl Processor {
Ok(())
}
/// Processes a [InitializeMultisig](enum.TokenInstruction.html) instruction.
pub fn process_initialize_multisig(accounts: &[AccountInfo], m: u8) -> ProgramResult {
Self::_process_initialize_multisig(accounts, m, true)
}
/// Processes a [InitializeMultisig2](enum.TokenInstruction.html) instruction.
pub fn process_initialize_multisig2(accounts: &[AccountInfo], m: u8) -> ProgramResult {
Self::_process_initialize_multisig(accounts, m, false)
}
/// Processes a [Transfer](enum.TokenInstruction.html) instruction.
pub fn process_transfer(
program_id: &Pubkey,
@ -688,6 +739,14 @@ impl Processor {
msg!("Instruction: InitializeMint");
Self::process_initialize_mint(accounts, decimals, mint_authority, freeze_authority)
}
TokenInstruction::InitializeMint2 {
decimals,
mint_authority,
freeze_authority,
} => {
msg!("Instruction: InitializeMint2");
Self::process_initialize_mint2(accounts, decimals, mint_authority, freeze_authority)
}
TokenInstruction::InitializeAccount => {
msg!("Instruction: InitializeAccount");
Self::process_initialize_account(accounts)
@ -696,10 +755,18 @@ impl Processor {
msg!("Instruction: InitializeAccount2");
Self::process_initialize_account2(accounts, owner)
}
TokenInstruction::InitializeAccount3 { owner } => {
msg!("Instruction: InitializeAccount3");
Self::process_initialize_account3(accounts, owner)
}
TokenInstruction::InitializeMultisig { m } => {
msg!("Instruction: InitializeMultisig");
Self::process_initialize_multisig(accounts, m)
}
TokenInstruction::InitializeMultisig2 { m } => {
msg!("Instruction: InitializeMultisig2");
Self::process_initialize_multisig2(accounts, m)
}
TokenInstruction::Transfer { amount } => {
msg!("Instruction: Transfer");
Self::process_transfer(program_id, accounts, amount, None)
@ -849,16 +916,60 @@ mod tests {
use super::*;
use crate::instruction::*;
use solana_program::{
account_info::IntoAccountInfo, clock::Epoch, instruction::Instruction, sysvar::rent,
account_info::IntoAccountInfo, clock::Epoch, instruction::Instruction, program_error,
sysvar::rent,
};
use solana_sdk::account::{
create_account_for_test, create_is_signer_account_infos, Account as SolanaAccount,
};
struct SyscallStubs {}
impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
fn sol_log(&self, _message: &str) {}
fn sol_invoke_signed(
&self,
_instruction: &Instruction,
_account_infos: &[AccountInfo],
_signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
Err(ProgramError::Custom(42)) // Not supported
}
fn sol_get_clock_sysvar(&self, _var_addr: *mut u8) -> u64 {
program_error::UNSUPPORTED_SYSVAR
}
fn sol_get_epoch_schedule_sysvar(&self, _var_addr: *mut u8) -> u64 {
program_error::UNSUPPORTED_SYSVAR
}
#[allow(deprecated)]
fn sol_get_fees_sysvar(&self, _var_addr: *mut u8) -> u64 {
program_error::UNSUPPORTED_SYSVAR
}
fn sol_get_rent_sysvar(&self, var_addr: *mut u8) -> u64 {
unsafe {
*(var_addr as *mut _ as *mut Rent) = Rent::default();
}
solana_program::entrypoint::SUCCESS
}
}
fn do_process_instruction(
instruction: Instruction,
accounts: Vec<&mut SolanaAccount>,
) -> ProgramResult {
{
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
solana_sdk::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {}));
});
}
let mut meta = instruction
.accounts
.iter()
@ -1072,6 +1183,53 @@ mod tests {
assert_eq!(mint.freeze_authority, COption::Some(owner_key));
}
#[test]
fn test_initialize_mint2() {
let program_id = crate::id();
let owner_key = Pubkey::new_unique();
let mint_key = Pubkey::new_unique();
let mut mint_account = SolanaAccount::new(42, Mint::get_packed_len(), &program_id);
let mint2_key = Pubkey::new_unique();
let mut mint2_account =
SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id);
// mint is not rent exempt
assert_eq!(
Err(TokenError::NotRentExempt.into()),
do_process_instruction(
initialize_mint2(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
vec![&mut mint_account]
)
);
mint_account.lamports = mint_minimum_balance();
// create new mint
do_process_instruction(
initialize_mint2(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
vec![&mut mint_account],
)
.unwrap();
// create twice
assert_eq!(
Err(TokenError::AlreadyInUse.into()),
do_process_instruction(
initialize_mint2(&program_id, &mint_key, &owner_key, None, 2,).unwrap(),
vec![&mut mint_account]
)
);
// create another mint that can freeze
do_process_instruction(
initialize_mint2(&program_id, &mint2_key, &owner_key, Some(&owner_key), 2).unwrap(),
vec![&mut mint2_account],
)
.unwrap();
let mint = Mint::unpack_unchecked(&mint2_account.data).unwrap();
assert_eq!(mint.freeze_authority, COption::Some(owner_key));
}
#[test]
fn test_initialize_mint_account() {
let program_id = crate::id();
@ -4216,6 +4374,17 @@ mod tests {
],
)
);
assert_eq!(
Err(TokenError::NotRentExempt.into()),
do_process_instruction(
initialize_multisig2(&program_id, &multisig_key, &[&signer_keys[0]], 1).unwrap(),
vec![
&mut multisig_account,
&mut rent_sysvar,
&mut account_info_iter.next().unwrap(),
],
)
);
multisig_account.lamports = multisig_minimum_balance();
@ -5806,7 +5975,7 @@ mod tests {
}
#[test]
fn test_initialize_account2() {
fn test_initialize_account2_and_3() {
let program_id = crate::id();
let account_key = Pubkey::new_unique();
let mut account_account = SolanaAccount::new(
@ -5819,6 +5988,11 @@ mod tests {
Account::get_packed_len(),
&program_id,
);
let mut account3_account = SolanaAccount::new(
account_minimum_balance(),
Account::get_packed_len(),
&program_id,
);
let owner_key = Pubkey::new_unique();
let mut owner_account = SolanaAccount::default();
let mint_key = Pubkey::new_unique();
@ -5851,6 +6025,14 @@ mod tests {
.unwrap();
assert_eq!(account_account, account2_account);
do_process_instruction(
initialize_account3(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
vec![&mut account3_account, &mut mint_account, &mut rent_sysvar],
)
.unwrap();
assert_eq!(account_account, account3_account);
}
#[test]