Refactor: Remove `trait` from stake keyed account (#24148)

Removes trait from StakeAccount.
This commit is contained in:
Alexander Meißner 2022-04-06 22:58:09 +02:00 committed by GitHub
parent 25304ce485
commit efb9cbd8e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 445 additions and 493 deletions

View File

@ -1,10 +1,11 @@
#[deprecated(
since = "1.8.0",
note = "Please use `solana_sdk::stake::instruction` or `solana_program::stake::instruction` instead"
)]
pub use solana_sdk::stake::instruction::*;
use {
crate::{config, stake_state::StakeAccount},
crate::{
config,
stake_state::{
authorize, authorize_with_seed, deactivate, delegate, initialize, merge, set_lockup,
split, withdraw,
},
},
log::*,
solana_program_runtime::{
invoke_context::InvokeContext, sysvar_cache::get_sysvar_with_account_check,
@ -15,7 +16,7 @@ use {
keyed_account::keyed_account_at_index,
program_utils::limited_deserialize,
stake::{
instruction::StakeInstruction,
instruction::{LockupArgs, StakeInstruction},
program::id,
state::{Authorized, Lockup},
},
@ -44,7 +45,7 @@ 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)?;
me.initialize(&authorized, &lockup, &rent, &invoke_context.feature_set)
initialize(me, &authorized, &lockup, &rent, &invoke_context.feature_set)
}
StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => {
instruction_context.check_number_of_instruction_accounts(3)?;
@ -62,7 +63,8 @@ pub fn process_instruction(
.ok()
.map(|ka| ka.unsigned_key());
me.authorize(
authorize(
me,
&signers,
&authorized_pubkey,
stake_authorize,
@ -71,7 +73,8 @@ pub fn process_instruction(
custodian,
)
} else {
me.authorize(
authorize(
me,
&signers,
&authorized_pubkey,
stake_authorize,
@ -97,7 +100,8 @@ pub fn process_instruction(
.ok()
.map(|ka| ka.unsigned_key());
me.authorize_with_seed(
authorize_with_seed(
me,
authority_base,
&args.authority_seed,
&args.authority_owner,
@ -108,7 +112,8 @@ pub fn process_instruction(
custodian,
)
} else {
me.authorize_with_seed(
authorize_with_seed(
me,
authority_base,
&args.authority_seed,
&args.authority_owner,
@ -138,13 +143,13 @@ pub fn process_instruction(
}
let config = config::from(&*config_account.try_account_ref()?)
.ok_or(InstructionError::InvalidArgument)?;
me.delegate(vote, &clock, &stake_history, &config, &signers)
delegate(me, vote, &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)?;
me.split(invoke_context, lamports, split_stake, &signers)
split(me, invoke_context, lamports, split_stake, &signers)
}
StakeInstruction::Merge => {
instruction_context.check_number_of_instruction_accounts(2)?;
@ -157,7 +162,8 @@ pub fn process_instruction(
instruction_context,
3,
)?;
me.merge(
merge(
me,
invoke_context,
source_stake,
&clock,
@ -176,7 +182,8 @@ pub fn process_instruction(
3,
)?;
instruction_context.check_number_of_instruction_accounts(5)?;
me.withdraw(
withdraw(
me,
lamports,
to,
&clock,
@ -189,11 +196,11 @@ pub fn process_instruction(
StakeInstruction::Deactivate => {
let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?;
me.deactivate(&clock, &signers)
deactivate(me, &clock, &signers)
}
StakeInstruction::SetLockup(lockup) => {
let clock = invoke_context.get_sysvar_cache().get_clock()?;
me.set_lockup(&lockup, &signers, &clock)
set_lockup(me, &lockup, &signers, &clock)
}
StakeInstruction::InitializeChecked => {
if invoke_context
@ -214,7 +221,8 @@ pub fn process_instruction(
let rent =
get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?;
me.initialize(
initialize(
me,
&authorized,
&Lockup::default(),
&rent,
@ -243,7 +251,8 @@ pub fn process_instruction(
.ok()
.map(|ka| ka.unsigned_key());
me.authorize(
authorize(
me,
&signers,
authorized_pubkey,
stake_authorize,
@ -275,7 +284,8 @@ pub fn process_instruction(
.ok()
.map(|ka| ka.unsigned_key());
me.authorize_with_seed(
authorize_with_seed(
me,
authority_base,
&args.authority_seed,
&args.authority_owner,
@ -312,7 +322,7 @@ pub fn process_instruction(
custodian,
};
let clock = invoke_context.get_sysvar_cache().get_clock()?;
me.set_lockup(&lockup, &signers, &clock)
set_lockup(me, &lockup, &signers, &clock)
} else {
Err(InstructionError::InvalidInstructionData)
}
@ -356,7 +366,11 @@ mod tests {
rent::Rent,
stake::{
config as stake_config,
instruction::{self, LockupArgs},
instruction::{
self, authorize_checked, authorize_checked_with_seed, initialize_checked,
set_lockup_checked, AuthorizeCheckedWithSeedArgs, AuthorizeWithSeedArgs,
LockupArgs, StakeError,
},
state::{Authorized, Lockup, StakeAuthorize},
},
stake_history::{StakeHistory, StakeHistoryEntry},

View File

@ -365,484 +365,422 @@ fn calculate_stake_rewards(
Some((staker_rewards, voter_rewards, credits_observed))
}
pub trait StakeAccount {
fn initialize(
&self,
authorized: &Authorized,
lockup: &Lockup,
rent: &Rent,
feature_set: &FeatureSet,
) -> Result<(), InstructionError>;
fn authorize(
&self,
signers: &HashSet<Pubkey>,
new_authority: &Pubkey,
stake_authorize: StakeAuthorize,
require_custodian_for_locked_stake_authorize: bool,
clock: &Clock,
custodian: Option<&Pubkey>,
) -> Result<(), InstructionError>;
fn authorize_with_seed(
&self,
authority_base: &KeyedAccount,
authority_seed: &str,
authority_owner: &Pubkey,
new_authority: &Pubkey,
stake_authorize: StakeAuthorize,
require_custodian_for_locked_stake_authorize: bool,
clock: &Clock,
custodian: Option<&Pubkey>,
) -> Result<(), InstructionError>;
fn delegate(
&self,
vote_account: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
config: &Config,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError>;
fn set_lockup(
&self,
lockup: &LockupArgs,
signers: &HashSet<Pubkey>,
clock: &Clock,
) -> Result<(), InstructionError>;
fn split(
&self,
invoke_context: &InvokeContext,
lamports: u64,
split_stake: &KeyedAccount,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
fn merge(
&self,
invoke_context: &InvokeContext,
source_stake: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
fn withdraw(
&self,
lamports: u64,
to: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
withdraw_authority: &KeyedAccount,
custodian: Option<&KeyedAccount>,
feature_set: &FeatureSet,
) -> Result<(), InstructionError>;
pub fn initialize(
stake_account: &KeyedAccount,
authorized: &Authorized,
lockup: &Lockup,
rent: &Rent,
feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
if stake_account.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()?);
let minimum_delegation = crate::get_minimum_delegation(feature_set);
let minimum_balance = rent_exempt_reserve + minimum_delegation;
if stake_account.lamports()? >= minimum_balance {
stake_account.set_state(&StakeState::Initialized(Meta {
rent_exempt_reserve,
authorized: *authorized,
lockup: *lockup,
}))
} else {
Err(InstructionError::InsufficientFunds)
}
} else {
Err(InstructionError::InvalidAccountData)
}
}
impl<'a> StakeAccount for KeyedAccount<'a> {
fn initialize(
&self,
authorized: &Authorized,
lockup: &Lockup,
rent: &Rent,
feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
if self.data_len()? != std::mem::size_of::<StakeState>() {
return Err(InstructionError::InvalidAccountData);
}
if let StakeState::Uninitialized = self.state()? {
let rent_exempt_reserve = rent.minimum_balance(self.data_len()?);
let minimum_delegation = crate::get_minimum_delegation(feature_set);
let minimum_balance = rent_exempt_reserve + minimum_delegation;
if self.lamports()? >= minimum_balance {
self.set_state(&StakeState::Initialized(Meta {
rent_exempt_reserve,
authorized: *authorized,
lockup: *lockup,
}))
} else {
Err(InstructionError::InsufficientFunds)
}
} else {
Err(InstructionError::InvalidAccountData)
}
}
/// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called
/// multiple times, but will implicitly withdraw authorization from the previously authorized
/// staker. The default staker is the owner of the stake account's pubkey.
fn authorize(
&self,
signers: &HashSet<Pubkey>,
new_authority: &Pubkey,
stake_authorize: StakeAuthorize,
require_custodian_for_locked_stake_authorize: bool,
clock: &Clock,
custodian: Option<&Pubkey>,
) -> Result<(), InstructionError> {
match self.state()? {
StakeState::Stake(mut meta, stake) => {
meta.authorized.authorize(
signers,
new_authority,
stake_authorize,
if require_custodian_for_locked_stake_authorize {
Some((&meta.lockup, clock, custodian))
} else {
None
},
)?;
self.set_state(&StakeState::Stake(meta, stake))
}
StakeState::Initialized(mut meta) => {
meta.authorized.authorize(
signers,
new_authority,
stake_authorize,
if require_custodian_for_locked_stake_authorize {
Some((&meta.lockup, clock, custodian))
} else {
None
},
)?;
self.set_state(&StakeState::Initialized(meta))
}
_ => Err(InstructionError::InvalidAccountData),
}
}
fn authorize_with_seed(
&self,
authority_base: &KeyedAccount,
authority_seed: &str,
authority_owner: &Pubkey,
new_authority: &Pubkey,
stake_authorize: StakeAuthorize,
require_custodian_for_locked_stake_authorize: bool,
clock: &Clock,
custodian: Option<&Pubkey>,
) -> Result<(), InstructionError> {
let mut signers = HashSet::default();
if let Some(base_pubkey) = authority_base.signer_key() {
signers.insert(Pubkey::create_with_seed(
base_pubkey,
authority_seed,
authority_owner,
)?);
}
self.authorize(
&signers,
new_authority,
stake_authorize,
require_custodian_for_locked_stake_authorize,
clock,
custodian,
)
}
fn delegate(
&self,
vote_account: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
config: &Config,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
if vote_account.owner()? != solana_vote_program::id() {
return Err(InstructionError::IncorrectProgramId);
}
match self.state()? {
StakeState::Initialized(meta) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
let ValidatedDelegatedInfo { stake_amount } =
validate_delegated_amount(self, &meta)?;
let stake = new_stake(
stake_amount,
vote_account.unsigned_key(),
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(),
clock.epoch,
config,
);
self.set_state(&StakeState::Stake(meta, stake))
}
StakeState::Stake(meta, mut stake) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
let ValidatedDelegatedInfo { stake_amount } =
validate_delegated_amount(self, &meta)?;
redelegate(
&mut stake,
stake_amount,
vote_account.unsigned_key(),
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(),
clock,
stake_history,
config,
)?;
self.set_state(&StakeState::Stake(meta, stake))
}
_ => Err(InstructionError::InvalidAccountData),
}
}
fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError> {
if let StakeState::Stake(meta, mut stake) = self.state()? {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
stake.deactivate(clock.epoch)?;
self.set_state(&StakeState::Stake(meta, stake))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn set_lockup(
&self,
lockup: &LockupArgs,
signers: &HashSet<Pubkey>,
clock: &Clock,
) -> Result<(), InstructionError> {
match self.state()? {
StakeState::Initialized(mut meta) => {
meta.set_lockup(lockup, signers, clock)?;
self.set_state(&StakeState::Initialized(meta))
}
StakeState::Stake(mut meta, stake) => {
meta.set_lockup(lockup, signers, clock)?;
self.set_state(&StakeState::Stake(meta, stake))
}
_ => Err(InstructionError::InvalidAccountData),
}
}
fn split(
&self,
invoke_context: &InvokeContext,
lamports: u64,
split: &KeyedAccount,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
if split.owner()? != id() {
return Err(InstructionError::IncorrectProgramId);
}
if split.data_len()? != std::mem::size_of::<StakeState>() {
return Err(InstructionError::InvalidAccountData);
}
if !matches!(split.state()?, StakeState::Uninitialized) {
return Err(InstructionError::InvalidAccountData);
}
if lamports > self.lamports()? {
return Err(InstructionError::InsufficientFunds);
}
match self.state()? {
StakeState::Stake(meta, mut stake) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
let validated_split_info = validate_split_amount(
invoke_context,
self,
split,
lamports,
&meta,
Some(&stake),
)?;
// split the stake, subtract rent_exempt_balance unless
// the destination account already has those lamports
// in place.
// this means that the new stake account will have a stake equivalent to
// lamports minus rent_exempt_reserve if it starts out with a zero balance
let (remaining_stake_delta, split_stake_amount) =
if validated_split_info.source_remaining_balance == 0 {
// If split amount equals the full source stake (as implied by 0
// source_remaining_balance), the new split stake must equal the same
// amount, regardless of any current lamport balance in the split account.
// Since split accounts retain the state of their source account, this
// prevents any magic activation of stake by prefunding the split account.
//
// The new split stake also needs to ignore any positive delta between the
// original rent_exempt_reserve and the split_rent_exempt_reserve, in order
// to prevent magic activation of stake by splitting between accounts of
// different sizes.
let remaining_stake_delta =
lamports.saturating_sub(meta.rent_exempt_reserve);
(remaining_stake_delta, remaining_stake_delta)
} else {
// Otherwise, the new split stake should reflect the entire split
// requested, less any lamports needed to cover the split_rent_exempt_reserve.
(
lamports,
lamports.saturating_sub(
validated_split_info
.destination_rent_exempt_reserve
.saturating_sub(split.lamports()?),
),
)
};
let split_stake = stake.split(remaining_stake_delta, split_stake_amount)?;
let mut split_meta = meta;
split_meta.rent_exempt_reserve =
validated_split_info.destination_rent_exempt_reserve;
self.set_state(&StakeState::Stake(meta, stake))?;
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, self, split, lamports, &meta, None)?;
let mut split_meta = meta;
split_meta.rent_exempt_reserve =
validated_split_info.destination_rent_exempt_reserve;
split.set_state(&StakeState::Initialized(split_meta))?;
}
StakeState::Uninitialized => {
if !signers.contains(self.unsigned_key()) {
return Err(InstructionError::MissingRequiredSignature);
}
}
_ => return Err(InstructionError::InvalidAccountData),
}
// Deinitialize state upon zero balance
if lamports == self.lamports()? {
self.set_state(&StakeState::Uninitialized)?;
}
split
.try_account_ref_mut()?
.checked_add_lamports(lamports)?;
self.try_account_ref_mut()?.checked_sub_lamports(lamports)?;
Ok(())
}
fn merge(
&self,
invoke_context: &InvokeContext,
source_account: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
// Ensure source isn't spoofed
if source_account.owner()? != id() {
return Err(InstructionError::IncorrectProgramId);
}
// Close the self-reference loophole
if source_account.unsigned_key() == self.unsigned_key() {
return Err(InstructionError::InvalidArgument);
}
ic_msg!(invoke_context, "Checking if destination stake is mergeable");
let stake_merge_kind =
MergeKind::get_if_mergeable(invoke_context, self, clock, stake_history)?;
let meta = stake_merge_kind.meta();
// Authorized staker is allowed to split/merge accounts
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, clock, stake_history)?;
ic_msg!(invoke_context, "Merging stake accounts");
if let Some(merged_state) =
stake_merge_kind.merge(invoke_context, source_merge_kind, clock)?
{
self.set_state(&merged_state)?;
}
// Source is about to be drained, deinitialize its state
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)?;
self.try_account_ref_mut()?.checked_add_lamports(lamports)?;
Ok(())
}
fn withdraw(
&self,
lamports: u64,
to: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
withdraw_authority: &KeyedAccount,
custodian: Option<&KeyedAccount>,
feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
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 self.state()? {
StakeState::Stake(meta, stake) => {
meta.authorized
.check(&signers, StakeAuthorize::Withdrawer)?;
// if we have a deactivation epoch and we're in cooldown
let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
stake.delegation.stake(clock.epoch, Some(stake_history))
/// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called
/// 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,
signers: &HashSet<Pubkey>,
new_authority: &Pubkey,
stake_authorize: StakeAuthorize,
require_custodian_for_locked_stake_authorize: bool,
clock: &Clock,
custodian: Option<&Pubkey>,
) -> Result<(), InstructionError> {
match stake_account.state()? {
StakeState::Stake(mut meta, stake) => {
meta.authorized.authorize(
signers,
new_authority,
stake_authorize,
if require_custodian_for_locked_stake_authorize {
Some((&meta.lockup, clock, custodian))
} else {
// Assume full stake if the stake account hasn't been
// de-activated, because in the future the exposed stake
// might be higher than stake.stake() due to warmup
stake.delegation.stake
};
let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
(meta.lockup, staked_and_reserve, staked != 0)
}
StakeState::Initialized(meta) => {
meta.authorized
.check(&signers, StakeAuthorize::Withdrawer)?;
// stake accounts must have a balance >= rent_exempt_reserve + minimum_stake_delegation
let reserve = checked_add(
meta.rent_exempt_reserve,
crate::get_minimum_delegation(feature_set),
)?;
(meta.lockup, reserve, false)
}
StakeState::Uninitialized => {
if !signers.contains(self.unsigned_key()) {
return Err(InstructionError::MissingRequiredSignature);
}
(Lockup::default(), 0, false) // no lockup, no restrictions
}
_ => return Err(InstructionError::InvalidAccountData),
};
// 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());
if lockup.is_in_force(clock, custodian_pubkey) {
return Err(StakeError::LockupInForce.into());
None
},
)?;
stake_account.set_state(&StakeState::Stake(meta, stake))
}
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 > self.lamports()?
{
return Err(InstructionError::InsufficientFunds);
StakeState::Initialized(mut meta) => {
meta.authorized.authorize(
signers,
new_authority,
stake_authorize,
if require_custodian_for_locked_stake_authorize {
Some((&meta.lockup, clock, custodian))
} else {
None
},
)?;
stake_account.set_state(&StakeState::Initialized(meta))
}
if lamports != self.lamports()? // not a full withdrawal
&& lamports_and_reserve > self.lamports()?
{
assert!(!is_staked);
return Err(InstructionError::InsufficientFunds);
}
// Deinitialize state upon zero balance
if lamports == self.lamports()? {
self.set_state(&StakeState::Uninitialized)?;
}
self.try_account_ref_mut()?.checked_sub_lamports(lamports)?;
to.try_account_ref_mut()?.checked_add_lamports(lamports)?;
Ok(())
_ => Err(InstructionError::InvalidAccountData),
}
}
pub fn authorize_with_seed(
stake_account: &KeyedAccount,
authority_base: &KeyedAccount,
authority_seed: &str,
authority_owner: &Pubkey,
new_authority: &Pubkey,
stake_authorize: StakeAuthorize,
require_custodian_for_locked_stake_authorize: bool,
clock: &Clock,
custodian: Option<&Pubkey>,
) -> Result<(), InstructionError> {
let mut signers = HashSet::default();
if let Some(base_pubkey) = authority_base.signer_key() {
signers.insert(Pubkey::create_with_seed(
base_pubkey,
authority_seed,
authority_owner,
)?);
}
authorize(
stake_account,
&signers,
new_authority,
stake_authorize,
require_custodian_for_locked_stake_authorize,
clock,
custodian,
)
}
pub fn delegate(
stake_account: &KeyedAccount,
vote_account: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
config: &Config,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
if vote_account.owner()? != solana_vote_program::id() {
return Err(InstructionError::IncorrectProgramId);
}
match stake_account.state()? {
StakeState::Initialized(meta) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
let ValidatedDelegatedInfo { stake_amount } =
validate_delegated_amount(stake_account, &meta)?;
let stake = new_stake(
stake_amount,
vote_account.unsigned_key(),
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(),
clock.epoch,
config,
);
stake_account.set_state(&StakeState::Stake(meta, stake))
}
StakeState::Stake(meta, mut stake) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
let ValidatedDelegatedInfo { stake_amount } =
validate_delegated_amount(stake_account, &meta)?;
redelegate(
&mut stake,
stake_amount,
vote_account.unsigned_key(),
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(),
clock,
stake_history,
config,
)?;
stake_account.set_state(&StakeState::Stake(meta, stake))
}
_ => Err(InstructionError::InvalidAccountData),
}
}
pub fn deactivate(
stake_account: &KeyedAccount,
clock: &Clock,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
if let StakeState::Stake(meta, mut stake) = stake_account.state()? {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
stake.deactivate(clock.epoch)?;
stake_account.set_state(&StakeState::Stake(meta, stake))
} else {
Err(InstructionError::InvalidAccountData)
}
}
pub fn set_lockup(
stake_account: &KeyedAccount,
lockup: &LockupArgs,
signers: &HashSet<Pubkey>,
clock: &Clock,
) -> Result<(), InstructionError> {
match stake_account.state()? {
StakeState::Initialized(mut meta) => {
meta.set_lockup(lockup, signers, clock)?;
stake_account.set_state(&StakeState::Initialized(meta))
}
StakeState::Stake(mut meta, stake) => {
meta.set_lockup(lockup, signers, clock)?;
stake_account.set_state(&StakeState::Stake(meta, stake))
}
_ => Err(InstructionError::InvalidAccountData),
}
}
pub fn split(
stake_account: &KeyedAccount,
invoke_context: &InvokeContext,
lamports: u64,
split: &KeyedAccount,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
if split.owner()? != id() {
return Err(InstructionError::IncorrectProgramId);
}
if split.data_len()? != std::mem::size_of::<StakeState>() {
return Err(InstructionError::InvalidAccountData);
}
if !matches!(split.state()?, StakeState::Uninitialized) {
return Err(InstructionError::InvalidAccountData);
}
if lamports > stake_account.lamports()? {
return Err(InstructionError::InsufficientFunds);
}
match stake_account.state()? {
StakeState::Stake(meta, mut stake) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
let validated_split_info = validate_split_amount(
invoke_context,
stake_account,
split,
lamports,
&meta,
Some(&stake),
)?;
// split the stake, subtract rent_exempt_balance unless
// the destination account already has those lamports
// in place.
// this means that the new stake account will have a stake equivalent to
// lamports minus rent_exempt_reserve if it starts out with a zero balance
let (remaining_stake_delta, split_stake_amount) =
if validated_split_info.source_remaining_balance == 0 {
// If split amount equals the full source stake (as implied by 0
// source_remaining_balance), the new split stake must equal the same
// amount, regardless of any current lamport balance in the split account.
// Since split accounts retain the state of their source account, this
// prevents any magic activation of stake by prefunding the split account.
//
// The new split stake also needs to ignore any positive delta between the
// original rent_exempt_reserve and the split_rent_exempt_reserve, in order
// to prevent magic activation of stake by splitting between accounts of
// different sizes.
let remaining_stake_delta = lamports.saturating_sub(meta.rent_exempt_reserve);
(remaining_stake_delta, remaining_stake_delta)
} else {
// Otherwise, the new split stake should reflect the entire split
// requested, less any lamports needed to cover the split_rent_exempt_reserve.
(
lamports,
lamports.saturating_sub(
validated_split_info
.destination_rent_exempt_reserve
.saturating_sub(split.lamports()?),
),
)
};
let split_stake = stake.split(remaining_stake_delta, split_stake_amount)?;
let mut split_meta = meta;
split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
stake_account.set_state(&StakeState::Stake(meta, stake))?;
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 mut split_meta = meta;
split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
split.set_state(&StakeState::Initialized(split_meta))?;
}
StakeState::Uninitialized => {
if !signers.contains(stake_account.unsigned_key()) {
return Err(InstructionError::MissingRequiredSignature);
}
}
_ => return Err(InstructionError::InvalidAccountData),
}
// Deinitialize state upon zero balance
if lamports == stake_account.lamports()? {
stake_account.set_state(&StakeState::Uninitialized)?;
}
split
.try_account_ref_mut()?
.checked_add_lamports(lamports)?;
stake_account
.try_account_ref_mut()?
.checked_sub_lamports(lamports)?;
Ok(())
}
pub fn merge(
stake_account: &KeyedAccount,
invoke_context: &InvokeContext,
source_account: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
// Ensure source isn't spoofed
if source_account.owner()? != id() {
return Err(InstructionError::IncorrectProgramId);
}
// Close the stake_account-reference loophole
if source_account.unsigned_key() == stake_account.unsigned_key() {
return Err(InstructionError::InvalidArgument);
}
ic_msg!(invoke_context, "Checking if destination stake is mergeable");
let stake_merge_kind =
MergeKind::get_if_mergeable(invoke_context, stake_account, clock, stake_history)?;
let meta = stake_merge_kind.meta();
// Authorized staker is allowed to split/merge accounts
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, clock, stake_history)?;
ic_msg!(invoke_context, "Merging stake accounts");
if let Some(merged_state) = stake_merge_kind.merge(invoke_context, source_merge_kind, clock)? {
stake_account.set_state(&merged_state)?;
}
// Source is about to be drained, deinitialize its state
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)?;
Ok(())
}
pub fn withdraw(
stake_account: &KeyedAccount,
lamports: u64,
to: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
withdraw_authority: &KeyedAccount,
custodian: Option<&KeyedAccount>,
feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
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()? {
StakeState::Stake(meta, stake) => {
meta.authorized
.check(&signers, StakeAuthorize::Withdrawer)?;
// if we have a deactivation epoch and we're in cooldown
let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
stake.delegation.stake(clock.epoch, Some(stake_history))
} else {
// Assume full stake if the stake account hasn't been
// de-activated, because in the future the exposed stake
// might be higher than stake.stake() due to warmup
stake.delegation.stake
};
let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
(meta.lockup, staked_and_reserve, staked != 0)
}
StakeState::Initialized(meta) => {
meta.authorized
.check(&signers, StakeAuthorize::Withdrawer)?;
// stake accounts must have a balance >= rent_exempt_reserve + minimum_stake_delegation
let reserve = checked_add(
meta.rent_exempt_reserve,
crate::get_minimum_delegation(feature_set),
)?;
(meta.lockup, reserve, false)
}
StakeState::Uninitialized => {
if !signers.contains(stake_account.unsigned_key()) {
return Err(InstructionError::MissingRequiredSignature);
}
(Lockup::default(), 0, false) // no lockup, no restrictions
}
_ => return Err(InstructionError::InvalidAccountData),
};
// 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());
if lockup.is_in_force(clock, custodian_pubkey) {
return Err(StakeError::LockupInForce.into());
}
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()?
{
return Err(InstructionError::InsufficientFunds);
}
if lamports != stake_account.lamports()? // not a full withdrawal
&& lamports_and_reserve > stake_account.lamports()?
{
assert!(!is_staked);
return Err(InstructionError::InsufficientFunds);
}
// Deinitialize state upon zero balance
if lamports == stake_account.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)?;
Ok(())
}
/// After calling `validate_delegated_amount()`, this struct contains calculated values that are used
/// by the caller.
struct ValidatedDelegatedInfo {