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
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,94 +365,23 @@ fn calculate_stake_rewards(
Some((staker_rewards, voter_rewards, credits_observed))
}
pub trait StakeAccount {
fn initialize(
&self,
pub fn initialize(
stake_account: &KeyedAccount,
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>;
}
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>() {
) -> Result<(), InstructionError> {
if stake_account.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()?);
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 self.lamports()? >= minimum_balance {
self.set_state(&StakeState::Initialized(Meta {
if stake_account.lamports()? >= minimum_balance {
stake_account.set_state(&StakeState::Initialized(Meta {
rent_exempt_reserve,
authorized: *authorized,
lockup: *lockup,
@ -463,21 +392,21 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
} 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,
/// 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 self.state()? {
) -> Result<(), InstructionError> {
match stake_account.state()? {
StakeState::Stake(mut meta, stake) => {
meta.authorized.authorize(
signers,
@ -489,7 +418,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
None
},
)?;
self.set_state(&StakeState::Stake(meta, stake))
stake_account.set_state(&StakeState::Stake(meta, stake))
}
StakeState::Initialized(mut meta) => {
meta.authorized.authorize(
@ -502,13 +431,14 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
None
},
)?;
self.set_state(&StakeState::Initialized(meta))
stake_account.set_state(&StakeState::Initialized(meta))
}
_ => Err(InstructionError::InvalidAccountData),
}
}
fn authorize_with_seed(
&self,
}
pub fn authorize_with_seed(
stake_account: &KeyedAccount,
authority_base: &KeyedAccount,
authority_seed: &str,
authority_owner: &Pubkey,
@ -517,7 +447,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
require_custodian_for_locked_stake_authorize: bool,
clock: &Clock,
custodian: Option<&Pubkey>,
) -> Result<(), InstructionError> {
) -> Result<(), InstructionError> {
let mut signers = HashSet::default();
if let Some(base_pubkey) = authority_base.signer_key() {
signers.insert(Pubkey::create_with_seed(
@ -526,7 +456,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
authority_owner,
)?);
}
self.authorize(
authorize(
stake_account,
&signers,
new_authority,
stake_authorize,
@ -534,24 +465,25 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
clock,
custodian,
)
}
fn delegate(
&self,
}
pub fn delegate(
stake_account: &KeyedAccount,
vote_account: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
config: &Config,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
) -> Result<(), InstructionError> {
if vote_account.owner()? != solana_vote_program::id() {
return Err(InstructionError::IncorrectProgramId);
}
match self.state()? {
match stake_account.state()? {
StakeState::Initialized(meta) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
let ValidatedDelegatedInfo { stake_amount } =
validate_delegated_amount(self, &meta)?;
validate_delegated_amount(stake_account, &meta)?;
let stake = new_stake(
stake_amount,
vote_account.unsigned_key(),
@ -559,12 +491,12 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
clock.epoch,
config,
);
self.set_state(&StakeState::Stake(meta, stake))
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(self, &meta)?;
validate_delegated_amount(stake_account, &meta)?;
redelegate(
&mut stake,
stake_amount,
@ -574,47 +506,53 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
stake_history,
config,
)?;
self.set_state(&StakeState::Stake(meta, stake))
stake_account.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()? {
}
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)?;
self.set_state(&StakeState::Stake(meta, stake))
stake_account.set_state(&StakeState::Stake(meta, stake))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn set_lockup(
&self,
}
pub fn set_lockup(
stake_account: &KeyedAccount,
lockup: &LockupArgs,
signers: &HashSet<Pubkey>,
clock: &Clock,
) -> Result<(), InstructionError> {
match self.state()? {
) -> Result<(), InstructionError> {
match stake_account.state()? {
StakeState::Initialized(mut meta) => {
meta.set_lockup(lockup, signers, clock)?;
self.set_state(&StakeState::Initialized(meta))
stake_account.set_state(&StakeState::Initialized(meta))
}
StakeState::Stake(mut meta, stake) => {
meta.set_lockup(lockup, signers, clock)?;
self.set_state(&StakeState::Stake(meta, stake))
stake_account.set_state(&StakeState::Stake(meta, stake))
}
_ => Err(InstructionError::InvalidAccountData),
}
}
}
fn split(
&self,
pub fn split(
stake_account: &KeyedAccount,
invoke_context: &InvokeContext,
lamports: u64,
split: &KeyedAccount,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
) -> Result<(), InstructionError> {
if split.owner()? != id() {
return Err(InstructionError::IncorrectProgramId);
}
@ -624,16 +562,16 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
if !matches!(split.state()?, StakeState::Uninitialized) {
return Err(InstructionError::InvalidAccountData);
}
if lamports > self.lamports()? {
if lamports > stake_account.lamports()? {
return Err(InstructionError::InsufficientFunds);
}
match self.state()? {
match stake_account.state()? {
StakeState::Stake(meta, mut stake) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
let validated_split_info = validate_split_amount(
invoke_context,
self,
stake_account,
split,
lamports,
&meta,
@ -657,8 +595,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
// 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);
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
@ -674,23 +611,21 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
};
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;
split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
self.set_state(&StakeState::Stake(meta, stake))?;
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, self, split, lamports, &meta, None)?;
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_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()) {
if !signers.contains(stake_account.unsigned_key()) {
return Err(InstructionError::MissingRequiredSignature);
}
}
@ -698,37 +633,39 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
}
// Deinitialize state upon zero balance
if lamports == self.lamports()? {
self.set_state(&StakeState::Uninitialized)?;
if lamports == stake_account.lamports()? {
stake_account.set_state(&StakeState::Uninitialized)?;
}
split
.try_account_ref_mut()?
.checked_add_lamports(lamports)?;
self.try_account_ref_mut()?.checked_sub_lamports(lamports)?;
stake_account
.try_account_ref_mut()?
.checked_sub_lamports(lamports)?;
Ok(())
}
}
fn merge(
&self,
pub fn merge(
stake_account: &KeyedAccount,
invoke_context: &InvokeContext,
source_account: &KeyedAccount,
clock: &Clock,
stake_history: &StakeHistory,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
) -> 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() {
// 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, self, clock, stake_history)?;
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
@ -739,10 +676,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
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)?;
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
@ -753,12 +688,14 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
source_account
.try_account_ref_mut()?
.checked_sub_lamports(lamports)?;
self.try_account_ref_mut()?.checked_add_lamports(lamports)?;
stake_account
.try_account_ref_mut()?
.checked_add_lamports(lamports)?;
Ok(())
}
}
fn withdraw(
&self,
pub fn withdraw(
stake_account: &KeyedAccount,
lamports: u64,
to: &KeyedAccount,
clock: &Clock,
@ -766,14 +703,14 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
withdraw_authority: &KeyedAccount,
custodian: Option<&KeyedAccount>,
feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
) -> 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()? {
let (lockup, reserve, is_staked) = match stake_account.state()? {
StakeState::Stake(meta, stake) => {
meta.authorized
.check(&signers, StakeAuthorize::Withdrawer)?;
@ -802,7 +739,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
(meta.lockup, reserve, false)
}
StakeState::Uninitialized => {
if !signers.contains(self.unsigned_key()) {
if !signers.contains(stake_account.unsigned_key()) {
return Err(InstructionError::MissingRequiredSignature);
}
(Lockup::default(), 0, false) // no lockup, no restrictions
@ -820,27 +757,28 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
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()?
&& lamports_and_reserve > stake_account.lamports()?
{
return Err(InstructionError::InsufficientFunds);
}
if lamports != self.lamports()? // not a full withdrawal
&& lamports_and_reserve > self.lamports()?
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 == self.lamports()? {
self.set_state(&StakeState::Uninitialized)?;
if lamports == stake_account.lamports()? {
stake_account.set_state(&StakeState::Uninitialized)?;
}
self.try_account_ref_mut()?.checked_sub_lamports(lamports)?;
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