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.
This commit is contained in:
Alexander Meißner 2022-04-10 09:55:37 +02:00 committed by GitHub
parent 1882434c69
commit bf13fb4c4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 327 additions and 195 deletions

View File

@ -12,11 +12,12 @@ use {
account::{AccountSharedData, ReadableAccount, WritableAccount}, account::{AccountSharedData, ReadableAccount, WritableAccount},
genesis_config::GenesisConfig, genesis_config::GenesisConfig,
stake::config::{self, Config}, stake::config::{self, Config},
transaction_context::BorrowedAccount,
}, },
}; };
pub fn from<T: ReadableAccount>(account: &T) -> Option<Config> { pub fn from(account: &BorrowedAccount) -> Option<Config> {
get_config_data(account.data()) get_config_data(account.get_data())
.ok() .ok()
.and_then(|data| deserialize(data).ok()) .and_then(|data| deserialize(data).ok())
} }
@ -35,14 +36,3 @@ pub fn add_genesis_account(genesis_config: &mut GenesisConfig) -> u64 {
lamports 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()));
}
}

View File

@ -13,17 +13,38 @@ use {
solana_sdk::{ solana_sdk::{
feature_set, feature_set,
instruction::InstructionError, instruction::InstructionError,
keyed_account::keyed_account_at_index,
program_utils::limited_deserialize, program_utils::limited_deserialize,
pubkey::Pubkey,
stake::{ stake::{
instruction::{LockupArgs, StakeInstruction}, instruction::{LockupArgs, StakeInstruction},
program::id, program::id,
state::{Authorized, Lockup}, state::{Authorized, Lockup},
}, },
sysvar::clock::Clock, 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<Option<&'a Pubkey>, 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( pub fn process_instruction(
first_instruction_account: usize, first_instruction_account: usize,
invoke_context: &mut InvokeContext, invoke_context: &mut InvokeContext,
@ -31,13 +52,11 @@ pub fn process_instruction(
let transaction_context = &invoke_context.transaction_context; let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_context = transaction_context.get_current_instruction_context()?;
let data = instruction_context.get_instruction_data(); let data = instruction_context.get_instruction_data();
let keyed_accounts = invoke_context.get_keyed_accounts()?;
trace!("process_instruction: {:?}", data); trace!("process_instruction: {:?}", data);
trace!("keyed_accounts: {:?}", keyed_accounts);
let me = &keyed_account_at_index(keyed_accounts, first_instruction_account)?; let mut me = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
if me.owner()? != id() { if *me.get_owner() != id() {
return Err(InstructionError::InvalidAccountOwner); return Err(InstructionError::InvalidAccountOwner);
} }
@ -45,7 +64,13 @@ pub fn process_instruction(
match limited_deserialize(data)? { match limited_deserialize(data)? {
StakeInstruction::Initialize(authorized, lockup) => { StakeInstruction::Initialize(authorized, lockup) => {
let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; 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) => { StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => {
let require_custodian_for_locked_stake_authorize = invoke_context let require_custodian_for_locked_stake_authorize = invoke_context
@ -56,23 +81,25 @@ pub fn process_instruction(
let clock = let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?;
instruction_context.check_number_of_instruction_accounts(3)?; instruction_context.check_number_of_instruction_accounts(3)?;
let custodian = let custodian_pubkey = get_optional_pubkey(
keyed_account_at_index(keyed_accounts, first_instruction_account + 3) transaction_context,
.ok() instruction_context,
.map(|ka| ka.unsigned_key()); first_instruction_account + 3,
false,
)?;
authorize( authorize(
me, &mut me,
&signers, &signers,
&authorized_pubkey, &authorized_pubkey,
stake_authorize, stake_authorize,
require_custodian_for_locked_stake_authorize, require_custodian_for_locked_stake_authorize,
&clock, &clock,
custodian, custodian_pubkey,
) )
} else { } else {
authorize( authorize(
me, &mut me,
&signers, &signers,
&authorized_pubkey, &authorized_pubkey,
stake_authorize, stake_authorize,
@ -84,35 +111,38 @@ pub fn process_instruction(
} }
StakeInstruction::AuthorizeWithSeed(args) => { StakeInstruction::AuthorizeWithSeed(args) => {
instruction_context.check_number_of_instruction_accounts(2)?; 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 let require_custodian_for_locked_stake_authorize = invoke_context
.feature_set .feature_set
.is_active(&feature_set::require_custodian_for_locked_stake_authorize::id()); .is_active(&feature_set::require_custodian_for_locked_stake_authorize::id());
if require_custodian_for_locked_stake_authorize { if require_custodian_for_locked_stake_authorize {
let clock = let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
let custodian = let custodian_pubkey = get_optional_pubkey(
keyed_account_at_index(keyed_accounts, first_instruction_account + 3) transaction_context,
.ok() instruction_context,
.map(|ka| ka.unsigned_key()); first_instruction_account + 3,
false,
)?;
authorize_with_seed( authorize_with_seed(
me, transaction_context,
authority_base, instruction_context,
&mut me,
first_instruction_account + 1,
&args.authority_seed, &args.authority_seed,
&args.authority_owner, &args.authority_owner,
&args.new_authorized_pubkey, &args.new_authorized_pubkey,
args.stake_authorize, args.stake_authorize,
require_custodian_for_locked_stake_authorize, require_custodian_for_locked_stake_authorize,
&clock, &clock,
custodian, custodian_pubkey,
) )
} else { } else {
authorize_with_seed( authorize_with_seed(
me, transaction_context,
authority_base, instruction_context,
&mut me,
first_instruction_account + 1,
&args.authority_seed, &args.authority_seed,
&args.authority_owner, &args.authority_owner,
&args.new_authorized_pubkey, &args.new_authorized_pubkey,
@ -125,7 +155,6 @@ pub fn process_instruction(
} }
StakeInstruction::DelegateStake => { StakeInstruction::DelegateStake => {
instruction_context.check_number_of_instruction_accounts(2)?; instruction_context.check_number_of_instruction_accounts(2)?;
let vote = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
let clock = let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
let stake_history = get_sysvar_with_account_check::stake_history( let stake_history = get_sysvar_with_account_check::stake_history(
@ -134,25 +163,40 @@ pub fn process_instruction(
3, 3,
)?; )?;
instruction_context.check_number_of_instruction_accounts(5)?; instruction_context.check_number_of_instruction_accounts(5)?;
drop(me);
let config_account = let config_account =
keyed_account_at_index(keyed_accounts, first_instruction_account + 4)?; instruction_context.try_borrow_instruction_account(transaction_context, 4)?;
if !config::check_id(config_account.unsigned_key()) { if !config::check_id(config_account.get_key()) {
return Err(InstructionError::InvalidArgument); return Err(InstructionError::InvalidArgument);
} }
let config = config::from(&*config_account.try_account_ref()?) let config = config::from(&config_account).ok_or(InstructionError::InvalidArgument)?;
.ok_or(InstructionError::InvalidArgument)?; drop(config_account);
delegate(me, vote, &clock, &stake_history, &config, &signers) delegate(
transaction_context,
instruction_context,
first_instruction_account,
first_instruction_account + 1,
&clock,
&stake_history,
&config,
&signers,
)
} }
StakeInstruction::Split(lamports) => { StakeInstruction::Split(lamports) => {
instruction_context.check_number_of_instruction_accounts(2)?; instruction_context.check_number_of_instruction_accounts(2)?;
let split_stake = drop(me);
&keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; split(
split(me, invoke_context, lamports, split_stake, &signers) invoke_context,
transaction_context,
instruction_context,
first_instruction_account,
lamports,
first_instruction_account + 1,
&signers,
)
} }
StakeInstruction::Merge => { StakeInstruction::Merge => {
instruction_context.check_number_of_instruction_accounts(2)?; instruction_context.check_number_of_instruction_accounts(2)?;
let source_stake =
&keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
let clock = let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
let stake_history = get_sysvar_with_account_check::stake_history( let stake_history = get_sysvar_with_account_check::stake_history(
@ -160,10 +204,13 @@ pub fn process_instruction(
instruction_context, instruction_context,
3, 3,
)?; )?;
drop(me);
merge( merge(
me,
invoke_context, invoke_context,
source_stake, transaction_context,
instruction_context,
first_instruction_account,
first_instruction_account + 1,
&clock, &clock,
&stake_history, &stake_history,
&signers, &signers,
@ -171,7 +218,6 @@ pub fn process_instruction(
} }
StakeInstruction::Withdraw(lamports) => { StakeInstruction::Withdraw(lamports) => {
instruction_context.check_number_of_instruction_accounts(2)?; instruction_context.check_number_of_instruction_accounts(2)?;
let to = &keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
let clock = let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
let stake_history = get_sysvar_with_account_check::stake_history( let stake_history = get_sysvar_with_account_check::stake_history(
@ -180,25 +226,32 @@ pub fn process_instruction(
3, 3,
)?; )?;
instruction_context.check_number_of_instruction_accounts(5)?; instruction_context.check_number_of_instruction_accounts(5)?;
drop(me);
withdraw( withdraw(
me, transaction_context,
instruction_context,
first_instruction_account,
lamports, lamports,
to, first_instruction_account + 1,
&clock, &clock,
&stake_history, &stake_history,
keyed_account_at_index(keyed_accounts, first_instruction_account + 4)?, first_instruction_account + 4,
keyed_account_at_index(keyed_accounts, first_instruction_account + 5).ok(), if instruction_context.get_number_of_instruction_accounts() >= 6 {
Some(first_instruction_account + 5)
} else {
None
},
&invoke_context.feature_set, &invoke_context.feature_set,
) )
} }
StakeInstruction::Deactivate => { StakeInstruction::Deactivate => {
let clock = let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?;
deactivate(me, &clock, &signers) deactivate(&mut me, &clock, &signers)
} }
StakeInstruction::SetLockup(lockup) => { StakeInstruction::SetLockup(lockup) => {
let clock = invoke_context.get_sysvar_cache().get_clock()?; let clock = invoke_context.get_sysvar_cache().get_clock()?;
set_lockup(me, &lockup, &signers, &clock) set_lockup(&mut me, &lockup, &signers, &clock)
} }
StakeInstruction::InitializeChecked => { StakeInstruction::InitializeChecked => {
if invoke_context if invoke_context
@ -206,21 +259,25 @@ pub fn process_instruction(
.is_active(&feature_set::vote_stake_checked_instructions::id()) .is_active(&feature_set::vote_stake_checked_instructions::id())
{ {
instruction_context.check_number_of_instruction_accounts(4)?; 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 { let authorized = Authorized {
staker: *keyed_account_at_index(keyed_accounts, first_instruction_account + 2)? staker: *staker_pubkey,
.unsigned_key(), withdrawer: *withdrawer_pubkey,
withdrawer: *keyed_account_at_index(
keyed_accounts,
first_instruction_account + 3,
)?
.signer_key()
.ok_or(InstructionError::MissingRequiredSignature)?,
}; };
let rent = let rent =
get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?; get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?;
initialize( initialize(
me, &mut me,
&authorized, &authorized,
&Lockup::default(), &Lockup::default(),
&rent, &rent,
@ -238,25 +295,27 @@ pub fn process_instruction(
let clock = let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?;
instruction_context.check_number_of_instruction_accounts(4)?; instruction_context.check_number_of_instruction_accounts(4)?;
let _current_authority = let authorized_pubkey = transaction_context.get_key_of_account_at_index(
keyed_account_at_index(keyed_accounts, first_instruction_account + 2)?; instruction_context.get_index_in_transaction(first_instruction_account + 3)?,
let authorized_pubkey = )?;
&keyed_account_at_index(keyed_accounts, first_instruction_account + 3)? if !instruction_context.is_signer(first_instruction_account + 3)? {
.signer_key() return Err(InstructionError::MissingRequiredSignature);
.ok_or(InstructionError::MissingRequiredSignature)?; }
let custodian = let custodian_pubkey = get_optional_pubkey(
keyed_account_at_index(keyed_accounts, first_instruction_account + 4) transaction_context,
.ok() instruction_context,
.map(|ka| ka.unsigned_key()); first_instruction_account + 4,
false,
)?;
authorize( authorize(
me, &mut me,
&signers, &signers,
authorized_pubkey, authorized_pubkey,
stake_authorize, stake_authorize,
true, true,
&clock, &clock,
custodian, custodian_pubkey,
) )
} else { } else {
Err(InstructionError::InvalidInstructionData) Err(InstructionError::InvalidInstructionData)
@ -268,30 +327,34 @@ pub fn process_instruction(
.is_active(&feature_set::vote_stake_checked_instructions::id()) .is_active(&feature_set::vote_stake_checked_instructions::id())
{ {
instruction_context.check_number_of_instruction_accounts(2)?; instruction_context.check_number_of_instruction_accounts(2)?;
let authority_base =
keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
let clock = let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?; get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
instruction_context.check_number_of_instruction_accounts(4)?; instruction_context.check_number_of_instruction_accounts(4)?;
let authorized_pubkey = let authorized_pubkey = transaction_context.get_key_of_account_at_index(
&keyed_account_at_index(keyed_accounts, first_instruction_account + 3)? instruction_context.get_index_in_transaction(first_instruction_account + 3)?,
.signer_key() )?;
.ok_or(InstructionError::MissingRequiredSignature)?; if !instruction_context.is_signer(first_instruction_account + 3)? {
let custodian = return Err(InstructionError::MissingRequiredSignature);
keyed_account_at_index(keyed_accounts, first_instruction_account + 4) }
.ok() let custodian_pubkey = get_optional_pubkey(
.map(|ka| ka.unsigned_key()); transaction_context,
instruction_context,
first_instruction_account + 4,
false,
)?;
authorize_with_seed( authorize_with_seed(
me, transaction_context,
authority_base, instruction_context,
&mut me,
first_instruction_account + 1,
&args.authority_seed, &args.authority_seed,
&args.authority_owner, &args.authority_owner,
authorized_pubkey, authorized_pubkey,
args.stake_authorize, args.stake_authorize,
true, true,
&clock, &clock,
custodian, custodian_pubkey,
) )
} else { } else {
Err(InstructionError::InvalidInstructionData) Err(InstructionError::InvalidInstructionData)
@ -302,25 +365,20 @@ pub fn process_instruction(
.feature_set .feature_set
.is_active(&feature_set::vote_stake_checked_instructions::id()) .is_active(&feature_set::vote_stake_checked_instructions::id())
{ {
let custodian = if let Ok(custodian) = let custodian_pubkey = get_optional_pubkey(
keyed_account_at_index(keyed_accounts, first_instruction_account + 2) transaction_context,
{ instruction_context,
Some( first_instruction_account + 2,
*custodian true,
.signer_key() )?;
.ok_or(InstructionError::MissingRequiredSignature)?,
)
} else {
None
};
let lockup = LockupArgs { let lockup = LockupArgs {
unix_timestamp: lockup_checked.unix_timestamp, unix_timestamp: lockup_checked.unix_timestamp,
epoch: lockup_checked.epoch, epoch: lockup_checked.epoch,
custodian, custodian: custodian_pubkey.cloned(),
}; };
let clock = invoke_context.get_sysvar_cache().get_clock()?; let clock = invoke_context.get_sysvar_cache().get_clock()?;
set_lockup(me, &lockup, &signers, &clock) set_lockup(&mut me, &lockup, &signers, &clock)
} else { } else {
Err(InstructionError::InvalidInstructionData) Err(InstructionError::InvalidInstructionData)
} }
@ -335,6 +393,7 @@ pub fn process_instruction(
let minimum_delegation = crate::get_minimum_delegation(feature_set); let minimum_delegation = crate::get_minimum_delegation(feature_set);
let minimum_delegation = Vec::from(minimum_delegation.to_le_bytes()); let minimum_delegation = Vec::from(minimum_delegation.to_le_bytes());
drop(me);
invoke_context invoke_context
.transaction_context .transaction_context
.set_return_data(id(), minimum_delegation) .set_return_data(id(), minimum_delegation)

View File

@ -12,13 +12,12 @@ use {
solana_program_runtime::{ic_msg, invoke_context::InvokeContext}, solana_program_runtime::{ic_msg, invoke_context::InvokeContext},
solana_sdk::{ solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount}, account::{AccountSharedData, ReadableAccount, WritableAccount},
account_utils::{State, StateMut}, account_utils::StateMut,
clock::{Clock, Epoch}, clock::{Clock, Epoch},
feature_set::{ feature_set::{
stake_merge_with_unmatched_credits_observed, stake_split_uses_rent_sysvar, FeatureSet, stake_merge_with_unmatched_credits_observed, stake_split_uses_rent_sysvar, FeatureSet,
}, },
instruction::{checked_add, InstructionError}, instruction::{checked_add, InstructionError},
keyed_account::KeyedAccount,
pubkey::Pubkey, pubkey::Pubkey,
rent::{Rent, ACCOUNT_STORAGE_OVERHEAD}, rent::{Rent, ACCOUNT_STORAGE_OVERHEAD},
stake::{ stake::{
@ -27,6 +26,7 @@ use {
program::id, program::id,
}, },
stake_history::{StakeHistory, StakeHistoryEntry}, stake_history::{StakeHistory, StakeHistoryEntry},
transaction_context::{BorrowedAccount, InstructionContext, TransactionContext},
}, },
solana_vote_program::vote_state::{VoteState, VoteStateVersions}, solana_vote_program::vote_state::{VoteState, VoteStateVersions},
std::{collections::HashSet, convert::TryFrom}, std::{collections::HashSet, convert::TryFrom},
@ -366,21 +366,21 @@ fn calculate_stake_rewards(
} }
pub fn initialize( pub fn initialize(
stake_account: &KeyedAccount, stake_account: &mut BorrowedAccount,
authorized: &Authorized, authorized: &Authorized,
lockup: &Lockup, lockup: &Lockup,
rent: &Rent, rent: &Rent,
feature_set: &FeatureSet, feature_set: &FeatureSet,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if stake_account.data_len()? != std::mem::size_of::<StakeState>() { if stake_account.get_data().len() != std::mem::size_of::<StakeState>() {
return Err(InstructionError::InvalidAccountData); return Err(InstructionError::InvalidAccountData);
} }
if let StakeState::Uninitialized = stake_account.state()? { if let StakeState::Uninitialized = stake_account.get_state()? {
let rent_exempt_reserve = rent.minimum_balance(stake_account.data_len()?); let rent_exempt_reserve = rent.minimum_balance(stake_account.get_data().len());
let minimum_delegation = crate::get_minimum_delegation(feature_set); let minimum_delegation = crate::get_minimum_delegation(feature_set);
let minimum_balance = rent_exempt_reserve + minimum_delegation; 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 { stake_account.set_state(&StakeState::Initialized(Meta {
rent_exempt_reserve, rent_exempt_reserve,
authorized: *authorized, authorized: *authorized,
@ -398,7 +398,7 @@ pub fn initialize(
/// multiple times, but will implicitly withdraw authorization from the previously authorized /// multiple times, but will implicitly withdraw authorization from the previously authorized
/// staker. The default staker is the owner of the stake account's pubkey. /// staker. The default staker is the owner of the stake account's pubkey.
pub fn authorize( pub fn authorize(
stake_account: &KeyedAccount, stake_account: &mut BorrowedAccount,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
new_authority: &Pubkey, new_authority: &Pubkey,
stake_authorize: StakeAuthorize, stake_authorize: StakeAuthorize,
@ -406,7 +406,7 @@ pub fn authorize(
clock: &Clock, clock: &Clock,
custodian: Option<&Pubkey>, custodian: Option<&Pubkey>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
match stake_account.state()? { match stake_account.get_state()? {
StakeState::Stake(mut meta, stake) => { StakeState::Stake(mut meta, stake) => {
meta.authorized.authorize( meta.authorized.authorize(
signers, signers,
@ -437,9 +437,12 @@ pub fn authorize(
} }
} }
#[allow(clippy::too_many_arguments)]
pub fn authorize_with_seed( pub fn authorize_with_seed(
stake_account: &KeyedAccount, transaction_context: &TransactionContext,
authority_base: &KeyedAccount, instruction_context: &InstructionContext,
stake_account: &mut BorrowedAccount,
authority_base_index: usize,
authority_seed: &str, authority_seed: &str,
authority_owner: &Pubkey, authority_owner: &Pubkey,
new_authority: &Pubkey, new_authority: &Pubkey,
@ -449,7 +452,10 @@ pub fn authorize_with_seed(
custodian: Option<&Pubkey>, custodian: Option<&Pubkey>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let mut signers = HashSet::default(); 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( signers.insert(Pubkey::create_with_seed(
base_pubkey, base_pubkey,
authority_seed, authority_seed,
@ -468,26 +474,35 @@ pub fn authorize_with_seed(
} }
pub fn delegate( pub fn delegate(
stake_account: &KeyedAccount, transaction_context: &TransactionContext,
vote_account: &KeyedAccount, instruction_context: &InstructionContext,
stake_account_index: usize,
vote_account_index: usize,
clock: &Clock, clock: &Clock,
stake_history: &StakeHistory, stake_history: &StakeHistory,
config: &Config, config: &Config,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> 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); return Err(InstructionError::IncorrectProgramId);
} }
let vote_pubkey = *vote_account.get_key();
let vote_state = vote_account.get_state::<VoteStateVersions>();
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) => { StakeState::Initialized(meta) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?; meta.authorized.check(signers, StakeAuthorize::Staker)?;
let ValidatedDelegatedInfo { stake_amount } = let ValidatedDelegatedInfo { stake_amount } =
validate_delegated_amount(stake_account, &meta)?; validate_delegated_amount(&stake_account, &meta)?;
let stake = new_stake( let stake = new_stake(
stake_amount, stake_amount,
vote_account.unsigned_key(), &vote_pubkey,
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(), &vote_state?.convert_to_current(),
clock.epoch, clock.epoch,
config, config,
); );
@ -496,12 +511,12 @@ pub fn delegate(
StakeState::Stake(meta, mut stake) => { StakeState::Stake(meta, mut stake) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?; meta.authorized.check(signers, StakeAuthorize::Staker)?;
let ValidatedDelegatedInfo { stake_amount } = let ValidatedDelegatedInfo { stake_amount } =
validate_delegated_amount(stake_account, &meta)?; validate_delegated_amount(&stake_account, &meta)?;
redelegate( redelegate(
&mut stake, &mut stake,
stake_amount, stake_amount,
vote_account.unsigned_key(), &vote_pubkey,
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(), &vote_state?.convert_to_current(),
clock, clock,
stake_history, stake_history,
config, config,
@ -513,11 +528,11 @@ pub fn delegate(
} }
pub fn deactivate( pub fn deactivate(
stake_account: &KeyedAccount, stake_account: &mut BorrowedAccount,
clock: &Clock, clock: &Clock,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> 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)?; meta.authorized.check(signers, StakeAuthorize::Staker)?;
stake.deactivate(clock.epoch)?; stake.deactivate(clock.epoch)?;
@ -528,12 +543,12 @@ pub fn deactivate(
} }
pub fn set_lockup( pub fn set_lockup(
stake_account: &KeyedAccount, stake_account: &mut BorrowedAccount,
lockup: &LockupArgs, lockup: &LockupArgs,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
clock: &Clock, clock: &Clock,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
match stake_account.state()? { match stake_account.get_state()? {
StakeState::Initialized(mut meta) => { StakeState::Initialized(mut meta) => {
meta.set_lockup(lockup, signers, clock)?; meta.set_lockup(lockup, signers, clock)?;
stake_account.set_state(&StakeState::Initialized(meta)) stake_account.set_state(&StakeState::Initialized(meta))
@ -547,32 +562,43 @@ pub fn set_lockup(
} }
pub fn split( pub fn split(
stake_account: &KeyedAccount,
invoke_context: &InvokeContext, invoke_context: &InvokeContext,
transaction_context: &TransactionContext,
instruction_context: &InstructionContext,
stake_account_index: usize,
lamports: u64, lamports: u64,
split: &KeyedAccount, split_index: usize,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> 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); return Err(InstructionError::IncorrectProgramId);
} }
if split.data_len()? != std::mem::size_of::<StakeState>() { if split.get_data().len() != std::mem::size_of::<StakeState>() {
return Err(InstructionError::InvalidAccountData); return Err(InstructionError::InvalidAccountData);
} }
if !matches!(split.state()?, StakeState::Uninitialized) { if !matches!(split.get_state()?, StakeState::Uninitialized) {
return Err(InstructionError::InvalidAccountData); 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); 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) => { StakeState::Stake(meta, mut stake) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?; meta.authorized.check(signers, StakeAuthorize::Staker)?;
let validated_split_info = validate_split_amount( let validated_split_info = validate_split_amount(
invoke_context, invoke_context,
stake_account, transaction_context,
split, instruction_context,
stake_account_index,
split_index,
lamports, lamports,
&meta, &meta,
Some(&stake), Some(&stake),
@ -605,7 +631,7 @@ pub fn split(
lamports.saturating_sub( lamports.saturating_sub(
validated_split_info validated_split_info
.destination_rent_exempt_reserve .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; let mut split_meta = meta;
split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve; 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))?; 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))?; split.set_state(&StakeState::Stake(split_meta, split_stake))?;
} }
StakeState::Initialized(meta) => { StakeState::Initialized(meta) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?; meta.authorized.check(signers, StakeAuthorize::Staker)?;
let validated_split_info = let validated_split_info = validate_split_amount(
validate_split_amount(invoke_context, stake_account, split, lamports, &meta, None)?; invoke_context,
transaction_context,
instruction_context,
stake_account_index,
split_index,
lamports,
&meta,
None,
)?;
let mut split_meta = meta; let mut split_meta = meta;
split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve; 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))?; split.set_state(&StakeState::Initialized(split_meta))?;
} }
StakeState::Uninitialized => { 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); return Err(InstructionError::MissingRequiredSignature);
} }
} }
@ -633,54 +677,67 @@ pub fn split(
} }
// Deinitialize state upon zero balance // 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)?; stake_account.set_state(&StakeState::Uninitialized)?;
} }
drop(stake_account);
split let mut split = instruction_context.try_borrow_account(transaction_context, split_index)?;
.try_account_ref_mut()? split.checked_add_lamports(lamports)?;
.checked_add_lamports(lamports)?; drop(split);
stake_account let mut stake_account =
.try_account_ref_mut()? instruction_context.try_borrow_account(transaction_context, stake_account_index)?;
.checked_sub_lamports(lamports)?; stake_account.checked_sub_lamports(lamports)?;
Ok(()) Ok(())
} }
pub fn merge( pub fn merge(
stake_account: &KeyedAccount,
invoke_context: &InvokeContext, invoke_context: &InvokeContext,
source_account: &KeyedAccount, transaction_context: &TransactionContext,
instruction_context: &InstructionContext,
stake_account_index: usize,
source_account_index: usize,
clock: &Clock, clock: &Clock,
stake_history: &StakeHistory, stake_history: &StakeHistory,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let mut source_account =
instruction_context.try_borrow_account(transaction_context, source_account_index)?;
// Ensure source isn't spoofed // Ensure source isn't spoofed
if source_account.owner()? != id() { if *source_account.get_owner() != id() {
return Err(InstructionError::IncorrectProgramId); return Err(InstructionError::IncorrectProgramId);
} }
// Close the stake_account-reference loophole // 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); 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"); ic_msg!(invoke_context, "Checking if destination stake is mergeable");
let stake_merge_kind = MergeKind::get_if_mergeable( let stake_merge_kind = MergeKind::get_if_mergeable(
invoke_context, invoke_context,
&stake_account.state()?, &stake_account.get_state()?,
stake_account.lamports()?, stake_account.get_lamports(),
clock, clock,
stake_history, stake_history,
)?; )?;
let meta = stake_merge_kind.meta();
// Authorized staker is allowed to split/merge accounts // 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"); ic_msg!(invoke_context, "Checking if source stake is mergeable");
let source_merge_kind = MergeKind::get_if_mergeable( let source_merge_kind = MergeKind::get_if_mergeable(
invoke_context, invoke_context,
&source_account.state()?, &source_account.get_state()?,
source_account.lamports()?, source_account.get_lamports(),
clock, clock,
stake_history, stake_history,
)?; )?;
@ -694,33 +751,37 @@ pub fn merge(
source_account.set_state(&StakeState::Uninitialized)?; source_account.set_state(&StakeState::Uninitialized)?;
// Drain the source stake account // Drain the source stake account
let lamports = source_account.lamports()?; let lamports = source_account.get_lamports();
source_account source_account.checked_sub_lamports(lamports)?;
.try_account_ref_mut()? stake_account.checked_add_lamports(lamports)?;
.checked_sub_lamports(lamports)?;
stake_account
.try_account_ref_mut()?
.checked_add_lamports(lamports)?;
Ok(()) Ok(())
} }
#[allow(clippy::too_many_arguments)]
pub fn withdraw( pub fn withdraw(
stake_account: &KeyedAccount, transaction_context: &TransactionContext,
instruction_context: &InstructionContext,
stake_account_index: usize,
lamports: u64, lamports: u64,
to: &KeyedAccount, to_index: usize,
clock: &Clock, clock: &Clock,
stake_history: &StakeHistory, stake_history: &StakeHistory,
withdraw_authority: &KeyedAccount, withdraw_authority_index: usize,
custodian: Option<&KeyedAccount>, custodian_index: Option<usize>,
feature_set: &FeatureSet, feature_set: &FeatureSet,
) -> Result<(), InstructionError> { ) -> 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 mut signers = HashSet::new();
let withdraw_authority_pubkey = withdraw_authority
.signer_key()
.ok_or(InstructionError::MissingRequiredSignature)?;
signers.insert(*withdraw_authority_pubkey); 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) => { StakeState::Stake(meta, stake) => {
meta.authorized meta.authorized
.check(&signers, StakeAuthorize::Withdrawer)?; .check(&signers, StakeAuthorize::Withdrawer)?;
@ -749,7 +810,7 @@ pub fn withdraw(
(meta.lockup, reserve, false) (meta.lockup, reserve, false)
} }
StakeState::Uninitialized => { StakeState::Uninitialized => {
if !signers.contains(stake_account.unsigned_key()) { if !signers.contains(stake_account.get_key()) {
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
} }
(Lockup::default(), 0, false) // no lockup, no restrictions (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 // verify that lockup has expired or that the withdrawal is signed by
// the custodian, both epoch and unix_timestamp must have passed // 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) { if lockup.is_in_force(clock, custodian_pubkey) {
return Err(StakeError::LockupInForce.into()); return Err(StakeError::LockupInForce.into());
} }
@ -767,27 +838,27 @@ pub fn withdraw(
let lamports_and_reserve = checked_add(lamports, reserve)?; let lamports_and_reserve = checked_add(lamports, reserve)?;
// if the stake is active, we mustn't allow the account to go away // if the stake is active, we mustn't allow the account to go away
if is_staked // line coverage for branch coverage 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); return Err(InstructionError::InsufficientFunds);
} }
if lamports != stake_account.lamports()? // not a full withdrawal if lamports != stake_account.get_lamports() // not a full withdrawal
&& lamports_and_reserve > stake_account.lamports()? && lamports_and_reserve > stake_account.get_lamports()
{ {
assert!(!is_staked); assert!(!is_staked);
return Err(InstructionError::InsufficientFunds); return Err(InstructionError::InsufficientFunds);
} }
// Deinitialize state upon zero balance // Deinitialize state upon zero balance
if lamports == stake_account.lamports()? { if lamports == stake_account.get_lamports() {
stake_account.set_state(&StakeState::Uninitialized)?; stake_account.set_state(&StakeState::Uninitialized)?;
} }
stake_account stake_account.checked_sub_lamports(lamports)?;
.try_account_ref_mut()? drop(stake_account);
.checked_sub_lamports(lamports)?; let mut to = instruction_context.try_borrow_account(transaction_context, to_index)?;
to.try_account_ref_mut()?.checked_add_lamports(lamports)?; to.checked_add_lamports(lamports)?;
Ok(()) Ok(())
} }
@ -800,10 +871,12 @@ struct ValidatedDelegatedInfo {
/// Ensure the stake delegation amount is valid. This checks that the account meets the minimum /// 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. /// balance requirements of delegated stake. If not, return an error.
fn validate_delegated_amount( fn validate_delegated_amount(
account: &KeyedAccount, account: &BorrowedAccount,
meta: &Meta, meta: &Meta,
) -> Result<ValidatedDelegatedInfo, InstructionError> { ) -> Result<ValidatedDelegatedInfo, InstructionError> {
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 }) Ok(ValidatedDelegatedInfo { stake_amount })
} }
@ -821,14 +894,24 @@ struct ValidatedSplitInfo {
/// not, return an error. /// not, return an error.
fn validate_split_amount( fn validate_split_amount(
invoke_context: &InvokeContext, invoke_context: &InvokeContext,
source_account: &KeyedAccount, transaction_context: &TransactionContext,
destination_account: &KeyedAccount, instruction_context: &InstructionContext,
source_account_index: usize,
destination_account_index: usize,
lamports: u64, lamports: u64,
source_meta: &Meta, source_meta: &Meta,
source_stake: Option<&Stake>, source_stake: Option<&Stake>,
) -> Result<ValidatedSplitInfo, InstructionError> { ) -> Result<ValidatedSplitInfo, InstructionError> {
let source_lamports = source_account.lamports()?; let source_account =
let destination_lamports = destination_account.lamports()?; 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 // Split amount has to be something
if lamports == 0 { if lamports == 0 {
@ -869,12 +952,12 @@ fn validate_split_amount(
.is_active(&stake_split_uses_rent_sysvar::ID) .is_active(&stake_split_uses_rent_sysvar::ID)
{ {
let rent = invoke_context.get_sysvar_cache().get_rent()?; let rent = invoke_context.get_sysvar_cache().get_rent()?;
rent.minimum_balance(destination_account.data_len()?) rent.minimum_balance(destination_data_len)
} else { } else {
calculate_split_rent_exempt_reserve( calculate_split_rent_exempt_reserve(
source_meta.rent_exempt_reserve, source_meta.rent_exempt_reserve,
source_account.data_len()? as u64, source_data_len as u64,
destination_account.data_len()? as u64, destination_data_len as u64,
) )
}; };
let destination_minimum_balance = let destination_minimum_balance =