From 00f28eeb0c9740b0d5a7332638ccea6caf8fda2e Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Thu, 28 Jan 2021 16:31:35 -0700 Subject: [PATCH] token: Add InitializeAccount2 instruction Passes the owner as instruction data rather than on the accounts list, improving CPI ergonomics where the owner's `AccountInfo` isn't otherwise required --- token/program/src/instruction.rs | 59 ++++++++++++++++++++++++- token/program/src/processor.rs | 76 ++++++++++++++++++++++++++++++-- 2 files changed, 130 insertions(+), 5 deletions(-) diff --git a/token/program/src/instruction.rs b/token/program/src/instruction.rs index 21d712cd..063eb7c4 100644 --- a/token/program/src/instruction.rs +++ b/token/program/src/instruction.rs @@ -349,6 +349,20 @@ pub enum TokenInstruction { /// Expected number of base 10 digits to the right of the decimal place. decimals: u8, }, + /// Like InitializeAccount, but the owner pubkey is passed via instruction data + /// rather than the accounts list. This variant may be preferable when using + /// Cross Program Invocation from an instruction that does not need the owner's + /// `AccountInfo` otherwise. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The account to initialize. + /// 1. `[]` The mint this account will be associated with. + /// 3. `[]` Rent sysvar + InitializeAccount2 { + /// The new account's owner/multisignature. + owner: Pubkey, + }, } impl TokenInstruction { /// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html). @@ -446,6 +460,10 @@ impl TokenInstruction { Self::BurnChecked { amount, decimals } } + 16 => { + let (owner, _rest) = Self::unpack_pubkey(rest)?; + Self::InitializeAccount2 { owner } + } _ => return Err(TokenError::InvalidInstruction.into()), }) @@ -518,6 +536,10 @@ impl TokenInstruction { buf.extend_from_slice(&amount.to_le_bytes()); buf.push(decimals); } + &Self::InitializeAccount2 { owner } => { + buf.push(16); + buf.extend_from_slice(owner.as_ref()); + } }; buf } @@ -625,7 +647,7 @@ pub fn initialize_account( mint_pubkey: &Pubkey, owner_pubkey: &Pubkey, ) -> Result { - let data = TokenInstruction::InitializeAccount.pack(); // TODO do we need to return result? + let data = TokenInstruction::InitializeAccount.pack(); let accounts = vec![ AccountMeta::new(*account_pubkey, false), @@ -641,6 +663,31 @@ pub fn initialize_account( }) } +/// Creates a `InitializeAccount2` instruction. +pub fn initialize_account2( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + owner_pubkey: &Pubkey, +) -> Result { + let data = TokenInstruction::InitializeAccount2 { + owner: *owner_pubkey, + } + .pack(); + + let accounts = vec![ + AccountMeta::new(*account_pubkey, false), + AccountMeta::new_readonly(*mint_pubkey, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ]; + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + /// Creates a `InitializeMultisig` instruction. pub fn initialize_multisig( token_program_id: &Pubkey, @@ -1214,5 +1261,15 @@ mod test { assert_eq!(packed, expect); let unpacked = TokenInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeAccount2 { + owner: Pubkey::new(&[2u8; 32]), + }; + let packed = check.pack(); + let mut expect = vec![16u8]; + expect.extend_from_slice(&[2u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); } } diff --git a/token/program/src/processor.rs b/token/program/src/processor.rs index 4ea40ae6..5d22ca5f 100644 --- a/token/program/src/processor.rs +++ b/token/program/src/processor.rs @@ -52,12 +52,18 @@ impl Processor { Ok(()) } - /// Processes an [InitializeAccount](enum.TokenInstruction.html) instruction. - pub fn process_initialize_account(accounts: &[AccountInfo]) -> ProgramResult { + fn _process_initialize_account( + accounts: &[AccountInfo], + owner: Option<&Pubkey>, + ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let new_account_info = next_account_info(account_info_iter)?; let mint_info = next_account_info(account_info_iter)?; - let owner_info = next_account_info(account_info_iter)?; + let owner = if let Some(owner) = owner { + owner + } else { + 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)?)?; @@ -76,7 +82,7 @@ impl Processor { } account.mint = *mint_info.key; - account.owner = *owner_info.key; + account.owner = *owner; account.delegate = COption::None; account.delegated_amount = 0; account.state = AccountState::Initialized; @@ -97,6 +103,16 @@ impl Processor { Ok(()) } + /// Processes an [InitializeAccount](enum.TokenInstruction.html) instruction. + pub fn process_initialize_account(accounts: &[AccountInfo]) -> ProgramResult { + Self::_process_initialize_account(accounts, None) + } + + /// Processes an [InitializeAccount2](enum.TokenInstruction.html) instruction. + pub fn process_initialize_account2(accounts: &[AccountInfo], owner: Pubkey) -> ProgramResult { + Self::_process_initialize_account(accounts, Some(&owner)) + } + /// Processes a [InitializeMultisig](enum.TokenInstruction.html) instruction. pub fn process_initialize_multisig(accounts: &[AccountInfo], m: u8) -> ProgramResult { let account_info_iter = &mut accounts.iter(); @@ -641,6 +657,10 @@ impl Processor { msg!("Instruction: InitializeAccount"); Self::process_initialize_account(accounts) } + TokenInstruction::InitializeAccount2 { owner } => { + msg!("Instruction: InitializeAccount2"); + Self::process_initialize_account2(accounts, owner) + } TokenInstruction::InitializeMultisig { m } => { msg!("Instruction: InitializeMultisig"); Self::process_initialize_multisig(accounts, m) @@ -5660,4 +5680,52 @@ mod tests { let account = Account::unpack_unchecked(&account_account.data).unwrap(); assert_eq!(account.state, AccountState::Initialized); } + + #[test] + fn test_initialize_account2() { + let program_id = Pubkey::new_unique(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let mut account2_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(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // create mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + do_process_instruction( + initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut owner_account, + &mut rent_sysvar, + ], + ) + .unwrap(); + + do_process_instruction( + initialize_account2(&program_id, &account_key, &mint_key, &owner_key).unwrap(), + vec![&mut account2_account, &mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + assert_eq!(account_account, account2_account); + } }