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},
genesis_config::GenesisConfig,
stake::config::{self, Config},
transaction_context::BorrowedAccount,
},
};
pub fn from<T: ReadableAccount>(account: &T) -> Option<Config> {
get_config_data(account.data())
pub fn from(account: &BorrowedAccount) -> Option<Config> {
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()));
}
}

View File

@ -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<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(
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)

View File

@ -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::<StakeState>() {
if stake_account.get_data().len() != std::mem::size_of::<StakeState>() {
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<Pubkey>,
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<Pubkey>,
) -> 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::<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) => {
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::<VoteStateVersions>::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::<VoteStateVersions>::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<Pubkey>,
) -> 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<Pubkey>,
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<Pubkey>,
) -> 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::<StakeState>() {
if split.get_data().len() != std::mem::size_of::<StakeState>() {
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<Pubkey>,
) -> 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<usize>,
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<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 })
}
@ -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<ValidatedSplitInfo, InstructionError> {
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 =