From bf13fb4c4b5670c10a77aeee4bd4c033a1ffbedb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Sun, 10 Apr 2022 09:55:37 +0200 Subject: [PATCH] Remove `KeyedAccount` in builtin program "stake" (#24210) * Inline keyed_account_at_index() in all instructions of stake which have more than one KeyedAccount parameter, because these could cause a borrow collision. * Uses transaction_context.get_key_of_account_at_index() in stake. * Refactors stake::config::from to use BorrowedAccount instead of ReadableAccount. * Replaces KeyedAccount by BorrowedAccount in stake. --- programs/stake/src/config.rs | 16 +- programs/stake/src/stake_instruction.rs | 243 +++++++++++++--------- programs/stake/src/stake_state.rs | 263 ++++++++++++++++-------- 3 files changed, 327 insertions(+), 195 deletions(-) diff --git a/programs/stake/src/config.rs b/programs/stake/src/config.rs index a51e22fe46..1b6b138051 100644 --- a/programs/stake/src/config.rs +++ b/programs/stake/src/config.rs @@ -12,11 +12,12 @@ use { account::{AccountSharedData, ReadableAccount, WritableAccount}, genesis_config::GenesisConfig, stake::config::{self, Config}, + transaction_context::BorrowedAccount, }, }; -pub fn from(account: &T) -> Option { - get_config_data(account.data()) +pub fn from(account: &BorrowedAccount) -> Option { + get_config_data(account.get_data()) .ok() .and_then(|data| deserialize(data).ok()) } @@ -35,14 +36,3 @@ pub fn add_genesis_account(genesis_config: &mut GenesisConfig) -> u64 { lamports } - -#[cfg(test)] -mod tests { - use {super::*, std::cell::RefCell}; - - #[test] - fn test() { - let account = RefCell::new(create_account(0, &Config::default())); - assert_eq!(from(&account.borrow()), Some(Config::default())); - } -} diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index ac563f9669..7b9e85079b 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -13,17 +13,38 @@ use { solana_sdk::{ feature_set, instruction::InstructionError, - keyed_account::keyed_account_at_index, program_utils::limited_deserialize, + pubkey::Pubkey, stake::{ instruction::{LockupArgs, StakeInstruction}, program::id, state::{Authorized, Lockup}, }, sysvar::clock::Clock, + transaction_context::{InstructionContext, TransactionContext}, }, }; +fn get_optional_pubkey<'a>( + transaction_context: &'a TransactionContext, + instruction_context: &'a InstructionContext, + index_in_instruction: usize, + should_be_signer: bool, +) -> Result, InstructionError> { + Ok( + if instruction_context.get_number_of_accounts() > index_in_instruction { + if should_be_signer && !instruction_context.is_signer(index_in_instruction)? { + return Err(InstructionError::MissingRequiredSignature); + } + Some(transaction_context.get_key_of_account_at_index( + instruction_context.get_index_in_transaction(index_in_instruction)?, + )?) + } else { + None + }, + ) +} + pub fn process_instruction( first_instruction_account: usize, invoke_context: &mut InvokeContext, @@ -31,13 +52,11 @@ pub fn process_instruction( let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let data = instruction_context.get_instruction_data(); - let keyed_accounts = invoke_context.get_keyed_accounts()?; trace!("process_instruction: {:?}", data); - trace!("keyed_accounts: {:?}", keyed_accounts); - let me = &keyed_account_at_index(keyed_accounts, first_instruction_account)?; - if me.owner()? != id() { + let mut me = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + if *me.get_owner() != id() { return Err(InstructionError::InvalidAccountOwner); } @@ -45,7 +64,13 @@ pub fn process_instruction( match limited_deserialize(data)? { StakeInstruction::Initialize(authorized, lockup) => { let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; - initialize(me, &authorized, &lockup, &rent, &invoke_context.feature_set) + initialize( + &mut me, + &authorized, + &lockup, + &rent, + &invoke_context.feature_set, + ) } StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => { let require_custodian_for_locked_stake_authorize = invoke_context @@ -56,23 +81,25 @@ pub fn process_instruction( let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; instruction_context.check_number_of_instruction_accounts(3)?; - let custodian = - keyed_account_at_index(keyed_accounts, first_instruction_account + 3) - .ok() - .map(|ka| ka.unsigned_key()); + let custodian_pubkey = get_optional_pubkey( + transaction_context, + instruction_context, + first_instruction_account + 3, + false, + )?; authorize( - me, + &mut me, &signers, &authorized_pubkey, stake_authorize, require_custodian_for_locked_stake_authorize, &clock, - custodian, + custodian_pubkey, ) } else { authorize( - me, + &mut me, &signers, &authorized_pubkey, stake_authorize, @@ -84,35 +111,38 @@ pub fn process_instruction( } StakeInstruction::AuthorizeWithSeed(args) => { instruction_context.check_number_of_instruction_accounts(2)?; - let authority_base = - keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; let require_custodian_for_locked_stake_authorize = invoke_context .feature_set .is_active(&feature_set::require_custodian_for_locked_stake_authorize::id()); - if require_custodian_for_locked_stake_authorize { let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; - let custodian = - keyed_account_at_index(keyed_accounts, first_instruction_account + 3) - .ok() - .map(|ka| ka.unsigned_key()); + let custodian_pubkey = get_optional_pubkey( + transaction_context, + instruction_context, + first_instruction_account + 3, + false, + )?; authorize_with_seed( - me, - authority_base, + transaction_context, + instruction_context, + &mut me, + first_instruction_account + 1, &args.authority_seed, &args.authority_owner, &args.new_authorized_pubkey, args.stake_authorize, require_custodian_for_locked_stake_authorize, &clock, - custodian, + custodian_pubkey, ) } else { authorize_with_seed( - me, - authority_base, + transaction_context, + instruction_context, + &mut me, + first_instruction_account + 1, &args.authority_seed, &args.authority_owner, &args.new_authorized_pubkey, @@ -125,7 +155,6 @@ pub fn process_instruction( } StakeInstruction::DelegateStake => { instruction_context.check_number_of_instruction_accounts(2)?; - let vote = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; let stake_history = get_sysvar_with_account_check::stake_history( @@ -134,25 +163,40 @@ pub fn process_instruction( 3, )?; instruction_context.check_number_of_instruction_accounts(5)?; + drop(me); let config_account = - keyed_account_at_index(keyed_accounts, first_instruction_account + 4)?; - if !config::check_id(config_account.unsigned_key()) { + instruction_context.try_borrow_instruction_account(transaction_context, 4)?; + if !config::check_id(config_account.get_key()) { return Err(InstructionError::InvalidArgument); } - let config = config::from(&*config_account.try_account_ref()?) - .ok_or(InstructionError::InvalidArgument)?; - delegate(me, vote, &clock, &stake_history, &config, &signers) + let config = config::from(&config_account).ok_or(InstructionError::InvalidArgument)?; + drop(config_account); + delegate( + transaction_context, + instruction_context, + first_instruction_account, + first_instruction_account + 1, + &clock, + &stake_history, + &config, + &signers, + ) } StakeInstruction::Split(lamports) => { instruction_context.check_number_of_instruction_accounts(2)?; - let split_stake = - &keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; - split(me, invoke_context, lamports, split_stake, &signers) + drop(me); + split( + invoke_context, + transaction_context, + instruction_context, + first_instruction_account, + lamports, + first_instruction_account + 1, + &signers, + ) } StakeInstruction::Merge => { instruction_context.check_number_of_instruction_accounts(2)?; - let source_stake = - &keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; let stake_history = get_sysvar_with_account_check::stake_history( @@ -160,10 +204,13 @@ pub fn process_instruction( instruction_context, 3, )?; + drop(me); merge( - me, invoke_context, - source_stake, + transaction_context, + instruction_context, + first_instruction_account, + first_instruction_account + 1, &clock, &stake_history, &signers, @@ -171,7 +218,6 @@ pub fn process_instruction( } StakeInstruction::Withdraw(lamports) => { instruction_context.check_number_of_instruction_accounts(2)?; - let to = &keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; let stake_history = get_sysvar_with_account_check::stake_history( @@ -180,25 +226,32 @@ pub fn process_instruction( 3, )?; instruction_context.check_number_of_instruction_accounts(5)?; + drop(me); withdraw( - me, + transaction_context, + instruction_context, + first_instruction_account, lamports, - to, + first_instruction_account + 1, &clock, &stake_history, - keyed_account_at_index(keyed_accounts, first_instruction_account + 4)?, - keyed_account_at_index(keyed_accounts, first_instruction_account + 5).ok(), + first_instruction_account + 4, + if instruction_context.get_number_of_instruction_accounts() >= 6 { + Some(first_instruction_account + 5) + } else { + None + }, &invoke_context.feature_set, ) } StakeInstruction::Deactivate => { let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; - deactivate(me, &clock, &signers) + deactivate(&mut me, &clock, &signers) } StakeInstruction::SetLockup(lockup) => { let clock = invoke_context.get_sysvar_cache().get_clock()?; - set_lockup(me, &lockup, &signers, &clock) + set_lockup(&mut me, &lockup, &signers, &clock) } StakeInstruction::InitializeChecked => { if invoke_context @@ -206,21 +259,25 @@ pub fn process_instruction( .is_active(&feature_set::vote_stake_checked_instructions::id()) { instruction_context.check_number_of_instruction_accounts(4)?; + let staker_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_in_transaction(first_instruction_account + 2)?, + )?; + let withdrawer_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_in_transaction(first_instruction_account + 3)?, + )?; + if !instruction_context.is_signer(first_instruction_account + 3)? { + return Err(InstructionError::MissingRequiredSignature); + } + let authorized = Authorized { - staker: *keyed_account_at_index(keyed_accounts, first_instruction_account + 2)? - .unsigned_key(), - withdrawer: *keyed_account_at_index( - keyed_accounts, - first_instruction_account + 3, - )? - .signer_key() - .ok_or(InstructionError::MissingRequiredSignature)?, + staker: *staker_pubkey, + withdrawer: *withdrawer_pubkey, }; let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; initialize( - me, + &mut me, &authorized, &Lockup::default(), &rent, @@ -238,25 +295,27 @@ pub fn process_instruction( let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; instruction_context.check_number_of_instruction_accounts(4)?; - let _current_authority = - keyed_account_at_index(keyed_accounts, first_instruction_account + 2)?; - let authorized_pubkey = - &keyed_account_at_index(keyed_accounts, first_instruction_account + 3)? - .signer_key() - .ok_or(InstructionError::MissingRequiredSignature)?; - let custodian = - keyed_account_at_index(keyed_accounts, first_instruction_account + 4) - .ok() - .map(|ka| ka.unsigned_key()); + let authorized_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_in_transaction(first_instruction_account + 3)?, + )?; + if !instruction_context.is_signer(first_instruction_account + 3)? { + return Err(InstructionError::MissingRequiredSignature); + } + let custodian_pubkey = get_optional_pubkey( + transaction_context, + instruction_context, + first_instruction_account + 4, + false, + )?; authorize( - me, + &mut me, &signers, authorized_pubkey, stake_authorize, true, &clock, - custodian, + custodian_pubkey, ) } else { Err(InstructionError::InvalidInstructionData) @@ -268,30 +327,34 @@ pub fn process_instruction( .is_active(&feature_set::vote_stake_checked_instructions::id()) { instruction_context.check_number_of_instruction_accounts(2)?; - let authority_base = - keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; instruction_context.check_number_of_instruction_accounts(4)?; - let authorized_pubkey = - &keyed_account_at_index(keyed_accounts, first_instruction_account + 3)? - .signer_key() - .ok_or(InstructionError::MissingRequiredSignature)?; - let custodian = - keyed_account_at_index(keyed_accounts, first_instruction_account + 4) - .ok() - .map(|ka| ka.unsigned_key()); + let authorized_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_in_transaction(first_instruction_account + 3)?, + )?; + if !instruction_context.is_signer(first_instruction_account + 3)? { + return Err(InstructionError::MissingRequiredSignature); + } + let custodian_pubkey = get_optional_pubkey( + transaction_context, + instruction_context, + first_instruction_account + 4, + false, + )?; authorize_with_seed( - me, - authority_base, + transaction_context, + instruction_context, + &mut me, + first_instruction_account + 1, &args.authority_seed, &args.authority_owner, authorized_pubkey, args.stake_authorize, true, &clock, - custodian, + custodian_pubkey, ) } else { Err(InstructionError::InvalidInstructionData) @@ -302,25 +365,20 @@ pub fn process_instruction( .feature_set .is_active(&feature_set::vote_stake_checked_instructions::id()) { - let custodian = if let Ok(custodian) = - keyed_account_at_index(keyed_accounts, first_instruction_account + 2) - { - Some( - *custodian - .signer_key() - .ok_or(InstructionError::MissingRequiredSignature)?, - ) - } else { - None - }; + let custodian_pubkey = get_optional_pubkey( + transaction_context, + instruction_context, + first_instruction_account + 2, + true, + )?; let lockup = LockupArgs { unix_timestamp: lockup_checked.unix_timestamp, epoch: lockup_checked.epoch, - custodian, + custodian: custodian_pubkey.cloned(), }; let clock = invoke_context.get_sysvar_cache().get_clock()?; - set_lockup(me, &lockup, &signers, &clock) + set_lockup(&mut me, &lockup, &signers, &clock) } else { Err(InstructionError::InvalidInstructionData) } @@ -335,6 +393,7 @@ pub fn process_instruction( let minimum_delegation = crate::get_minimum_delegation(feature_set); let minimum_delegation = Vec::from(minimum_delegation.to_le_bytes()); + drop(me); invoke_context .transaction_context .set_return_data(id(), minimum_delegation) diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index ea5f22fd24..e6fe343675 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -12,13 +12,12 @@ use { solana_program_runtime::{ic_msg, invoke_context::InvokeContext}, solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, - account_utils::{State, StateMut}, + account_utils::StateMut, clock::{Clock, Epoch}, feature_set::{ stake_merge_with_unmatched_credits_observed, stake_split_uses_rent_sysvar, FeatureSet, }, instruction::{checked_add, InstructionError}, - keyed_account::KeyedAccount, pubkey::Pubkey, rent::{Rent, ACCOUNT_STORAGE_OVERHEAD}, stake::{ @@ -27,6 +26,7 @@ use { program::id, }, stake_history::{StakeHistory, StakeHistoryEntry}, + transaction_context::{BorrowedAccount, InstructionContext, TransactionContext}, }, solana_vote_program::vote_state::{VoteState, VoteStateVersions}, std::{collections::HashSet, convert::TryFrom}, @@ -366,21 +366,21 @@ fn calculate_stake_rewards( } pub fn initialize( - stake_account: &KeyedAccount, + stake_account: &mut BorrowedAccount, authorized: &Authorized, lockup: &Lockup, rent: &Rent, feature_set: &FeatureSet, ) -> Result<(), InstructionError> { - if stake_account.data_len()? != std::mem::size_of::() { + if stake_account.get_data().len() != std::mem::size_of::() { return Err(InstructionError::InvalidAccountData); } - if let StakeState::Uninitialized = stake_account.state()? { - let rent_exempt_reserve = rent.minimum_balance(stake_account.data_len()?); + if let StakeState::Uninitialized = stake_account.get_state()? { + let rent_exempt_reserve = rent.minimum_balance(stake_account.get_data().len()); let minimum_delegation = crate::get_minimum_delegation(feature_set); let minimum_balance = rent_exempt_reserve + minimum_delegation; - if stake_account.lamports()? >= minimum_balance { + if stake_account.get_lamports() >= minimum_balance { stake_account.set_state(&StakeState::Initialized(Meta { rent_exempt_reserve, authorized: *authorized, @@ -398,7 +398,7 @@ pub fn initialize( /// multiple times, but will implicitly withdraw authorization from the previously authorized /// staker. The default staker is the owner of the stake account's pubkey. pub fn authorize( - stake_account: &KeyedAccount, + stake_account: &mut BorrowedAccount, signers: &HashSet, new_authority: &Pubkey, stake_authorize: StakeAuthorize, @@ -406,7 +406,7 @@ pub fn authorize( clock: &Clock, custodian: Option<&Pubkey>, ) -> Result<(), InstructionError> { - match stake_account.state()? { + match stake_account.get_state()? { StakeState::Stake(mut meta, stake) => { meta.authorized.authorize( signers, @@ -437,9 +437,12 @@ pub fn authorize( } } +#[allow(clippy::too_many_arguments)] pub fn authorize_with_seed( - stake_account: &KeyedAccount, - authority_base: &KeyedAccount, + transaction_context: &TransactionContext, + instruction_context: &InstructionContext, + stake_account: &mut BorrowedAccount, + authority_base_index: usize, authority_seed: &str, authority_owner: &Pubkey, new_authority: &Pubkey, @@ -449,7 +452,10 @@ pub fn authorize_with_seed( custodian: Option<&Pubkey>, ) -> Result<(), InstructionError> { let mut signers = HashSet::default(); - if let Some(base_pubkey) = authority_base.signer_key() { + if instruction_context.is_signer(authority_base_index)? { + let base_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_in_transaction(authority_base_index)?, + )?; signers.insert(Pubkey::create_with_seed( base_pubkey, authority_seed, @@ -468,26 +474,35 @@ pub fn authorize_with_seed( } pub fn delegate( - stake_account: &KeyedAccount, - vote_account: &KeyedAccount, + transaction_context: &TransactionContext, + instruction_context: &InstructionContext, + stake_account_index: usize, + vote_account_index: usize, clock: &Clock, stake_history: &StakeHistory, config: &Config, signers: &HashSet, ) -> Result<(), InstructionError> { - if vote_account.owner()? != solana_vote_program::id() { + let vote_account = + instruction_context.try_borrow_account(transaction_context, vote_account_index)?; + if *vote_account.get_owner() != solana_vote_program::id() { return Err(InstructionError::IncorrectProgramId); } + let vote_pubkey = *vote_account.get_key(); + let vote_state = vote_account.get_state::(); + drop(vote_account); - match stake_account.state()? { + let mut stake_account = + instruction_context.try_borrow_account(transaction_context, stake_account_index)?; + match stake_account.get_state()? { StakeState::Initialized(meta) => { meta.authorized.check(signers, StakeAuthorize::Staker)?; let ValidatedDelegatedInfo { stake_amount } = - validate_delegated_amount(stake_account, &meta)?; + validate_delegated_amount(&stake_account, &meta)?; let stake = new_stake( stake_amount, - vote_account.unsigned_key(), - &State::::state(vote_account)?.convert_to_current(), + &vote_pubkey, + &vote_state?.convert_to_current(), clock.epoch, config, ); @@ -496,12 +511,12 @@ pub fn delegate( StakeState::Stake(meta, mut stake) => { meta.authorized.check(signers, StakeAuthorize::Staker)?; let ValidatedDelegatedInfo { stake_amount } = - validate_delegated_amount(stake_account, &meta)?; + validate_delegated_amount(&stake_account, &meta)?; redelegate( &mut stake, stake_amount, - vote_account.unsigned_key(), - &State::::state(vote_account)?.convert_to_current(), + &vote_pubkey, + &vote_state?.convert_to_current(), clock, stake_history, config, @@ -513,11 +528,11 @@ pub fn delegate( } pub fn deactivate( - stake_account: &KeyedAccount, + stake_account: &mut BorrowedAccount, clock: &Clock, signers: &HashSet, ) -> Result<(), InstructionError> { - if let StakeState::Stake(meta, mut stake) = stake_account.state()? { + if let StakeState::Stake(meta, mut stake) = stake_account.get_state()? { meta.authorized.check(signers, StakeAuthorize::Staker)?; stake.deactivate(clock.epoch)?; @@ -528,12 +543,12 @@ pub fn deactivate( } pub fn set_lockup( - stake_account: &KeyedAccount, + stake_account: &mut BorrowedAccount, lockup: &LockupArgs, signers: &HashSet, clock: &Clock, ) -> Result<(), InstructionError> { - match stake_account.state()? { + match stake_account.get_state()? { StakeState::Initialized(mut meta) => { meta.set_lockup(lockup, signers, clock)?; stake_account.set_state(&StakeState::Initialized(meta)) @@ -547,32 +562,43 @@ pub fn set_lockup( } pub fn split( - stake_account: &KeyedAccount, invoke_context: &InvokeContext, + transaction_context: &TransactionContext, + instruction_context: &InstructionContext, + stake_account_index: usize, lamports: u64, - split: &KeyedAccount, + split_index: usize, signers: &HashSet, ) -> Result<(), InstructionError> { - if split.owner()? != id() { + let split = instruction_context.try_borrow_account(transaction_context, split_index)?; + if *split.get_owner() != id() { return Err(InstructionError::IncorrectProgramId); } - if split.data_len()? != std::mem::size_of::() { + if split.get_data().len() != std::mem::size_of::() { return Err(InstructionError::InvalidAccountData); } - if !matches!(split.state()?, StakeState::Uninitialized) { + if !matches!(split.get_state()?, StakeState::Uninitialized) { return Err(InstructionError::InvalidAccountData); } - if lamports > stake_account.lamports()? { + let split_lamport_balance = split.get_lamports(); + drop(split); + let stake_account = + instruction_context.try_borrow_account(transaction_context, stake_account_index)?; + if lamports > stake_account.get_lamports() { return Err(InstructionError::InsufficientFunds); } + let stake_state = stake_account.get_state()?; + drop(stake_account); - match stake_account.state()? { + match stake_state { StakeState::Stake(meta, mut stake) => { meta.authorized.check(signers, StakeAuthorize::Staker)?; let validated_split_info = validate_split_amount( invoke_context, - stake_account, - split, + transaction_context, + instruction_context, + stake_account_index, + split_index, lamports, &meta, Some(&stake), @@ -605,7 +631,7 @@ pub fn split( lamports.saturating_sub( validated_split_info .destination_rent_exempt_reserve - .saturating_sub(split.lamports()?), + .saturating_sub(split_lamport_balance), ), ) }; @@ -613,19 +639,37 @@ pub fn split( let mut split_meta = meta; split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve; + let mut stake_account = + instruction_context.try_borrow_account(transaction_context, stake_account_index)?; stake_account.set_state(&StakeState::Stake(meta, stake))?; + drop(stake_account); + let mut split = + instruction_context.try_borrow_account(transaction_context, split_index)?; split.set_state(&StakeState::Stake(split_meta, split_stake))?; } StakeState::Initialized(meta) => { meta.authorized.check(signers, StakeAuthorize::Staker)?; - let validated_split_info = - validate_split_amount(invoke_context, stake_account, split, lamports, &meta, None)?; + let validated_split_info = validate_split_amount( + invoke_context, + transaction_context, + instruction_context, + stake_account_index, + split_index, + lamports, + &meta, + None, + )?; let mut split_meta = meta; split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve; + let mut split = + instruction_context.try_borrow_account(transaction_context, split_index)?; split.set_state(&StakeState::Initialized(split_meta))?; } StakeState::Uninitialized => { - if !signers.contains(stake_account.unsigned_key()) { + let stake_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_in_transaction(stake_account_index)?, + )?; + if !signers.contains(stake_pubkey) { return Err(InstructionError::MissingRequiredSignature); } } @@ -633,54 +677,67 @@ pub fn split( } // Deinitialize state upon zero balance - if lamports == stake_account.lamports()? { + let mut stake_account = + instruction_context.try_borrow_account(transaction_context, stake_account_index)?; + if lamports == stake_account.get_lamports() { stake_account.set_state(&StakeState::Uninitialized)?; } + drop(stake_account); - split - .try_account_ref_mut()? - .checked_add_lamports(lamports)?; - stake_account - .try_account_ref_mut()? - .checked_sub_lamports(lamports)?; + let mut split = instruction_context.try_borrow_account(transaction_context, split_index)?; + split.checked_add_lamports(lamports)?; + drop(split); + let mut stake_account = + instruction_context.try_borrow_account(transaction_context, stake_account_index)?; + stake_account.checked_sub_lamports(lamports)?; Ok(()) } pub fn merge( - stake_account: &KeyedAccount, invoke_context: &InvokeContext, - source_account: &KeyedAccount, + transaction_context: &TransactionContext, + instruction_context: &InstructionContext, + stake_account_index: usize, + source_account_index: usize, clock: &Clock, stake_history: &StakeHistory, signers: &HashSet, ) -> Result<(), InstructionError> { + let mut source_account = + instruction_context.try_borrow_account(transaction_context, source_account_index)?; // Ensure source isn't spoofed - if source_account.owner()? != id() { + if *source_account.get_owner() != id() { return Err(InstructionError::IncorrectProgramId); } // Close the stake_account-reference loophole - if source_account.unsigned_key() == stake_account.unsigned_key() { + if instruction_context.get_index_in_transaction(stake_account_index)? + == instruction_context.get_index_in_transaction(source_account_index)? + { return Err(InstructionError::InvalidArgument); } + let mut stake_account = + instruction_context.try_borrow_account(transaction_context, stake_account_index)?; ic_msg!(invoke_context, "Checking if destination stake is mergeable"); let stake_merge_kind = MergeKind::get_if_mergeable( invoke_context, - &stake_account.state()?, - stake_account.lamports()?, + &stake_account.get_state()?, + stake_account.get_lamports(), clock, stake_history, )?; - let meta = stake_merge_kind.meta(); // Authorized staker is allowed to split/merge accounts - meta.authorized.check(signers, StakeAuthorize::Staker)?; + stake_merge_kind + .meta() + .authorized + .check(signers, StakeAuthorize::Staker)?; ic_msg!(invoke_context, "Checking if source stake is mergeable"); let source_merge_kind = MergeKind::get_if_mergeable( invoke_context, - &source_account.state()?, - source_account.lamports()?, + &source_account.get_state()?, + source_account.get_lamports(), clock, stake_history, )?; @@ -694,33 +751,37 @@ pub fn merge( source_account.set_state(&StakeState::Uninitialized)?; // Drain the source stake account - let lamports = source_account.lamports()?; - source_account - .try_account_ref_mut()? - .checked_sub_lamports(lamports)?; - stake_account - .try_account_ref_mut()? - .checked_add_lamports(lamports)?; + let lamports = source_account.get_lamports(); + source_account.checked_sub_lamports(lamports)?; + stake_account.checked_add_lamports(lamports)?; Ok(()) } +#[allow(clippy::too_many_arguments)] pub fn withdraw( - stake_account: &KeyedAccount, + transaction_context: &TransactionContext, + instruction_context: &InstructionContext, + stake_account_index: usize, lamports: u64, - to: &KeyedAccount, + to_index: usize, clock: &Clock, stake_history: &StakeHistory, - withdraw_authority: &KeyedAccount, - custodian: Option<&KeyedAccount>, + withdraw_authority_index: usize, + custodian_index: Option, feature_set: &FeatureSet, ) -> Result<(), InstructionError> { + let withdraw_authority_pubkey = transaction_context.get_key_of_account_at_index( + instruction_context.get_index_in_transaction(withdraw_authority_index)?, + )?; + if !instruction_context.is_signer(withdraw_authority_index)? { + return Err(InstructionError::MissingRequiredSignature); + } let mut signers = HashSet::new(); - let withdraw_authority_pubkey = withdraw_authority - .signer_key() - .ok_or(InstructionError::MissingRequiredSignature)?; signers.insert(*withdraw_authority_pubkey); - let (lockup, reserve, is_staked) = match stake_account.state()? { + let mut stake_account = + instruction_context.try_borrow_account(transaction_context, stake_account_index)?; + let (lockup, reserve, is_staked) = match stake_account.get_state()? { StakeState::Stake(meta, stake) => { meta.authorized .check(&signers, StakeAuthorize::Withdrawer)?; @@ -749,7 +810,7 @@ pub fn withdraw( (meta.lockup, reserve, false) } StakeState::Uninitialized => { - if !signers.contains(stake_account.unsigned_key()) { + if !signers.contains(stake_account.get_key()) { return Err(InstructionError::MissingRequiredSignature); } (Lockup::default(), 0, false) // no lockup, no restrictions @@ -759,7 +820,17 @@ pub fn withdraw( // verify that lockup has expired or that the withdrawal is signed by // the custodian, both epoch and unix_timestamp must have passed - let custodian_pubkey = custodian.and_then(|keyed_account| keyed_account.signer_key()); + let custodian_pubkey = if let Some(custodian_index) = custodian_index { + if instruction_context.is_signer(custodian_index)? { + Some(transaction_context.get_key_of_account_at_index( + instruction_context.get_index_in_transaction(custodian_index)?, + )?) + } else { + None + } + } else { + None + }; if lockup.is_in_force(clock, custodian_pubkey) { return Err(StakeError::LockupInForce.into()); } @@ -767,27 +838,27 @@ pub fn withdraw( let lamports_and_reserve = checked_add(lamports, reserve)?; // if the stake is active, we mustn't allow the account to go away if is_staked // line coverage for branch coverage - && lamports_and_reserve > stake_account.lamports()? + && lamports_and_reserve > stake_account.get_lamports() { return Err(InstructionError::InsufficientFunds); } - if lamports != stake_account.lamports()? // not a full withdrawal - && lamports_and_reserve > stake_account.lamports()? + if lamports != stake_account.get_lamports() // not a full withdrawal + && lamports_and_reserve > stake_account.get_lamports() { assert!(!is_staked); return Err(InstructionError::InsufficientFunds); } // Deinitialize state upon zero balance - if lamports == stake_account.lamports()? { + if lamports == stake_account.get_lamports() { stake_account.set_state(&StakeState::Uninitialized)?; } - stake_account - .try_account_ref_mut()? - .checked_sub_lamports(lamports)?; - to.try_account_ref_mut()?.checked_add_lamports(lamports)?; + stake_account.checked_sub_lamports(lamports)?; + drop(stake_account); + let mut to = instruction_context.try_borrow_account(transaction_context, to_index)?; + to.checked_add_lamports(lamports)?; Ok(()) } @@ -800,10 +871,12 @@ struct ValidatedDelegatedInfo { /// Ensure the stake delegation amount is valid. This checks that the account meets the minimum /// balance requirements of delegated stake. If not, return an error. fn validate_delegated_amount( - account: &KeyedAccount, + account: &BorrowedAccount, meta: &Meta, ) -> Result { - let stake_amount = account.lamports()?.saturating_sub(meta.rent_exempt_reserve); // can't stake the rent + let stake_amount = account + .get_lamports() + .saturating_sub(meta.rent_exempt_reserve); // can't stake the rent Ok(ValidatedDelegatedInfo { stake_amount }) } @@ -821,14 +894,24 @@ struct ValidatedSplitInfo { /// not, return an error. fn validate_split_amount( invoke_context: &InvokeContext, - source_account: &KeyedAccount, - destination_account: &KeyedAccount, + transaction_context: &TransactionContext, + instruction_context: &InstructionContext, + source_account_index: usize, + destination_account_index: usize, lamports: u64, source_meta: &Meta, source_stake: Option<&Stake>, ) -> Result { - let source_lamports = source_account.lamports()?; - let destination_lamports = destination_account.lamports()?; + let source_account = + instruction_context.try_borrow_account(transaction_context, source_account_index)?; + let source_lamports = source_account.get_lamports(); + let source_data_len = source_account.get_data().len(); + drop(source_account); + let destination_account = + instruction_context.try_borrow_account(transaction_context, destination_account_index)?; + let destination_lamports = destination_account.get_lamports(); + let destination_data_len = destination_account.get_data().len(); + drop(destination_account); // Split amount has to be something if lamports == 0 { @@ -869,12 +952,12 @@ fn validate_split_amount( .is_active(&stake_split_uses_rent_sysvar::ID) { let rent = invoke_context.get_sysvar_cache().get_rent()?; - rent.minimum_balance(destination_account.data_len()?) + rent.minimum_balance(destination_data_len) } else { calculate_split_rent_exempt_reserve( source_meta.rent_exempt_reserve, - source_account.data_len()? as u64, - destination_account.data_len()? as u64, + source_data_len as u64, + destination_data_len as u64, ) }; let destination_minimum_balance =