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
This commit is contained in:
Trent Nelson 2021-01-28 16:31:35 -07:00 committed by Trent Nelson
parent 99d7ba1563
commit 00f28eeb0c
2 changed files with 130 additions and 5 deletions

View File

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

View File

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