Browse Source

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
Trent Nelson 4 years ago
parent
commit
6b6610dc6e
2 changed files with 130 additions and 5 deletions
  1. 58 1
      program/src/instruction.rs
  2. 72 4
      program/src/processor.rs

+ 58 - 1
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<Instruction, ProgramError> {
-    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<Instruction, ProgramError> {
+    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);
     }
 }

+ 72 - 4
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);
+    }
 }