2019-04-01 16:45:53 -07:00
|
|
|
//! Stake state
|
|
|
|
//! * delegate stakes to vote accounts
|
|
|
|
//! * keep track of rewards
|
|
|
|
//! * own mining pools
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
use {
|
|
|
|
solana_sdk::{
|
|
|
|
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
|
|
|
account_utils::{State, StateMut},
|
|
|
|
clock::{Clock, Epoch},
|
|
|
|
ic_msg,
|
|
|
|
instruction::{checked_add, InstructionError},
|
|
|
|
keyed_account::KeyedAccount,
|
|
|
|
process_instruction::InvokeContext,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
rent::{Rent, ACCOUNT_STORAGE_OVERHEAD},
|
|
|
|
stake::{
|
|
|
|
config::Config,
|
|
|
|
instruction::{LockupArgs, StakeError},
|
|
|
|
program::id,
|
|
|
|
state::{Authorized, Delegation, Lockup, Meta, Stake, StakeAuthorize, StakeState},
|
|
|
|
},
|
|
|
|
stake_history::{StakeHistory, StakeHistoryEntry},
|
|
|
|
},
|
|
|
|
solana_vote_program::vote_state::{VoteState, VoteStateVersions},
|
|
|
|
std::{collections::HashSet, convert::TryFrom},
|
2019-08-12 20:59:57 -07:00
|
|
|
};
|
2021-06-15 09:04:00 -07:00
|
|
|
|
|
|
|
#[deprecated(
|
|
|
|
since = "1.8.0",
|
|
|
|
note = "Please use `solana_sdk::stake::state` or `solana_program::stake::state` instead"
|
|
|
|
)]
|
|
|
|
pub use solana_sdk::stake::state::*;
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2021-02-11 22:24:23 -08:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum SkippedReason {
|
|
|
|
ZeroPoints,
|
|
|
|
ZeroPointValue,
|
|
|
|
ZeroReward,
|
|
|
|
ZeroCreditsAndReturnZero,
|
|
|
|
ZeroCreditsAndReturnCurrent,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<SkippedReason> for InflationPointCalculationEvent {
|
|
|
|
fn from(reason: SkippedReason) -> Self {
|
|
|
|
InflationPointCalculationEvent::Skipped(reason)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-07 23:43:50 -08:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum InflationPointCalculationEvent {
|
2020-11-26 20:20:47 -08:00
|
|
|
CalculatedPoints(u64, u128, u128, u128),
|
2020-11-07 23:43:50 -08:00
|
|
|
SplitRewards(u64, u64, u64, PointValue),
|
2020-11-26 20:20:47 -08:00
|
|
|
EffectiveStakeAtRewardedEpoch(u64),
|
2020-11-07 23:43:50 -08:00
|
|
|
RentExemptReserve(u64),
|
2020-11-15 11:38:46 -08:00
|
|
|
Delegation(Delegation, Pubkey),
|
2020-11-07 23:43:50 -08:00
|
|
|
Commission(u8),
|
2021-02-11 22:24:23 -08:00
|
|
|
CreditsObserved(u64, Option<u64>),
|
|
|
|
Skipped(SkippedReason),
|
2020-11-07 23:43:50 -08:00
|
|
|
}
|
|
|
|
|
2020-12-22 10:20:38 -08:00
|
|
|
pub(crate) fn null_tracer() -> Option<impl FnMut(&InflationPointCalculationEvent)> {
|
2020-11-07 23:43:50 -08:00
|
|
|
None::<fn(&_)>
|
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
// utility function, used by Stakes, tests
|
|
|
|
pub fn from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<StakeState> {
|
|
|
|
account.state().ok()
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
pub fn stake_from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<Stake> {
|
|
|
|
from(account).and_then(|state: StakeState| state.stake())
|
2019-09-26 13:29:29 -07:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
pub fn delegation_from(account: &AccountSharedData) -> Option<Delegation> {
|
|
|
|
from(account).and_then(|state: StakeState| state.delegation())
|
2019-09-12 19:03:28 -07:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
pub fn authorized_from(account: &AccountSharedData) -> Option<Authorized> {
|
|
|
|
from(account).and_then(|state: StakeState| state.authorized())
|
2019-12-19 14:37:47 -08:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
pub fn lockup_from<T: ReadableAccount + StateMut<StakeState>>(account: &T) -> Option<Lockup> {
|
|
|
|
from(account).and_then(|state: StakeState| state.lockup())
|
2019-09-26 13:29:29 -07:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
pub fn meta_from(account: &AccountSharedData) -> Option<Meta> {
|
|
|
|
from(account).and_then(|state: StakeState| state.meta())
|
2019-10-31 11:07:27 -07:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
fn redelegate(
|
|
|
|
stake: &mut Stake,
|
|
|
|
stake_lamports: u64,
|
|
|
|
voter_pubkey: &Pubkey,
|
|
|
|
vote_state: &VoteState,
|
|
|
|
clock: &Clock,
|
|
|
|
stake_history: &StakeHistory,
|
|
|
|
config: &Config,
|
|
|
|
can_reverse_deactivation: bool,
|
|
|
|
) -> Result<(), StakeError> {
|
|
|
|
// If stake is currently active:
|
|
|
|
if stake.stake(clock.epoch, Some(stake_history), true) != 0 {
|
|
|
|
// If pubkey of new voter is the same as current,
|
|
|
|
// and we are scheduled to start deactivating this epoch,
|
|
|
|
// we rescind deactivation
|
|
|
|
if stake.delegation.voter_pubkey == *voter_pubkey
|
|
|
|
&& clock.epoch == stake.delegation.deactivation_epoch
|
|
|
|
&& can_reverse_deactivation
|
|
|
|
{
|
|
|
|
stake.delegation.deactivation_epoch = std::u64::MAX;
|
|
|
|
return Ok(());
|
2020-11-19 12:15:06 -08:00
|
|
|
} else {
|
2021-06-15 09:04:00 -07:00
|
|
|
// can't redelegate to another pubkey if stake is active.
|
|
|
|
return Err(StakeError::TooSoonToRedelegate);
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
// Either the stake is freshly activated, is active but has been
|
|
|
|
// deactivated this epoch, or has fully de-activated.
|
|
|
|
// Redelegation implies either re-activation or un-deactivation
|
|
|
|
|
|
|
|
stake.delegation.stake = stake_lamports;
|
|
|
|
stake.delegation.activation_epoch = clock.epoch;
|
|
|
|
stake.delegation.deactivation_epoch = std::u64::MAX;
|
|
|
|
stake.delegation.voter_pubkey = *voter_pubkey;
|
|
|
|
stake.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate;
|
|
|
|
stake.credits_observed = vote_state.credits();
|
|
|
|
Ok(())
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
fn new_stake(
|
|
|
|
stake: u64,
|
|
|
|
voter_pubkey: &Pubkey,
|
|
|
|
vote_state: &VoteState,
|
|
|
|
activation_epoch: Epoch,
|
|
|
|
config: &Config,
|
|
|
|
) -> Stake {
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation::new(
|
|
|
|
voter_pubkey,
|
2019-11-25 13:14:32 -08:00
|
|
|
stake,
|
|
|
|
activation_epoch,
|
2021-06-15 09:04:00 -07:00
|
|
|
config.warmup_cooldown_rate,
|
|
|
|
),
|
|
|
|
credits_observed: vote_state.credits(),
|
2019-11-25 13:14:32 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-20 21:57:25 -07:00
|
|
|
/// captures a rewards round as lamports to be awarded
|
|
|
|
/// and the total points over which those lamports
|
|
|
|
/// are to be distributed
|
|
|
|
// basically read as rewards/points, but in integers instead of as an f64
|
2020-11-07 23:43:50 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-07-20 21:57:25 -07:00
|
|
|
pub struct PointValue {
|
|
|
|
pub rewards: u64, // lamports to split
|
|
|
|
pub points: u128, // over these points
|
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
fn redeem_stake_rewards(
|
|
|
|
stake: &mut Stake,
|
|
|
|
point_value: &PointValue,
|
|
|
|
vote_state: &VoteState,
|
|
|
|
stake_history: Option<&StakeHistory>,
|
|
|
|
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
|
|
|
|
fix_stake_deactivate: bool,
|
|
|
|
) -> Option<(u64, u64)> {
|
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
|
|
|
inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
|
|
|
|
stake.credits_observed,
|
|
|
|
None,
|
|
|
|
));
|
2019-11-25 13:14:32 -08:00
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
stake,
|
|
|
|
point_value,
|
|
|
|
vote_state,
|
|
|
|
stake_history,
|
|
|
|
inflation_point_calc_tracer,
|
|
|
|
fix_stake_deactivate,
|
|
|
|
)
|
|
|
|
.map(|(stakers_reward, voters_reward, credits_observed)| {
|
2021-02-11 22:24:23 -08:00
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
|
|
|
inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
|
2021-06-15 09:04:00 -07:00
|
|
|
stake.credits_observed,
|
|
|
|
Some(credits_observed),
|
2021-02-11 22:24:23 -08:00
|
|
|
));
|
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
stake.credits_observed = credits_observed;
|
|
|
|
stake.delegation.stake += stakers_reward;
|
|
|
|
(stakers_reward, voters_reward)
|
|
|
|
})
|
|
|
|
}
|
2020-01-22 12:21:31 -08:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
fn calculate_stake_points(
|
|
|
|
stake: &Stake,
|
|
|
|
vote_state: &VoteState,
|
|
|
|
stake_history: Option<&StakeHistory>,
|
|
|
|
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
|
|
|
|
fix_stake_deactivate: bool,
|
|
|
|
) -> u128 {
|
|
|
|
calculate_stake_points_and_credits(
|
|
|
|
stake,
|
|
|
|
vote_state,
|
|
|
|
stake_history,
|
|
|
|
inflation_point_calc_tracer,
|
|
|
|
fix_stake_deactivate,
|
|
|
|
)
|
|
|
|
.0
|
|
|
|
}
|
2020-07-20 21:57:25 -07:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
/// for a given stake and vote_state, calculate how many
|
|
|
|
/// points were earned (credits * stake) and new value
|
|
|
|
/// for credits_observed were the points paid
|
|
|
|
fn calculate_stake_points_and_credits(
|
|
|
|
stake: &Stake,
|
|
|
|
new_vote_state: &VoteState,
|
|
|
|
stake_history: Option<&StakeHistory>,
|
|
|
|
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
|
|
|
|
fix_stake_deactivate: bool,
|
|
|
|
) -> (u128, u64) {
|
|
|
|
// if there is no newer credits since observed, return no point
|
|
|
|
if new_vote_state.credits() <= stake.credits_observed {
|
|
|
|
if fix_stake_deactivate {
|
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
|
|
|
inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into());
|
2020-11-30 05:47:34 -08:00
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
return (0, stake.credits_observed);
|
|
|
|
} else {
|
2020-11-07 23:43:50 -08:00
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
2021-06-15 09:04:00 -07:00
|
|
|
inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnZero.into());
|
2020-11-07 23:43:50 -08:00
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
return (0, 0);
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
2020-07-20 21:57:25 -07:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
let mut points = 0;
|
|
|
|
let mut new_credits_observed = stake.credits_observed;
|
|
|
|
|
|
|
|
for (epoch, final_epoch_credits, initial_epoch_credits) in
|
|
|
|
new_vote_state.epoch_credits().iter().copied()
|
|
|
|
{
|
|
|
|
let stake_amount = u128::from(stake.delegation.stake(
|
|
|
|
epoch,
|
2020-11-07 23:43:50 -08:00
|
|
|
stake_history,
|
2020-11-11 13:11:57 -08:00
|
|
|
fix_stake_deactivate,
|
2021-06-15 09:04:00 -07:00
|
|
|
));
|
2020-11-18 17:34:51 -08:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
// figure out how much this stake has seen that
|
|
|
|
// for which the vote account has a record
|
|
|
|
let earned_credits = if stake.credits_observed < initial_epoch_credits {
|
|
|
|
// the staker observed the entire epoch
|
|
|
|
final_epoch_credits - initial_epoch_credits
|
|
|
|
} else if stake.credits_observed < final_epoch_credits {
|
|
|
|
// the staker registered sometime during the epoch, partial credit
|
|
|
|
final_epoch_credits - new_credits_observed
|
|
|
|
} else {
|
|
|
|
// the staker has already observed or been redeemed this epoch
|
|
|
|
// or was activated after this epoch
|
|
|
|
0
|
|
|
|
};
|
|
|
|
let earned_credits = u128::from(earned_credits);
|
2019-04-07 21:45:28 -07:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
// don't want to assume anything about order of the iterator...
|
|
|
|
new_credits_observed = new_credits_observed.max(final_epoch_credits);
|
2019-04-07 21:45:28 -07:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
// finally calculate points for this epoch
|
|
|
|
let earned_points = stake_amount * earned_credits;
|
|
|
|
points += earned_points;
|
2020-07-20 21:57:25 -07:00
|
|
|
|
2020-11-07 23:43:50 -08:00
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
2021-06-15 09:04:00 -07:00
|
|
|
inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints(
|
|
|
|
epoch,
|
|
|
|
stake_amount,
|
|
|
|
earned_credits,
|
|
|
|
earned_points,
|
2020-11-07 23:43:50 -08:00
|
|
|
));
|
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
}
|
2019-04-07 21:45:28 -07:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
(points, new_credits_observed)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// for a given stake and vote_state, calculate what distributions and what updates should be made
|
|
|
|
/// returns a tuple in the case of a payout of:
|
|
|
|
/// * staker_rewards to be distributed
|
|
|
|
/// * voter_rewards to be distributed
|
|
|
|
/// * new value for credits_observed in the stake
|
|
|
|
/// returns None if there's no payout or if any deserved payout is < 1 lamport
|
|
|
|
pub fn calculate_stake_rewards(
|
|
|
|
stake: &Stake,
|
|
|
|
point_value: &PointValue,
|
|
|
|
vote_state: &VoteState,
|
|
|
|
stake_history: Option<&StakeHistory>,
|
|
|
|
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
|
|
|
|
fix_stake_deactivate: bool,
|
|
|
|
) -> Option<(u64, u64, u64)> {
|
|
|
|
let (points, credits_observed) = calculate_stake_points_and_credits(
|
|
|
|
stake,
|
|
|
|
vote_state,
|
|
|
|
stake_history,
|
|
|
|
inflation_point_calc_tracer,
|
|
|
|
fix_stake_deactivate,
|
|
|
|
);
|
2020-07-20 21:57:25 -07:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
// Drive credits_observed forward unconditionally when rewards are disabled
|
|
|
|
if point_value.rewards == 0 && fix_stake_deactivate {
|
|
|
|
return Some((0, 0, credits_observed));
|
2019-04-07 21:45:28 -07:00
|
|
|
}
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
if points == 0 {
|
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
|
|
|
inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into());
|
2019-09-11 09:48:29 -07:00
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
return None;
|
2019-09-11 09:48:29 -07:00
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
if point_value.points == 0 {
|
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
|
|
|
inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into());
|
2019-10-31 11:07:27 -07:00
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
return None;
|
2019-10-31 11:07:27 -07:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
let rewards = points
|
|
|
|
.checked_mul(u128::from(point_value.rewards))
|
|
|
|
.unwrap()
|
|
|
|
.checked_div(point_value.points)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let rewards = u64::try_from(rewards).unwrap();
|
|
|
|
|
|
|
|
// don't bother trying to split if fractional lamports got truncated
|
|
|
|
if rewards == 0 {
|
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
|
|
|
inflation_point_calc_tracer(&SkippedReason::ZeroReward.into());
|
2019-08-12 20:59:57 -07:00
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards);
|
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
|
|
|
inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards(
|
|
|
|
rewards,
|
|
|
|
voter_rewards,
|
|
|
|
staker_rewards,
|
|
|
|
(*point_value).clone(),
|
|
|
|
));
|
2019-06-17 19:34:21 -07:00
|
|
|
}
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
if (voter_rewards == 0 || staker_rewards == 0) && is_split {
|
|
|
|
// don't collect if we lose a whole lamport somewhere
|
|
|
|
// is_split means there should be tokens on both sides,
|
|
|
|
// uncool to move credits_observed if one side didn't get paid
|
|
|
|
return None;
|
2019-06-17 19:34:21 -07:00
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
|
|
|
|
Some((staker_rewards, voter_rewards, credits_observed))
|
2019-06-17 19:34:21 -07:00
|
|
|
}
|
|
|
|
|
2019-04-01 16:45:53 -07:00
|
|
|
pub trait StakeAccount {
|
2019-09-26 13:29:29 -07:00
|
|
|
fn initialize(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-09-26 13:29:29 -07:00
|
|
|
authorized: &Authorized,
|
|
|
|
lockup: &Lockup,
|
2019-10-31 11:07:27 -07:00
|
|
|
rent: &Rent,
|
2019-09-26 13:29:29 -07:00
|
|
|
) -> Result<(), InstructionError>;
|
2019-09-12 19:03:28 -07:00
|
|
|
fn authorize(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-10-14 15:02:24 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
2020-08-21 11:28:01 -07:00
|
|
|
new_authority: &Pubkey,
|
|
|
|
stake_authorize: StakeAuthorize,
|
2021-02-05 22:40:07 -08:00
|
|
|
require_custodian_for_locked_stake_authorize: bool,
|
|
|
|
clock: &Clock,
|
|
|
|
custodian: Option<&Pubkey>,
|
2020-08-21 11:28:01 -07:00
|
|
|
) -> Result<(), InstructionError>;
|
|
|
|
fn authorize_with_seed(
|
|
|
|
&self,
|
|
|
|
authority_base: &KeyedAccount,
|
|
|
|
authority_seed: &str,
|
|
|
|
authority_owner: &Pubkey,
|
|
|
|
new_authority: &Pubkey,
|
|
|
|
stake_authorize: StakeAuthorize,
|
2021-02-05 22:40:07 -08:00
|
|
|
require_custodian_for_locked_stake_authorize: bool,
|
|
|
|
clock: &Clock,
|
|
|
|
custodian: Option<&Pubkey>,
|
2019-09-12 19:03:28 -07:00
|
|
|
) -> Result<(), InstructionError>;
|
2020-01-29 17:59:14 -08:00
|
|
|
fn delegate(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-06-10 12:17:29 -07:00
|
|
|
vote_account: &KeyedAccount,
|
2020-01-29 17:59:14 -08:00
|
|
|
clock: &Clock,
|
|
|
|
stake_history: &StakeHistory,
|
2019-08-15 14:35:48 -07:00
|
|
|
config: &Config,
|
2019-10-14 15:02:24 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
2021-05-20 22:31:55 -07:00
|
|
|
can_reverse_deactivation: bool,
|
2019-06-10 12:17:29 -07:00
|
|
|
) -> Result<(), InstructionError>;
|
2020-01-29 17:59:14 -08:00
|
|
|
fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError>;
|
2020-01-28 20:59:53 -08:00
|
|
|
fn set_lockup(
|
|
|
|
&self,
|
2020-03-02 12:28:43 -08:00
|
|
|
lockup: &LockupArgs,
|
2020-01-28 20:59:53 -08:00
|
|
|
signers: &HashSet<Pubkey>,
|
2021-05-20 14:04:07 -07:00
|
|
|
clock: Option<&Clock>,
|
2020-01-28 20:59:53 -08:00
|
|
|
) -> Result<(), InstructionError>;
|
2019-10-31 11:07:27 -07:00
|
|
|
fn split(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-10-31 11:07:27 -07:00
|
|
|
lamports: u64,
|
2020-01-22 17:54:06 -08:00
|
|
|
split_stake: &KeyedAccount,
|
2019-10-31 11:07:27 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
|
|
|
) -> Result<(), InstructionError>;
|
2020-06-10 17:22:47 -07:00
|
|
|
fn merge(
|
|
|
|
&self,
|
2021-01-21 09:59:24 -08:00
|
|
|
invoke_context: &dyn InvokeContext,
|
2020-06-10 17:22:47 -07:00
|
|
|
source_stake: &KeyedAccount,
|
|
|
|
clock: &Clock,
|
|
|
|
stake_history: &StakeHistory,
|
|
|
|
signers: &HashSet<Pubkey>,
|
2021-05-20 19:23:36 -07:00
|
|
|
can_merge_expired_lockups: bool,
|
2020-06-10 17:22:47 -07:00
|
|
|
) -> Result<(), InstructionError>;
|
2019-06-21 22:28:34 -07:00
|
|
|
fn withdraw(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-06-21 22:28:34 -07:00
|
|
|
lamports: u64,
|
2020-01-22 17:54:06 -08:00
|
|
|
to: &KeyedAccount,
|
2020-01-29 17:59:14 -08:00
|
|
|
clock: &Clock,
|
|
|
|
stake_history: &StakeHistory,
|
2020-07-31 12:37:53 -07:00
|
|
|
withdraw_authority: &KeyedAccount,
|
|
|
|
custodian: Option<&KeyedAccount>,
|
2021-05-20 19:01:08 -07:00
|
|
|
prevent_withdraw_to_zero: bool,
|
2019-06-21 22:28:34 -07:00
|
|
|
) -> Result<(), InstructionError>;
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> StakeAccount for KeyedAccount<'a> {
|
2019-09-26 13:29:29 -07:00
|
|
|
fn initialize(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-09-26 13:29:29 -07:00
|
|
|
authorized: &Authorized,
|
|
|
|
lockup: &Lockup,
|
2019-10-31 11:07:27 -07:00
|
|
|
rent: &Rent,
|
2019-09-26 13:29:29 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2020-11-23 12:13:38 -08:00
|
|
|
if self.data_len()? != std::mem::size_of::<StakeState>() {
|
|
|
|
return Err(InstructionError::InvalidAccountData);
|
|
|
|
}
|
2019-09-04 13:34:09 -07:00
|
|
|
if let StakeState::Uninitialized = self.state()? {
|
2020-01-22 09:11:56 -08:00
|
|
|
let rent_exempt_reserve = rent.minimum_balance(self.data_len()?);
|
2019-10-31 11:07:27 -07:00
|
|
|
|
2020-01-22 09:11:56 -08:00
|
|
|
if rent_exempt_reserve < self.lamports()? {
|
2019-10-31 11:07:27 -07:00
|
|
|
self.set_state(&StakeState::Initialized(Meta {
|
|
|
|
rent_exempt_reserve,
|
|
|
|
authorized: *authorized,
|
|
|
|
lockup: *lockup,
|
|
|
|
}))
|
|
|
|
} else {
|
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
}
|
2019-09-12 19:03:28 -07:00
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
2019-10-31 11:07:27 -07:00
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
/// 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(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-10-14 15:02:24 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
2020-08-21 11:28:01 -07:00
|
|
|
new_authority: &Pubkey,
|
|
|
|
stake_authorize: StakeAuthorize,
|
2021-02-05 22:40:07 -08:00
|
|
|
require_custodian_for_locked_stake_authorize: bool,
|
|
|
|
clock: &Clock,
|
|
|
|
custodian: Option<&Pubkey>,
|
2019-09-12 19:03:28 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2019-10-31 11:07:27 -07:00
|
|
|
match self.state()? {
|
|
|
|
StakeState::Stake(mut meta, stake) => {
|
2021-02-05 22:40:07 -08:00
|
|
|
meta.authorized.authorize(
|
|
|
|
signers,
|
|
|
|
new_authority,
|
|
|
|
stake_authorize,
|
|
|
|
if require_custodian_for_locked_stake_authorize {
|
|
|
|
Some((&meta.lockup, clock, custodian))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
|
|
|
)?;
|
2019-10-31 11:07:27 -07:00
|
|
|
self.set_state(&StakeState::Stake(meta, stake))
|
|
|
|
}
|
|
|
|
StakeState::Initialized(mut meta) => {
|
2021-02-05 22:40:07 -08:00
|
|
|
meta.authorized.authorize(
|
|
|
|
signers,
|
|
|
|
new_authority,
|
|
|
|
stake_authorize,
|
|
|
|
if require_custodian_for_locked_stake_authorize {
|
|
|
|
Some((&meta.lockup, clock, custodian))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
|
|
|
)?;
|
2019-10-31 11:07:27 -07:00
|
|
|
self.set_state(&StakeState::Initialized(meta))
|
|
|
|
}
|
|
|
|
_ => Err(InstructionError::InvalidAccountData),
|
2019-09-04 13:34:09 -07:00
|
|
|
}
|
|
|
|
}
|
2020-08-21 11:28:01 -07:00
|
|
|
fn authorize_with_seed(
|
|
|
|
&self,
|
|
|
|
authority_base: &KeyedAccount,
|
|
|
|
authority_seed: &str,
|
|
|
|
authority_owner: &Pubkey,
|
|
|
|
new_authority: &Pubkey,
|
|
|
|
stake_authorize: StakeAuthorize,
|
2021-02-05 22:40:07 -08:00
|
|
|
require_custodian_for_locked_stake_authorize: bool,
|
|
|
|
clock: &Clock,
|
|
|
|
custodian: Option<&Pubkey>,
|
2020-08-21 11:28:01 -07:00
|
|
|
) -> 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,
|
|
|
|
)?);
|
|
|
|
}
|
2021-02-05 22:40:07 -08:00
|
|
|
self.authorize(
|
|
|
|
&signers,
|
|
|
|
&new_authority,
|
|
|
|
stake_authorize,
|
|
|
|
require_custodian_for_locked_stake_authorize,
|
|
|
|
clock,
|
|
|
|
custodian,
|
|
|
|
)
|
2020-08-21 11:28:01 -07:00
|
|
|
}
|
2020-01-29 17:59:14 -08:00
|
|
|
fn delegate(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-06-10 12:17:29 -07:00
|
|
|
vote_account: &KeyedAccount,
|
2020-01-29 17:59:14 -08:00
|
|
|
clock: &Clock,
|
|
|
|
stake_history: &StakeHistory,
|
2019-08-15 14:35:48 -07:00
|
|
|
config: &Config,
|
2019-10-14 15:02:24 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
2021-05-20 22:31:55 -07:00
|
|
|
can_reverse_deactivation: bool,
|
2019-06-10 12:17:29 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2020-11-16 12:42:59 -08:00
|
|
|
if vote_account.owner()? != solana_vote_program::id() {
|
|
|
|
return Err(InstructionError::IncorrectProgramId);
|
|
|
|
}
|
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
match self.state()? {
|
|
|
|
StakeState::Initialized(meta) => {
|
|
|
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
2021-06-15 09:04:00 -07:00
|
|
|
let stake = new_stake(
|
2020-01-22 09:11:56 -08:00
|
|
|
self.lamports()?.saturating_sub(meta.rent_exempt_reserve), // can't stake the rent ;)
|
2019-10-31 11:07:27 -07:00
|
|
|
vote_account.unsigned_key(),
|
2020-02-25 17:12:01 -08:00
|
|
|
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(),
|
2019-10-31 11:07:27 -07:00
|
|
|
clock.epoch,
|
|
|
|
config,
|
|
|
|
);
|
|
|
|
self.set_state(&StakeState::Stake(meta, stake))
|
|
|
|
}
|
|
|
|
StakeState::Stake(meta, mut stake) => {
|
|
|
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
2021-06-15 09:04:00 -07:00
|
|
|
redelegate(
|
|
|
|
&mut stake,
|
2020-11-02 19:54:24 -08:00
|
|
|
self.lamports()?.saturating_sub(meta.rent_exempt_reserve), // can't stake the rent ;)
|
2020-01-29 17:59:14 -08:00
|
|
|
vote_account.unsigned_key(),
|
2020-02-25 17:12:01 -08:00
|
|
|
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(),
|
2020-01-29 17:59:14 -08:00
|
|
|
clock,
|
|
|
|
stake_history,
|
|
|
|
config,
|
2021-05-20 22:31:55 -07:00
|
|
|
can_reverse_deactivation,
|
2020-01-29 17:59:14 -08:00
|
|
|
)?;
|
2019-10-31 11:07:27 -07:00
|
|
|
self.set_state(&StakeState::Stake(meta, stake))
|
|
|
|
}
|
|
|
|
_ => Err(InstructionError::InvalidAccountData),
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|
|
|
|
}
|
2020-01-29 17:59:14 -08:00
|
|
|
fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError> {
|
2019-10-31 11:07:27 -07:00
|
|
|
if let StakeState::Stake(meta, mut stake) = self.state()? {
|
|
|
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
2019-10-15 12:50:31 -07:00
|
|
|
stake.deactivate(clock.epoch)?;
|
2019-06-21 20:43:24 -07:00
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
self.set_state(&StakeState::Stake(meta, stake))
|
2019-06-21 20:43:24 -07:00
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
2020-01-28 20:59:53 -08:00
|
|
|
fn set_lockup(
|
|
|
|
&self,
|
2020-03-02 12:28:43 -08:00
|
|
|
lockup: &LockupArgs,
|
2020-01-28 20:59:53 -08:00
|
|
|
signers: &HashSet<Pubkey>,
|
2021-05-20 14:04:07 -07:00
|
|
|
clock: Option<&Clock>,
|
2020-01-28 20:59:53 -08:00
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
match self.state()? {
|
|
|
|
StakeState::Initialized(mut meta) => {
|
2021-05-20 14:04:07 -07:00
|
|
|
meta.set_lockup(lockup, signers, clock)?;
|
2020-01-28 20:59:53 -08:00
|
|
|
self.set_state(&StakeState::Initialized(meta))
|
|
|
|
}
|
|
|
|
StakeState::Stake(mut meta, stake) => {
|
2021-05-20 14:04:07 -07:00
|
|
|
meta.set_lockup(lockup, signers, clock)?;
|
2020-01-28 20:59:53 -08:00
|
|
|
self.set_state(&StakeState::Stake(meta, stake))
|
|
|
|
}
|
|
|
|
_ => Err(InstructionError::InvalidAccountData),
|
|
|
|
}
|
|
|
|
}
|
2019-10-31 11:07:27 -07:00
|
|
|
|
|
|
|
fn split(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-10-31 11:07:27 -07:00
|
|
|
lamports: u64,
|
2020-01-22 17:54:06 -08:00
|
|
|
split: &KeyedAccount,
|
2019-10-31 11:07:27 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
|
|
|
) -> Result<(), InstructionError> {
|
2020-11-16 12:42:59 -08:00
|
|
|
if split.owner()? != id() {
|
|
|
|
return Err(InstructionError::IncorrectProgramId);
|
|
|
|
}
|
2021-02-16 19:42:46 -08:00
|
|
|
if split.data_len()? != std::mem::size_of::<StakeState>() {
|
|
|
|
return Err(InstructionError::InvalidAccountData);
|
|
|
|
}
|
2020-11-16 12:42:59 -08:00
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
if let StakeState::Uninitialized = split.state()? {
|
|
|
|
// verify enough account lamports
|
2020-01-22 09:11:56 -08:00
|
|
|
if lamports > self.lamports()? {
|
2019-10-31 11:07:27 -07:00
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.state()? {
|
|
|
|
StakeState::Stake(meta, mut stake) => {
|
|
|
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
2020-11-06 12:32:05 -08:00
|
|
|
let split_rent_exempt_reserve = calculate_split_rent_exempt_reserve(
|
|
|
|
meta.rent_exempt_reserve,
|
|
|
|
self.data_len()? as u64,
|
|
|
|
split.data_len()? as u64,
|
|
|
|
);
|
2019-10-31 11:07:27 -07:00
|
|
|
|
2021-02-16 19:42:46 -08:00
|
|
|
// verify enough lamports for rent and more than 0 stake in new split account
|
|
|
|
if lamports <= split_rent_exempt_reserve.saturating_sub(split.lamports()?)
|
2020-11-23 18:48:56 -08:00
|
|
|
// if not full withdrawal
|
|
|
|
|| (lamports != self.lamports()?
|
|
|
|
// verify more than 0 stake left in previous stake
|
2021-02-16 19:42:46 -08:00
|
|
|
&& checked_add(lamports, meta.rent_exempt_reserve)? >= self.lamports()?)
|
2019-10-31 11:07:27 -07:00
|
|
|
{
|
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
// split the stake, subtract rent_exempt_balance unless
|
2020-11-06 12:32:05 -08:00
|
|
|
// 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 lamports
|
|
|
|
== self.lamports()?
|
|
|
|
{
|
|
|
|
// If split amount equals the full source stake, 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.
|
2020-11-23 09:11:10 -08:00
|
|
|
// 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);
|
2021-02-16 19:42:46 -08:00
|
|
|
(remaining_stake_delta, remaining_stake_delta)
|
2020-11-06 12:32:05 -08:00
|
|
|
} else {
|
|
|
|
// Otherwise, the new split stake should reflect the entire split
|
2020-11-23 09:11:10 -08:00
|
|
|
// requested, less any lamports needed to cover the split_rent_exempt_reserve
|
2020-11-06 12:32:05 -08:00
|
|
|
(
|
|
|
|
lamports,
|
|
|
|
lamports - split_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 = split_rent_exempt_reserve;
|
2019-10-31 11:07:27 -07:00
|
|
|
|
|
|
|
self.set_state(&StakeState::Stake(meta, stake))?;
|
2020-11-06 12:32:05 -08:00
|
|
|
split.set_state(&StakeState::Stake(split_meta, split_stake))?;
|
2019-10-31 11:07:27 -07:00
|
|
|
}
|
|
|
|
StakeState::Initialized(meta) => {
|
|
|
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
2020-11-06 12:32:05 -08:00
|
|
|
let split_rent_exempt_reserve = calculate_split_rent_exempt_reserve(
|
|
|
|
meta.rent_exempt_reserve,
|
|
|
|
self.data_len()? as u64,
|
|
|
|
split.data_len()? as u64,
|
|
|
|
);
|
2019-10-31 11:07:27 -07:00
|
|
|
|
2021-02-16 19:42:46 -08:00
|
|
|
// enough lamports for rent and more than 0 stake in new split account
|
|
|
|
if lamports <= split_rent_exempt_reserve.saturating_sub(split.lamports()?)
|
|
|
|
// if not full withdrawal
|
|
|
|
|| (lamports != self.lamports()?
|
|
|
|
// verify more than 0 stake left in previous stake
|
|
|
|
&& checked_add(lamports, meta.rent_exempt_reserve)? >= self.lamports()?)
|
2019-10-31 11:07:27 -07:00
|
|
|
{
|
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
|
2020-11-17 11:03:00 -08:00
|
|
|
let mut split_meta = meta;
|
|
|
|
split_meta.rent_exempt_reserve = split_rent_exempt_reserve;
|
|
|
|
split.set_state(&StakeState::Initialized(split_meta))?;
|
2019-10-31 11:07:27 -07:00
|
|
|
}
|
|
|
|
StakeState::Uninitialized => {
|
|
|
|
if !signers.contains(&self.unsigned_key()) {
|
|
|
|
return Err(InstructionError::MissingRequiredSignature);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => return Err(InstructionError::InvalidAccountData),
|
|
|
|
}
|
|
|
|
|
2020-12-21 18:54:49 -08:00
|
|
|
// Deinitialize state upon zero balance
|
|
|
|
if lamports == self.lamports()? {
|
|
|
|
self.set_state(&StakeState::Uninitialized)?;
|
|
|
|
}
|
|
|
|
|
2021-04-27 07:11:35 -07:00
|
|
|
split
|
|
|
|
.try_account_ref_mut()?
|
|
|
|
.checked_add_lamports(lamports)?;
|
2021-04-29 10:38:21 -07:00
|
|
|
self.try_account_ref_mut()?.checked_sub_lamports(lamports)?;
|
2019-10-31 11:07:27 -07:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 17:22:47 -07:00
|
|
|
fn merge(
|
|
|
|
&self,
|
2021-01-21 09:59:24 -08:00
|
|
|
invoke_context: &dyn InvokeContext,
|
2020-11-19 12:34:45 -08:00
|
|
|
source_account: &KeyedAccount,
|
2020-06-10 17:22:47 -07:00
|
|
|
clock: &Clock,
|
|
|
|
stake_history: &StakeHistory,
|
|
|
|
signers: &HashSet<Pubkey>,
|
2021-05-20 19:23:36 -07:00
|
|
|
can_merge_expired_lockups: bool,
|
2020-06-10 17:22:47 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2020-11-22 19:11:00 -08:00
|
|
|
// Ensure source isn't spoofed
|
2020-11-19 12:34:45 -08:00
|
|
|
if source_account.owner()? != id() {
|
2020-11-16 12:42:59 -08:00
|
|
|
return Err(InstructionError::IncorrectProgramId);
|
|
|
|
}
|
2020-11-22 19:11:00 -08:00
|
|
|
// Close the self-reference loophole
|
|
|
|
if source_account.unsigned_key() == self.unsigned_key() {
|
|
|
|
return Err(InstructionError::InvalidArgument);
|
|
|
|
}
|
2020-11-16 12:42:59 -08:00
|
|
|
|
2021-01-21 09:59:24 -08:00
|
|
|
ic_msg!(invoke_context, "Checking if destination stake is mergeable");
|
|
|
|
let stake_merge_kind =
|
|
|
|
MergeKind::get_if_mergeable(invoke_context, self, clock, stake_history)?;
|
2020-11-19 13:44:16 -08:00
|
|
|
let meta = stake_merge_kind.meta();
|
2020-11-19 11:53:05 -08:00
|
|
|
|
2020-06-10 17:22:47 -07:00
|
|
|
// Authorized staker is allowed to split/merge accounts
|
|
|
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
|
|
|
|
2021-01-21 09:59:24 -08:00
|
|
|
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)?;
|
2020-06-10 17:22:47 -07:00
|
|
|
|
2021-05-20 19:23:36 -07:00
|
|
|
let clock = if can_merge_expired_lockups {
|
|
|
|
Some(clock)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2021-01-21 09:59:24 -08:00
|
|
|
ic_msg!(invoke_context, "Merging stake accounts");
|
2021-05-20 19:23:36 -07:00
|
|
|
if let Some(merged_state) =
|
|
|
|
stake_merge_kind.merge(invoke_context, source_merge_kind, clock)?
|
|
|
|
{
|
2020-11-19 13:44:16 -08:00
|
|
|
self.set_state(&merged_state)?;
|
2020-06-10 17:22:47 -07:00
|
|
|
}
|
|
|
|
|
2020-12-21 18:54:49 -08:00
|
|
|
// Source is about to be drained, deinitialize its state
|
|
|
|
source_account.set_state(&StakeState::Uninitialized)?;
|
|
|
|
|
2020-06-10 17:22:47 -07:00
|
|
|
// Drain the source stake account
|
2020-11-19 12:34:45 -08:00
|
|
|
let lamports = source_account.lamports()?;
|
2021-04-29 10:38:21 -07:00
|
|
|
source_account
|
|
|
|
.try_account_ref_mut()?
|
|
|
|
.checked_sub_lamports(lamports)?;
|
2021-04-27 07:11:35 -07:00
|
|
|
self.try_account_ref_mut()?.checked_add_lamports(lamports)?;
|
2020-06-10 17:22:47 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-06-21 22:28:34 -07:00
|
|
|
fn withdraw(
|
2020-01-22 17:54:06 -08:00
|
|
|
&self,
|
2019-06-21 22:28:34 -07:00
|
|
|
lamports: u64,
|
2020-01-22 17:54:06 -08:00
|
|
|
to: &KeyedAccount,
|
2020-01-29 17:59:14 -08:00
|
|
|
clock: &Clock,
|
|
|
|
stake_history: &StakeHistory,
|
2020-07-31 12:37:53 -07:00
|
|
|
withdraw_authority: &KeyedAccount,
|
|
|
|
custodian: Option<&KeyedAccount>,
|
2021-05-20 19:01:08 -07:00
|
|
|
prevent_withdraw_to_zero: bool,
|
2019-06-21 22:28:34 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2020-07-31 12:37:53 -07:00
|
|
|
let mut signers = HashSet::new();
|
|
|
|
let withdraw_authority_pubkey = withdraw_authority
|
|
|
|
.signer_key()
|
|
|
|
.ok_or(InstructionError::MissingRequiredSignature)?;
|
|
|
|
signers.insert(*withdraw_authority_pubkey);
|
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
let (lockup, reserve, is_staked) = match self.state()? {
|
|
|
|
StakeState::Stake(meta, stake) => {
|
2020-07-31 12:37:53 -07:00
|
|
|
meta.authorized
|
|
|
|
.check(&signers, StakeAuthorize::Withdrawer)?;
|
2019-08-17 18:12:30 -07:00
|
|
|
// if we have a deactivation epoch and we're in cooldown
|
2019-11-25 13:14:32 -08:00
|
|
|
let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
|
2020-11-11 13:11:57 -08:00
|
|
|
stake
|
|
|
|
.delegation
|
|
|
|
.stake(clock.epoch, Some(stake_history), true)
|
2019-06-21 22:28:34 -07:00
|
|
|
} else {
|
2019-08-17 18:12:30 -07:00
|
|
|
// Assume full stake if the stake account hasn't been
|
2019-09-16 17:47:42 -07:00
|
|
|
// de-activated, because in the future the exposed stake
|
|
|
|
// might be higher than stake.stake() due to warmup
|
2019-11-25 13:14:32 -08:00
|
|
|
stake.delegation.stake
|
2019-06-21 22:28:34 -07:00
|
|
|
};
|
2019-08-17 18:12:30 -07:00
|
|
|
|
2021-02-12 17:24:43 -08:00
|
|
|
let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
|
|
|
|
(meta.lockup, staked_and_reserve, staked != 0)
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
2019-10-31 11:07:27 -07:00
|
|
|
StakeState::Initialized(meta) => {
|
2020-07-31 12:37:53 -07:00
|
|
|
meta.authorized
|
|
|
|
.check(&signers, StakeAuthorize::Withdrawer)?;
|
2021-05-20 19:01:08 -07:00
|
|
|
let reserve = if prevent_withdraw_to_zero {
|
|
|
|
checked_add(meta.rent_exempt_reserve, 1)? // stake accounts must have a balance > rent_exempt_reserve
|
|
|
|
} else {
|
|
|
|
meta.rent_exempt_reserve
|
|
|
|
};
|
2019-10-31 11:07:27 -07:00
|
|
|
|
2021-05-20 19:01:08 -07:00
|
|
|
(meta.lockup, reserve, false)
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
2019-09-12 19:03:28 -07:00
|
|
|
StakeState::Uninitialized => {
|
2019-10-31 11:07:27 -07:00
|
|
|
if !signers.contains(&self.unsigned_key()) {
|
2019-09-12 19:03:28 -07:00
|
|
|
return Err(InstructionError::MissingRequiredSignature);
|
|
|
|
}
|
2019-10-31 11:07:27 -07:00
|
|
|
(Lockup::default(), 0, false) // no lockup, no restrictions
|
2019-09-12 19:03:28 -07:00
|
|
|
}
|
2019-09-04 13:34:09 -07:00
|
|
|
_ => return Err(InstructionError::InvalidAccountData),
|
2019-09-16 17:47:42 -07:00
|
|
|
};
|
|
|
|
|
2019-12-04 21:25:01 -08:00
|
|
|
// verify that lockup has expired or that the withdrawal is signed by
|
2019-12-19 14:37:47 -08:00
|
|
|
// the custodian, both epoch and unix_timestamp must have passed
|
2020-07-31 12:37:53 -07:00
|
|
|
let custodian_pubkey = custodian.and_then(|keyed_account| keyed_account.signer_key());
|
|
|
|
if lockup.is_in_force(&clock, custodian_pubkey) {
|
2019-10-02 18:33:01 -07:00
|
|
|
return Err(StakeError::LockupInForce.into());
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
2019-10-31 11:07:27 -07:00
|
|
|
|
2021-02-12 17:24:43 -08:00
|
|
|
let lamports_and_reserve = checked_add(lamports, reserve)?;
|
2019-10-31 11:07:27 -07:00
|
|
|
// if the stake is active, we mustn't allow the account to go away
|
|
|
|
if is_staked // line coverage for branch coverage
|
2021-02-12 17:24:43 -08:00
|
|
|
&& lamports_and_reserve > self.lamports()?
|
2019-10-31 11:07:27 -07:00
|
|
|
{
|
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
|
2020-01-22 09:11:56 -08:00
|
|
|
if lamports != self.lamports()? // not a full withdrawal
|
2021-02-12 17:24:43 -08:00
|
|
|
&& lamports_and_reserve > self.lamports()?
|
2019-10-31 11:07:27 -07:00
|
|
|
{
|
|
|
|
assert!(!is_staked);
|
2019-09-16 17:47:42 -07:00
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
|
2020-12-21 18:54:49 -08:00
|
|
|
// Deinitialize state upon zero balance
|
|
|
|
if lamports == self.lamports()? {
|
|
|
|
self.set_state(&StakeState::Uninitialized)?;
|
|
|
|
}
|
|
|
|
|
2021-04-29 10:38:21 -07:00
|
|
|
self.try_account_ref_mut()?.checked_sub_lamports(lamports)?;
|
2021-04-27 07:11:35 -07:00
|
|
|
to.try_account_ref_mut()?.checked_add_lamports(lamports)?;
|
2019-09-16 17:47:42 -07:00
|
|
|
Ok(())
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|
|
|
|
|
2020-11-19 13:44:16 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
enum MergeKind {
|
|
|
|
Inactive(Meta, u64),
|
|
|
|
ActivationEpoch(Meta, Stake),
|
|
|
|
FullyActive(Meta, Stake),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MergeKind {
|
|
|
|
fn meta(&self) -> &Meta {
|
|
|
|
match self {
|
|
|
|
Self::Inactive(meta, _) => meta,
|
|
|
|
Self::ActivationEpoch(meta, _) => meta,
|
|
|
|
Self::FullyActive(meta, _) => meta,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn active_stake(&self) -> Option<&Stake> {
|
|
|
|
match self {
|
|
|
|
Self::Inactive(_, _) => None,
|
|
|
|
Self::ActivationEpoch(_, stake) => Some(stake),
|
|
|
|
Self::FullyActive(_, stake) => Some(stake),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_if_mergeable(
|
2021-01-21 09:59:24 -08:00
|
|
|
invoke_context: &dyn InvokeContext,
|
2020-11-19 13:44:16 -08:00
|
|
|
stake_keyed_account: &KeyedAccount,
|
|
|
|
clock: &Clock,
|
|
|
|
stake_history: &StakeHistory,
|
|
|
|
) -> Result<Self, InstructionError> {
|
|
|
|
match stake_keyed_account.state()? {
|
|
|
|
StakeState::Stake(meta, stake) => {
|
|
|
|
// stake must not be in a transient state. Transient here meaning
|
|
|
|
// activating or deactivating with non-zero effective stake.
|
|
|
|
match stake.delegation.stake_activating_and_deactivating(
|
|
|
|
clock.epoch,
|
|
|
|
Some(stake_history),
|
|
|
|
true,
|
|
|
|
) {
|
|
|
|
/*
|
|
|
|
(e, a, d): e - effective, a - activating, d - deactivating */
|
|
|
|
(0, 0, 0) => Ok(Self::Inactive(meta, stake_keyed_account.lamports()?)),
|
|
|
|
(0, _, _) => Ok(Self::ActivationEpoch(meta, stake)),
|
|
|
|
(_, 0, 0) => Ok(Self::FullyActive(meta, stake)),
|
2021-01-21 09:59:24 -08:00
|
|
|
_ => {
|
|
|
|
let err = StakeError::MergeTransientStake;
|
|
|
|
ic_msg!(invoke_context, "{}", err);
|
|
|
|
Err(err.into())
|
|
|
|
}
|
2020-11-19 13:44:16 -08:00
|
|
|
}
|
2020-11-19 11:53:05 -08:00
|
|
|
}
|
2020-11-19 13:44:16 -08:00
|
|
|
StakeState::Initialized(meta) => {
|
|
|
|
Ok(Self::Inactive(meta, stake_keyed_account.lamports()?))
|
|
|
|
}
|
|
|
|
_ => Err(InstructionError::InvalidAccountData),
|
2020-11-19 11:53:05 -08:00
|
|
|
}
|
2020-11-19 13:44:16 -08:00
|
|
|
}
|
|
|
|
|
2021-01-21 09:59:24 -08:00
|
|
|
fn metas_can_merge(
|
|
|
|
invoke_context: &dyn InvokeContext,
|
|
|
|
stake: &Meta,
|
|
|
|
source: &Meta,
|
2021-05-20 22:58:16 -07:00
|
|
|
clock: Option<&Clock>,
|
2021-01-21 09:59:24 -08:00
|
|
|
) -> Result<(), InstructionError> {
|
2021-05-20 22:58:16 -07:00
|
|
|
let can_merge_lockups = match clock {
|
|
|
|
// pre-v4 behavior. lockups must match, even when expired
|
|
|
|
None => stake.lockup == source.lockup,
|
|
|
|
// v4 behavior. lockups may mismatch so long as both have expired
|
|
|
|
Some(clock) => {
|
|
|
|
stake.lockup == source.lockup
|
|
|
|
|| (!stake.lockup.is_in_force(clock, None)
|
|
|
|
&& !source.lockup.is_in_force(clock, None))
|
|
|
|
}
|
|
|
|
};
|
2020-11-19 13:44:16 -08:00
|
|
|
// `rent_exempt_reserve` has no bearing on the mergeability of accounts,
|
|
|
|
// as the source account will be culled by runtime once the operation
|
|
|
|
// succeeds. Considering it here would needlessly prevent merging stake
|
|
|
|
// accounts with differing data lengths, which already exist in the wild
|
|
|
|
// due to an SDK bug
|
2021-05-20 22:58:16 -07:00
|
|
|
if stake.authorized == source.authorized && can_merge_lockups {
|
2020-11-19 13:44:16 -08:00
|
|
|
Ok(())
|
|
|
|
} else {
|
2021-01-21 09:59:24 -08:00
|
|
|
ic_msg!(invoke_context, "Unable to merge due to metadata mismatch");
|
2020-11-19 13:44:16 -08:00
|
|
|
Err(StakeError::MergeMismatch.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn active_delegations_can_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
invoke_context: &dyn InvokeContext,
|
2020-11-19 13:44:16 -08:00
|
|
|
stake: &Delegation,
|
|
|
|
source: &Delegation,
|
|
|
|
) -> Result<(), InstructionError> {
|
2021-01-21 09:59:24 -08:00
|
|
|
if stake.voter_pubkey != source.voter_pubkey {
|
|
|
|
ic_msg!(invoke_context, "Unable to merge due to voter mismatch");
|
|
|
|
Err(StakeError::MergeMismatch.into())
|
|
|
|
} else if (stake.warmup_cooldown_rate - source.warmup_cooldown_rate).abs() < f64::EPSILON
|
2020-11-19 13:44:16 -08:00
|
|
|
&& stake.deactivation_epoch == Epoch::MAX
|
|
|
|
&& source.deactivation_epoch == Epoch::MAX
|
|
|
|
{
|
|
|
|
Ok(())
|
|
|
|
} else {
|
2021-01-21 09:59:24 -08:00
|
|
|
ic_msg!(invoke_context, "Unable to merge due to stake deactivation");
|
2020-11-19 13:44:16 -08:00
|
|
|
Err(StakeError::MergeMismatch.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-21 09:59:24 -08:00
|
|
|
fn active_stakes_can_merge(
|
|
|
|
invoke_context: &dyn InvokeContext,
|
|
|
|
stake: &Stake,
|
|
|
|
source: &Stake,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
Self::active_delegations_can_merge(invoke_context, &stake.delegation, &source.delegation)?;
|
2020-11-19 13:44:16 -08:00
|
|
|
// `credits_observed` MUST match to prevent earning multiple rewards
|
|
|
|
// from a stake account by merging it into another stake account that
|
|
|
|
// is small enough to not be paid out every epoch. This would effectively
|
|
|
|
// reset the larger stake accounts `credits_observed` to that of the
|
|
|
|
// smaller account.
|
|
|
|
if stake.credits_observed == source.credits_observed {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
2021-01-21 09:59:24 -08:00
|
|
|
ic_msg!(
|
|
|
|
invoke_context,
|
|
|
|
"Unable to merge due to credits observed mismatch"
|
|
|
|
);
|
2020-11-19 13:44:16 -08:00
|
|
|
Err(StakeError::MergeMismatch.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-21 09:59:24 -08:00
|
|
|
fn merge(
|
|
|
|
self,
|
|
|
|
invoke_context: &dyn InvokeContext,
|
|
|
|
source: Self,
|
2021-05-20 19:23:36 -07:00
|
|
|
clock: Option<&Clock>,
|
2021-01-21 09:59:24 -08:00
|
|
|
) -> Result<Option<StakeState>, InstructionError> {
|
2021-05-20 19:23:36 -07:00
|
|
|
Self::metas_can_merge(invoke_context, self.meta(), source.meta(), clock)?;
|
2020-11-19 13:44:16 -08:00
|
|
|
self.active_stake()
|
|
|
|
.zip(source.active_stake())
|
2021-01-21 09:59:24 -08:00
|
|
|
.map(|(stake, source)| Self::active_stakes_can_merge(invoke_context, stake, source))
|
2020-11-19 13:44:16 -08:00
|
|
|
.unwrap_or(Ok(()))?;
|
|
|
|
let merged_state = match (self, source) {
|
|
|
|
(Self::Inactive(_, _), Self::Inactive(_, _)) => None,
|
|
|
|
(Self::Inactive(_, _), Self::ActivationEpoch(_, _)) => None,
|
|
|
|
(Self::ActivationEpoch(meta, mut stake), Self::Inactive(_, source_lamports)) => {
|
2021-02-16 19:42:46 -08:00
|
|
|
stake.delegation.stake = checked_add(stake.delegation.stake, source_lamports)?;
|
2020-11-19 13:44:16 -08:00
|
|
|
Some(StakeState::Stake(meta, stake))
|
|
|
|
}
|
|
|
|
(
|
|
|
|
Self::ActivationEpoch(meta, mut stake),
|
|
|
|
Self::ActivationEpoch(source_meta, source_stake),
|
|
|
|
) => {
|
2021-02-16 19:42:46 -08:00
|
|
|
let source_lamports = checked_add(
|
|
|
|
source_meta.rent_exempt_reserve,
|
|
|
|
source_stake.delegation.stake,
|
|
|
|
)?;
|
|
|
|
stake.delegation.stake = checked_add(stake.delegation.stake, source_lamports)?;
|
2020-11-19 13:44:16 -08:00
|
|
|
Some(StakeState::Stake(meta, stake))
|
|
|
|
}
|
|
|
|
(Self::FullyActive(meta, mut stake), Self::FullyActive(_, source_stake)) => {
|
|
|
|
// Don't stake the source account's `rent_exempt_reserve` to
|
|
|
|
// protect against the magic activation loophole. It will
|
|
|
|
// instead be moved into the destination account as extra,
|
|
|
|
// withdrawable `lamports`
|
2021-02-16 19:42:46 -08:00
|
|
|
stake.delegation.stake =
|
|
|
|
checked_add(stake.delegation.stake, source_stake.delegation.stake)?;
|
2020-11-19 13:44:16 -08:00
|
|
|
Some(StakeState::Stake(meta, stake))
|
|
|
|
}
|
|
|
|
_ => return Err(StakeError::MergeMismatch.into()),
|
|
|
|
};
|
|
|
|
Ok(merged_state)
|
2020-11-19 11:53:05 -08:00
|
|
|
}
|
|
|
|
}
|
2020-11-19 13:44:16 -08:00
|
|
|
|
2020-01-22 12:21:31 -08:00
|
|
|
// utility function, used by runtime
|
2020-07-20 21:57:25 -07:00
|
|
|
// returns a tuple of (stakers_reward,voters_reward)
|
2020-01-22 12:21:31 -08:00
|
|
|
pub fn redeem_rewards(
|
2020-11-26 20:20:47 -08:00
|
|
|
rewarded_epoch: Epoch,
|
2021-03-09 13:06:07 -08:00
|
|
|
stake_account: &mut AccountSharedData,
|
|
|
|
vote_account: &mut AccountSharedData,
|
2020-07-20 21:57:25 -07:00
|
|
|
point_value: &PointValue,
|
2020-01-22 12:21:31 -08:00
|
|
|
stake_history: Option<&StakeHistory>,
|
2020-11-07 23:43:50 -08:00
|
|
|
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
|
2020-11-11 13:11:57 -08:00
|
|
|
fix_stake_deactivate: bool,
|
2020-02-04 18:50:24 -08:00
|
|
|
) -> Result<(u64, u64), InstructionError> {
|
2020-01-22 12:21:31 -08:00
|
|
|
if let StakeState::Stake(meta, mut stake) = stake_account.state()? {
|
2020-02-25 17:12:01 -08:00
|
|
|
let vote_state: VoteState =
|
|
|
|
StateMut::<VoteStateVersions>::state(vote_account)?.convert_to_current();
|
2020-11-07 23:43:50 -08:00
|
|
|
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
|
2020-11-26 20:20:47 -08:00
|
|
|
inflation_point_calc_tracer(
|
|
|
|
&InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake.stake(
|
|
|
|
rewarded_epoch,
|
|
|
|
stake_history,
|
|
|
|
fix_stake_deactivate,
|
|
|
|
)),
|
|
|
|
);
|
2020-11-07 23:43:50 -08:00
|
|
|
inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve(
|
|
|
|
meta.rent_exempt_reserve,
|
|
|
|
));
|
|
|
|
inflation_point_calc_tracer(&InflationPointCalculationEvent::Commission(
|
|
|
|
vote_state.commission,
|
|
|
|
));
|
|
|
|
}
|
2020-01-22 12:21:31 -08:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards(
|
|
|
|
&mut stake,
|
2020-11-07 23:43:50 -08:00
|
|
|
point_value,
|
|
|
|
&vote_state,
|
|
|
|
stake_history,
|
|
|
|
inflation_point_calc_tracer,
|
2020-11-11 13:11:57 -08:00
|
|
|
fix_stake_deactivate,
|
2020-11-07 23:43:50 -08:00
|
|
|
) {
|
2021-04-27 07:11:35 -07:00
|
|
|
stake_account.checked_add_lamports(stakers_reward)?;
|
|
|
|
vote_account.checked_add_lamports(voters_reward)?;
|
2020-01-22 12:21:31 -08:00
|
|
|
|
|
|
|
stake_account.set_state(&StakeState::Stake(meta, stake))?;
|
|
|
|
|
2020-02-04 18:50:24 -08:00
|
|
|
Ok((stakers_reward, voters_reward))
|
2020-01-22 12:21:31 -08:00
|
|
|
} else {
|
|
|
|
Err(StakeError::NoCreditsToRedeem.into())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-20 21:57:25 -07:00
|
|
|
// utility function, used by runtime
|
|
|
|
pub fn calculate_points(
|
2021-03-09 13:06:07 -08:00
|
|
|
stake_account: &AccountSharedData,
|
|
|
|
vote_account: &AccountSharedData,
|
2020-07-20 21:57:25 -07:00
|
|
|
stake_history: Option<&StakeHistory>,
|
2020-11-11 13:11:57 -08:00
|
|
|
fix_stake_deactivate: bool,
|
2020-07-20 21:57:25 -07:00
|
|
|
) -> Result<u128, InstructionError> {
|
|
|
|
if let StakeState::Stake(_meta, stake) = stake_account.state()? {
|
|
|
|
let vote_state: VoteState =
|
|
|
|
StateMut::<VoteStateVersions>::state(vote_account)?.convert_to_current();
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
Ok(calculate_stake_points(
|
|
|
|
&stake,
|
2020-11-11 13:11:57 -08:00
|
|
|
&vote_state,
|
|
|
|
stake_history,
|
|
|
|
&mut null_tracer(),
|
|
|
|
fix_stake_deactivate,
|
|
|
|
))
|
2020-07-20 21:57:25 -07:00
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-06 12:32:05 -08:00
|
|
|
// utility function, used by Split
|
|
|
|
//This emulates current Rent math in order to preserve backward compatibility. In the future, and
|
|
|
|
//to support variable rent, the Split instruction should pass in the Rent sysvar instead.
|
|
|
|
fn calculate_split_rent_exempt_reserve(
|
|
|
|
source_rent_exempt_reserve: u64,
|
|
|
|
source_data_len: u64,
|
|
|
|
split_data_len: u64,
|
|
|
|
) -> u64 {
|
|
|
|
let lamports_per_byte_year =
|
|
|
|
source_rent_exempt_reserve / (source_data_len + ACCOUNT_STORAGE_OVERHEAD);
|
|
|
|
lamports_per_byte_year * (split_data_len + ACCOUNT_STORAGE_OVERHEAD)
|
|
|
|
}
|
|
|
|
|
2020-11-19 12:15:06 -08:00
|
|
|
pub type RewriteStakeStatus = (&'static str, (u64, u64), (u64, u64));
|
|
|
|
|
|
|
|
pub fn rewrite_stakes(
|
2021-03-09 13:06:07 -08:00
|
|
|
stake_account: &mut AccountSharedData,
|
2020-11-19 12:15:06 -08:00
|
|
|
rent: &Rent,
|
|
|
|
) -> Result<RewriteStakeStatus, InstructionError> {
|
|
|
|
match stake_account.state()? {
|
|
|
|
StakeState::Initialized(mut meta) => {
|
2021-03-09 14:31:33 -08:00
|
|
|
let meta_status = meta.rewrite_rent_exempt_reserve(rent, stake_account.data().len());
|
2020-11-19 12:15:06 -08:00
|
|
|
|
|
|
|
if meta_status.is_none() {
|
|
|
|
return Err(InstructionError::InvalidAccountData);
|
|
|
|
}
|
|
|
|
|
|
|
|
stake_account.set_state(&StakeState::Initialized(meta))?;
|
|
|
|
Ok(("initialized", meta_status.unwrap_or_default(), (0, 0)))
|
|
|
|
}
|
|
|
|
StakeState::Stake(mut meta, mut stake) => {
|
2021-03-09 14:31:33 -08:00
|
|
|
let meta_status = meta.rewrite_rent_exempt_reserve(rent, stake_account.data().len());
|
2020-11-19 12:15:06 -08:00
|
|
|
let stake_status = stake
|
|
|
|
.delegation
|
2021-04-29 08:44:46 -07:00
|
|
|
.rewrite_stake(stake_account.lamports(), meta.rent_exempt_reserve);
|
2020-11-19 12:15:06 -08:00
|
|
|
|
|
|
|
if meta_status.is_none() && stake_status.is_none() {
|
|
|
|
return Err(InstructionError::InvalidAccountData);
|
|
|
|
}
|
|
|
|
|
|
|
|
stake_account.set_state(&StakeState::Stake(meta, stake))?;
|
|
|
|
Ok((
|
|
|
|
"stake",
|
|
|
|
meta_status.unwrap_or_default(),
|
|
|
|
stake_status.unwrap_or_default(),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
_ => Err(InstructionError::InvalidAccountData),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-12 20:59:57 -07:00
|
|
|
// utility function, used by runtime::Stakes, tests
|
|
|
|
pub fn new_stake_history_entry<'a, I>(
|
|
|
|
epoch: Epoch,
|
|
|
|
stakes: I,
|
|
|
|
history: Option<&StakeHistory>,
|
2020-11-11 13:11:57 -08:00
|
|
|
fix_stake_deactivate: bool,
|
2019-08-12 20:59:57 -07:00
|
|
|
) -> StakeHistoryEntry
|
|
|
|
where
|
2019-11-25 13:14:32 -08:00
|
|
|
I: Iterator<Item = &'a Delegation>,
|
2019-08-12 20:59:57 -07:00
|
|
|
{
|
|
|
|
// whatever the stake says they had for the epoch
|
|
|
|
// and whatever the were still waiting for
|
2019-08-17 18:12:30 -07:00
|
|
|
fn add(a: (u64, u64, u64), b: (u64, u64, u64)) -> (u64, u64, u64) {
|
|
|
|
(a.0 + b.0, a.1 + b.1, a.2 + b.2)
|
|
|
|
}
|
|
|
|
let (effective, activating, deactivating) = stakes.fold((0, 0, 0), |sum, stake| {
|
2020-11-11 13:11:57 -08:00
|
|
|
add(
|
|
|
|
sum,
|
|
|
|
stake.stake_activating_and_deactivating(epoch, history, fix_stake_deactivate),
|
|
|
|
)
|
2019-08-17 18:12:30 -07:00
|
|
|
});
|
2019-08-12 20:59:57 -07:00
|
|
|
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
2019-08-17 18:12:30 -07:00
|
|
|
deactivating,
|
2019-08-12 20:59:57 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-25 15:11:55 -08:00
|
|
|
// genesis investor accounts
|
|
|
|
pub fn create_lockup_stake_account(
|
|
|
|
authorized: &Authorized,
|
|
|
|
lockup: &Lockup,
|
|
|
|
rent: &Rent,
|
|
|
|
lamports: u64,
|
2021-03-09 13:06:07 -08:00
|
|
|
) -> AccountSharedData {
|
|
|
|
let mut stake_account =
|
|
|
|
AccountSharedData::new(lamports, std::mem::size_of::<StakeState>(), &id());
|
2019-11-25 15:11:55 -08:00
|
|
|
|
2021-03-09 14:31:33 -08:00
|
|
|
let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
|
2020-01-02 13:15:31 -08:00
|
|
|
assert!(
|
|
|
|
lamports >= rent_exempt_reserve,
|
|
|
|
"lamports: {} is less than rent_exempt_reserve {}",
|
|
|
|
lamports,
|
|
|
|
rent_exempt_reserve
|
|
|
|
);
|
2019-11-25 15:11:55 -08:00
|
|
|
|
|
|
|
stake_account
|
|
|
|
.set_state(&StakeState::Initialized(Meta {
|
|
|
|
authorized: *authorized,
|
|
|
|
lockup: *lockup,
|
|
|
|
rent_exempt_reserve,
|
|
|
|
}))
|
|
|
|
.expect("set_state");
|
|
|
|
|
|
|
|
stake_account
|
|
|
|
}
|
|
|
|
|
|
|
|
// utility function, used by Bank, tests, genesis for bootstrap
|
2019-09-26 13:29:29 -07:00
|
|
|
pub fn create_account(
|
|
|
|
authorized: &Pubkey,
|
|
|
|
voter_pubkey: &Pubkey,
|
2021-03-09 13:06:07 -08:00
|
|
|
vote_account: &AccountSharedData,
|
2019-11-12 12:33:40 -08:00
|
|
|
rent: &Rent,
|
2019-09-26 13:29:29 -07:00
|
|
|
lamports: u64,
|
2021-03-09 13:06:07 -08:00
|
|
|
) -> AccountSharedData {
|
2020-07-20 21:57:25 -07:00
|
|
|
do_create_account(
|
|
|
|
authorized,
|
|
|
|
voter_pubkey,
|
|
|
|
vote_account,
|
|
|
|
rent,
|
|
|
|
lamports,
|
|
|
|
Epoch::MAX,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// utility function, used by tests
|
|
|
|
pub fn create_account_with_activation_epoch(
|
|
|
|
authorized: &Pubkey,
|
|
|
|
voter_pubkey: &Pubkey,
|
2021-03-09 13:06:07 -08:00
|
|
|
vote_account: &AccountSharedData,
|
2020-07-20 21:57:25 -07:00
|
|
|
rent: &Rent,
|
|
|
|
lamports: u64,
|
|
|
|
activation_epoch: Epoch,
|
2021-03-09 13:06:07 -08:00
|
|
|
) -> AccountSharedData {
|
2020-07-20 21:57:25 -07:00
|
|
|
do_create_account(
|
|
|
|
authorized,
|
|
|
|
voter_pubkey,
|
|
|
|
vote_account,
|
|
|
|
rent,
|
|
|
|
lamports,
|
|
|
|
activation_epoch,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn do_create_account(
|
|
|
|
authorized: &Pubkey,
|
|
|
|
voter_pubkey: &Pubkey,
|
2021-03-09 13:06:07 -08:00
|
|
|
vote_account: &AccountSharedData,
|
2020-07-20 21:57:25 -07:00
|
|
|
rent: &Rent,
|
|
|
|
lamports: u64,
|
|
|
|
activation_epoch: Epoch,
|
2021-03-09 13:06:07 -08:00
|
|
|
) -> AccountSharedData {
|
|
|
|
let mut stake_account =
|
|
|
|
AccountSharedData::new(lamports, std::mem::size_of::<StakeState>(), &id());
|
2019-05-07 17:08:49 -07:00
|
|
|
|
2019-08-15 18:58:46 -07:00
|
|
|
let vote_state = VoteState::from(vote_account).expect("vote_state");
|
2019-11-25 15:11:55 -08:00
|
|
|
|
2021-03-09 14:31:33 -08:00
|
|
|
let rent_exempt_reserve = rent.minimum_balance(stake_account.data().len());
|
2019-11-25 15:11:55 -08:00
|
|
|
|
2019-05-07 17:08:49 -07:00
|
|
|
stake_account
|
2019-09-26 13:29:29 -07:00
|
|
|
.set_state(&StakeState::Stake(
|
2019-10-31 11:07:27 -07:00
|
|
|
Meta {
|
2019-11-25 15:11:55 -08:00
|
|
|
authorized: Authorized::auto(authorized),
|
2019-11-12 12:33:40 -08:00
|
|
|
rent_exempt_reserve,
|
2019-11-25 15:11:55 -08:00
|
|
|
..Meta::default()
|
2019-09-26 13:29:29 -07:00
|
|
|
},
|
2021-06-15 09:04:00 -07:00
|
|
|
new_stake(
|
2019-11-25 15:11:55 -08:00
|
|
|
lamports - rent_exempt_reserve, // underflow is an error, is basically: assert!(lamports > rent_exempt_reserve);
|
2019-11-12 12:33:40 -08:00
|
|
|
voter_pubkey,
|
|
|
|
&vote_state,
|
2020-07-20 21:57:25 -07:00
|
|
|
activation_epoch,
|
2019-11-12 12:33:40 -08:00
|
|
|
&Config::default(),
|
|
|
|
),
|
2019-09-26 13:29:29 -07:00
|
|
|
))
|
2019-05-07 17:08:49 -07:00
|
|
|
.expect("set_state");
|
|
|
|
|
|
|
|
stake_account
|
|
|
|
}
|
|
|
|
|
2019-04-01 16:45:53 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2021-01-21 09:59:24 -08:00
|
|
|
use solana_sdk::{
|
2021-04-23 10:34:22 -07:00
|
|
|
account::{AccountSharedData, WritableAccount},
|
2021-06-15 09:04:00 -07:00
|
|
|
clock::UnixTimestamp,
|
2021-04-23 10:34:22 -07:00
|
|
|
native_token,
|
|
|
|
process_instruction::MockInvokeContext,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
system_program,
|
2021-01-21 09:59:24 -08:00
|
|
|
};
|
2019-11-20 10:12:43 -08:00
|
|
|
use solana_vote_program::vote_state;
|
2020-11-02 19:53:27 -08:00
|
|
|
use std::{cell::RefCell, iter::FromIterator};
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2020-01-01 11:03:29 -08:00
|
|
|
#[test]
|
2020-04-21 21:05:49 -07:00
|
|
|
fn test_authorized_authorize() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let staker = solana_sdk::pubkey::new_rand();
|
2020-04-21 21:05:49 -07:00
|
|
|
let mut authorized = Authorized::auto(&staker);
|
|
|
|
let mut signers = HashSet::new();
|
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
|
2020-04-21 21:05:49 -07:00
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
signers.insert(staker);
|
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
authorized.authorize(&signers, &staker, StakeAuthorize::Staker, None),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_authorized_authorize_with_custodian() {
|
|
|
|
let staker = solana_sdk::pubkey::new_rand();
|
|
|
|
let custodian = solana_sdk::pubkey::new_rand();
|
|
|
|
let invalid_custodian = solana_sdk::pubkey::new_rand();
|
|
|
|
let mut authorized = Authorized::auto(&staker);
|
|
|
|
let mut signers = HashSet::new();
|
|
|
|
signers.insert(staker);
|
|
|
|
|
|
|
|
let lockup = Lockup {
|
|
|
|
epoch: 1,
|
|
|
|
unix_timestamp: 1,
|
|
|
|
custodian,
|
|
|
|
};
|
|
|
|
let clock = Clock {
|
|
|
|
epoch: 0,
|
|
|
|
unix_timestamp: 0,
|
|
|
|
..Clock::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Legacy behaviour when the `require_custodian_for_locked_stake_authorize` feature is
|
|
|
|
// inactive
|
|
|
|
assert_eq!(
|
|
|
|
authorized.authorize(&signers, &staker, StakeAuthorize::Withdrawer, None),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// No lockup, no custodian
|
|
|
|
assert_eq!(
|
|
|
|
authorized.authorize(
|
|
|
|
&signers,
|
|
|
|
&staker,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
Some((&Lockup::default(), &clock, None))
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// No lockup, invalid custodian not a signer
|
|
|
|
assert_eq!(
|
|
|
|
authorized.authorize(
|
|
|
|
&signers,
|
|
|
|
&staker,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
|
|
|
|
),
|
|
|
|
Ok(()) // <== invalid custodian doesn't matter, there's no lockup
|
|
|
|
);
|
|
|
|
|
|
|
|
// Lockup active, invalid custodian not a signer
|
|
|
|
assert_eq!(
|
|
|
|
authorized.authorize(
|
|
|
|
&signers,
|
|
|
|
&staker,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
Some((&lockup, &clock, Some(&invalid_custodian)))
|
|
|
|
),
|
|
|
|
Err(StakeError::CustodianSignatureMissing.into()),
|
|
|
|
);
|
|
|
|
|
|
|
|
signers.insert(invalid_custodian);
|
|
|
|
|
|
|
|
// No lockup, invalid custodian is a signer
|
|
|
|
assert_eq!(
|
|
|
|
authorized.authorize(
|
|
|
|
&signers,
|
|
|
|
&staker,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
Some((&Lockup::default(), &clock, Some(&invalid_custodian)))
|
|
|
|
),
|
|
|
|
Ok(()) // <== invalid custodian doesn't matter, there's no lockup
|
|
|
|
);
|
|
|
|
|
|
|
|
// Lockup active, invalid custodian is a signer
|
|
|
|
signers.insert(invalid_custodian);
|
|
|
|
assert_eq!(
|
|
|
|
authorized.authorize(
|
|
|
|
&signers,
|
|
|
|
&staker,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
Some((&lockup, &clock, Some(&invalid_custodian)))
|
|
|
|
),
|
|
|
|
Err(StakeError::LockupInForce.into()), // <== invalid custodian rejected
|
|
|
|
);
|
|
|
|
|
|
|
|
signers.remove(&invalid_custodian);
|
|
|
|
|
|
|
|
// Lockup active, no custodian
|
|
|
|
assert_eq!(
|
|
|
|
authorized.authorize(
|
|
|
|
&signers,
|
|
|
|
&staker,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
Some((&lockup, &clock, None))
|
|
|
|
),
|
|
|
|
Err(StakeError::CustodianMissing.into()),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Lockup active, custodian not a signer
|
|
|
|
assert_eq!(
|
|
|
|
authorized.authorize(
|
|
|
|
&signers,
|
|
|
|
&staker,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
Some((&lockup, &clock, Some(&custodian)))
|
|
|
|
),
|
|
|
|
Err(StakeError::CustodianSignatureMissing.into()),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Lockup active, custodian is a signer
|
|
|
|
signers.insert(custodian);
|
|
|
|
assert_eq!(
|
|
|
|
authorized.authorize(
|
|
|
|
&signers,
|
|
|
|
&staker,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
Some((&lockup, &clock, Some(&custodian)))
|
|
|
|
),
|
2020-04-21 21:05:49 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-08-18 15:41:49 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stake_state_stake_from_fail() {
|
2021-03-09 13:06:07 -08:00
|
|
|
let mut stake_account = AccountSharedData::new(0, std::mem::size_of::<StakeState>(), &id());
|
2019-08-12 20:59:57 -07:00
|
|
|
|
2019-08-18 15:41:49 -07:00
|
|
|
stake_account
|
|
|
|
.set_state(&StakeState::default())
|
|
|
|
.expect("set_state");
|
2019-08-12 20:59:57 -07:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
assert_eq!(stake_from(&stake_account), None);
|
2019-08-18 15:41:49 -07:00
|
|
|
}
|
2019-08-12 20:59:57 -07:00
|
|
|
|
2019-08-18 15:41:49 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stake_is_bootstrap() {
|
2021-05-19 07:31:47 -07:00
|
|
|
assert!(Delegation {
|
|
|
|
activation_epoch: std::u64::MAX,
|
|
|
|
..Delegation::default()
|
|
|
|
}
|
|
|
|
.is_bootstrap());
|
|
|
|
assert!(!Delegation {
|
|
|
|
activation_epoch: 0,
|
|
|
|
..Delegation::default()
|
|
|
|
}
|
|
|
|
.is_bootstrap());
|
2019-08-12 20:59:57 -07:00
|
|
|
}
|
|
|
|
|
2019-04-01 16:45:53 -07:00
|
|
|
#[test]
|
2020-01-29 17:59:14 -08:00
|
|
|
fn test_stake_delegate() {
|
|
|
|
let mut clock = Clock {
|
2019-08-01 14:27:47 -07:00
|
|
|
epoch: 1,
|
2020-01-29 17:59:14 -08:00
|
|
|
..Clock::default()
|
2019-07-31 15:13:26 -07:00
|
|
|
};
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let vote_pubkey = solana_sdk::pubkey::new_rand();
|
2021-05-20 22:31:55 -07:00
|
|
|
let vote_pubkey_2 = solana_sdk::pubkey::new_rand();
|
|
|
|
|
2019-04-01 16:45:53 -07:00
|
|
|
let mut vote_state = VoteState::default();
|
|
|
|
for i in 0..1000 {
|
2019-05-21 21:45:38 -07:00
|
|
|
vote_state.process_slot_vote_unchecked(i);
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|
2021-05-20 22:31:55 -07:00
|
|
|
let mut vote_state_2 = VoteState::default();
|
|
|
|
for i in 0..1000 {
|
|
|
|
vote_state_2.process_slot_vote_unchecked(i);
|
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_account = RefCell::new(vote_state::create_account(
|
2020-01-22 09:11:56 -08:00
|
|
|
&vote_pubkey,
|
2020-10-19 12:12:08 -07:00
|
|
|
&solana_sdk::pubkey::new_rand(),
|
2020-01-22 09:11:56 -08:00
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
2021-05-20 22:31:55 -07:00
|
|
|
let vote_account_2 = RefCell::new(vote_state::create_account(
|
|
|
|
&vote_pubkey_2,
|
|
|
|
&solana_sdk::pubkey::new_rand(),
|
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
2021-05-20 22:31:55 -07:00
|
|
|
let vote_keyed_account_2 = KeyedAccount::new(&vote_pubkey_2, false, &vote_account_2);
|
|
|
|
|
2020-02-25 17:12:01 -08:00
|
|
|
let vote_state_credits = vote_state.credits();
|
|
|
|
vote_keyed_account
|
2020-12-21 15:19:04 -08:00
|
|
|
.set_state(&VoteStateVersions::new_current(vote_state))
|
2020-02-25 17:12:01 -08:00
|
|
|
.unwrap();
|
2021-05-20 22:31:55 -07:00
|
|
|
let vote_state_credits_2 = vote_state_2.credits();
|
|
|
|
vote_keyed_account_2
|
|
|
|
.set_state(&VoteStateVersions::new_current(vote_state_2))
|
|
|
|
.unwrap();
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-06-10 12:17:29 -07:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-09-04 13:34:09 -07:00
|
|
|
stake_lamports,
|
2019-10-31 11:07:27 -07:00
|
|
|
&StakeState::Initialized(Meta {
|
|
|
|
authorized: Authorized {
|
2019-09-26 13:29:29 -07:00
|
|
|
staker: stake_pubkey,
|
|
|
|
withdrawer: stake_pubkey,
|
|
|
|
},
|
2019-10-31 11:07:27 -07:00
|
|
|
..Meta::default()
|
|
|
|
}),
|
2019-09-04 13:34:09 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-06-17 19:34:21 -07:00
|
|
|
// unsigned keyed account
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-10-14 15:02:24 -07:00
|
|
|
let mut signers = HashSet::default();
|
2019-04-01 16:45:53 -07:00
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.delegate(
|
2019-09-12 19:03:28 -07:00
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
2020-01-29 17:59:14 -08:00
|
|
|
&StakeHistory::default(),
|
2019-09-12 19:03:28 -07:00
|
|
|
&Config::default(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2019-09-12 19:03:28 -07:00
|
|
|
),
|
2019-04-01 16:45:53 -07:00
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
2019-06-17 19:34:21 -07:00
|
|
|
// signed keyed account
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-10-14 15:02:24 -07:00
|
|
|
signers.insert(stake_pubkey);
|
2019-04-01 16:45:53 -07:00
|
|
|
assert!(stake_keyed_account
|
2020-01-29 17:59:14 -08:00
|
|
|
.delegate(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&Config::default(),
|
|
|
|
&signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2020-01-29 17:59:14 -08:00
|
|
|
)
|
2019-04-01 16:45:53 -07:00
|
|
|
.is_ok());
|
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
// verify that delegate() looks right, compare against hand-rolled
|
2021-06-15 09:04:00 -07:00
|
|
|
let stake = stake_from(&stake_keyed_account.account.borrow()).unwrap();
|
2019-04-01 16:45:53 -07:00
|
|
|
assert_eq!(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake,
|
|
|
|
Stake {
|
2019-11-25 13:14:32 -08:00
|
|
|
delegation: Delegation {
|
|
|
|
voter_pubkey: vote_pubkey,
|
|
|
|
stake: stake_lamports,
|
|
|
|
activation_epoch: clock.epoch,
|
|
|
|
deactivation_epoch: std::u64::MAX,
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
2020-02-25 17:12:01 -08:00
|
|
|
credits_observed: vote_state_credits,
|
2019-09-11 09:48:29 -07:00
|
|
|
}
|
2019-06-17 19:34:21 -07:00
|
|
|
);
|
2020-01-29 17:59:14 -08:00
|
|
|
|
|
|
|
clock.epoch += 1;
|
|
|
|
|
2021-05-20 22:31:55 -07:00
|
|
|
// verify that delegate fails as stake is active and not deactivating
|
2019-10-29 14:42:45 -07:00
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.delegate(
|
2019-10-29 14:42:45 -07:00
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
2020-01-29 17:59:14 -08:00
|
|
|
&StakeHistory::default(),
|
2019-10-29 14:42:45 -07:00
|
|
|
&Config::default(),
|
2021-05-20 22:31:55 -07:00
|
|
|
&signers,
|
|
|
|
true
|
|
|
|
),
|
|
|
|
Err(StakeError::TooSoonToRedelegate.into())
|
|
|
|
);
|
|
|
|
|
|
|
|
// deactivate
|
|
|
|
stake_keyed_account.deactivate(&clock, &signers).unwrap();
|
|
|
|
|
|
|
|
// verify that delegate to a different vote account fails
|
|
|
|
// during deactivation
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.delegate(
|
|
|
|
&vote_keyed_account_2,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&Config::default(),
|
|
|
|
&signers,
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
Err(StakeError::TooSoonToRedelegate.into())
|
|
|
|
);
|
|
|
|
|
|
|
|
// verify that delegate succeeds to same vote account
|
|
|
|
// when stake is deactivating
|
|
|
|
stake_keyed_account
|
|
|
|
.delegate(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&Config::default(),
|
|
|
|
&signers,
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// verify that deactivation has been cleared
|
2021-06-15 09:04:00 -07:00
|
|
|
let stake = stake_from(&stake_keyed_account.account.borrow()).unwrap();
|
2021-05-20 22:31:55 -07:00
|
|
|
assert_eq!(stake.delegation.deactivation_epoch, std::u64::MAX);
|
|
|
|
|
|
|
|
// verify that delegate to a different vote account fails
|
|
|
|
// if stake is still active
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.delegate(
|
|
|
|
&vote_keyed_account_2,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&Config::default(),
|
|
|
|
&signers,
|
|
|
|
true,
|
2019-10-29 14:42:45 -07:00
|
|
|
),
|
|
|
|
Err(StakeError::TooSoonToRedelegate.into())
|
|
|
|
);
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
// deactivate, so we can re-delegate
|
|
|
|
stake_keyed_account.deactivate(&clock, &signers).unwrap();
|
|
|
|
|
|
|
|
// without stake history, cool down is instantaneous
|
2019-10-29 14:42:45 -07:00
|
|
|
clock.epoch += 1;
|
2020-01-29 17:59:14 -08:00
|
|
|
|
2021-05-20 22:31:55 -07:00
|
|
|
// verify that delegate can be called to new vote account, 2nd is redelegate
|
2019-09-11 09:48:29 -07:00
|
|
|
assert!(stake_keyed_account
|
2020-01-29 17:59:14 -08:00
|
|
|
.delegate(
|
2021-05-20 22:31:55 -07:00
|
|
|
&vote_keyed_account_2,
|
2020-01-29 17:59:14 -08:00
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&Config::default(),
|
2021-05-20 22:31:55 -07:00
|
|
|
&signers,
|
|
|
|
true,
|
2020-01-29 17:59:14 -08:00
|
|
|
)
|
2019-09-11 09:48:29 -07:00
|
|
|
.is_ok());
|
|
|
|
|
2020-11-16 12:42:59 -08:00
|
|
|
// signed but faked vote account
|
2021-05-20 22:31:55 -07:00
|
|
|
let faked_vote_account = vote_account_2.clone();
|
2021-04-23 10:34:22 -07:00
|
|
|
faked_vote_account
|
|
|
|
.borrow_mut()
|
|
|
|
.set_owner(solana_sdk::pubkey::new_rand());
|
2021-05-20 22:31:55 -07:00
|
|
|
let faked_vote_keyed_account =
|
|
|
|
KeyedAccount::new(&vote_pubkey_2, false, &faked_vote_account);
|
2020-11-16 12:42:59 -08:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.delegate(
|
|
|
|
&faked_vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&Config::default(),
|
|
|
|
&signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2020-11-16 12:42:59 -08:00
|
|
|
),
|
|
|
|
Err(solana_sdk::instruction::InstructionError::IncorrectProgramId)
|
|
|
|
);
|
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
// verify that delegate() looks right, compare against hand-rolled
|
2021-06-15 09:04:00 -07:00
|
|
|
let stake = stake_from(&stake_keyed_account.account.borrow()).unwrap();
|
2020-01-29 17:59:14 -08:00
|
|
|
assert_eq!(
|
|
|
|
stake,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
2021-05-20 22:31:55 -07:00
|
|
|
voter_pubkey: vote_pubkey_2,
|
2020-01-29 17:59:14 -08:00
|
|
|
stake: stake_lamports,
|
|
|
|
activation_epoch: clock.epoch,
|
|
|
|
deactivation_epoch: std::u64::MAX,
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
2021-05-20 22:31:55 -07:00
|
|
|
credits_observed: vote_state_credits_2,
|
2020-01-29 17:59:14 -08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// verify that non-stakes fail delegate()
|
2019-06-24 16:01:02 -07:00
|
|
|
let stake_state = StakeState::RewardsPool;
|
|
|
|
|
2019-04-01 16:45:53 -07:00
|
|
|
stake_keyed_account.set_state(&stake_state).unwrap();
|
|
|
|
assert!(stake_keyed_account
|
2020-01-29 17:59:14 -08:00
|
|
|
.delegate(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&Config::default(),
|
2021-05-20 22:31:55 -07:00
|
|
|
&signers,
|
|
|
|
true,
|
2020-01-29 17:59:14 -08:00
|
|
|
)
|
2019-04-01 16:45:53 -07:00
|
|
|
.is_err());
|
|
|
|
}
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2019-11-25 13:14:32 -08:00
|
|
|
fn create_stake_history_from_delegations(
|
2019-08-18 15:41:49 -07:00
|
|
|
bootstrap: Option<u64>,
|
|
|
|
epochs: std::ops::Range<Epoch>,
|
2019-11-25 13:14:32 -08:00
|
|
|
delegations: &[Delegation],
|
2019-08-18 15:41:49 -07:00
|
|
|
) -> StakeHistory {
|
|
|
|
let mut stake_history = StakeHistory::default();
|
|
|
|
|
2019-11-25 13:14:32 -08:00
|
|
|
let bootstrap_delegation = if let Some(bootstrap) = bootstrap {
|
|
|
|
vec![Delegation {
|
2019-08-18 15:41:49 -07:00
|
|
|
activation_epoch: std::u64::MAX,
|
|
|
|
stake: bootstrap,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-18 15:41:49 -07:00
|
|
|
}]
|
|
|
|
} else {
|
|
|
|
vec![]
|
|
|
|
};
|
|
|
|
|
|
|
|
for epoch in epochs {
|
|
|
|
let entry = new_stake_history_entry(
|
|
|
|
epoch,
|
2019-11-25 13:14:32 -08:00
|
|
|
delegations.iter().chain(bootstrap_delegation.iter()),
|
2019-08-18 15:41:49 -07:00
|
|
|
Some(&stake_history),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2019-08-18 15:41:49 -07:00
|
|
|
);
|
|
|
|
stake_history.add(epoch, entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
stake_history
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_stake_activating_and_deactivating() {
|
2019-11-25 13:14:32 -08:00
|
|
|
let stake = Delegation {
|
2019-08-18 15:41:49 -07:00
|
|
|
stake: 1_000,
|
|
|
|
activation_epoch: 0, // activating at zero
|
|
|
|
deactivation_epoch: 5,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-18 15:41:49 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// save this off so stake.config.warmup_rate changes don't break this test
|
2020-05-15 09:35:43 -07:00
|
|
|
let increment = (1_000_f64 * stake.warmup_cooldown_rate) as u64;
|
2019-08-18 15:41:49 -07:00
|
|
|
|
|
|
|
let mut stake_history = StakeHistory::default();
|
|
|
|
// assert that this stake follows step function if there's no history
|
|
|
|
assert_eq!(
|
2020-11-11 13:11:57 -08:00
|
|
|
stake.stake_activating_and_deactivating(
|
|
|
|
stake.activation_epoch,
|
|
|
|
Some(&stake_history),
|
|
|
|
true
|
|
|
|
),
|
2019-08-18 15:41:49 -07:00
|
|
|
(0, stake.stake, 0)
|
|
|
|
);
|
|
|
|
for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
|
|
|
|
assert_eq!(
|
2020-11-11 13:11:57 -08:00
|
|
|
stake.stake_activating_and_deactivating(epoch, Some(&stake_history), true),
|
2019-08-18 15:41:49 -07:00
|
|
|
(stake.stake, 0, 0)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// assert that this stake is full deactivating
|
|
|
|
assert_eq!(
|
2020-11-11 13:11:57 -08:00
|
|
|
stake.stake_activating_and_deactivating(
|
|
|
|
stake.deactivation_epoch,
|
|
|
|
Some(&stake_history),
|
|
|
|
true
|
|
|
|
),
|
2019-08-18 15:41:49 -07:00
|
|
|
(stake.stake, 0, stake.stake)
|
|
|
|
);
|
|
|
|
// assert that this stake is fully deactivated if there's no history
|
|
|
|
assert_eq!(
|
|
|
|
stake.stake_activating_and_deactivating(
|
|
|
|
stake.deactivation_epoch + 1,
|
2020-11-11 13:11:57 -08:00
|
|
|
Some(&stake_history),
|
|
|
|
true,
|
2019-08-18 15:41:49 -07:00
|
|
|
),
|
|
|
|
(0, 0, 0)
|
|
|
|
);
|
|
|
|
|
|
|
|
stake_history.add(
|
|
|
|
0u64, // entry for zero doesn't have my activating amount
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: 1_000,
|
|
|
|
activating: 0,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
// assert that this stake is broken, because above setup is broken
|
|
|
|
assert_eq!(
|
2020-11-11 13:11:57 -08:00
|
|
|
stake.stake_activating_and_deactivating(1, Some(&stake_history), true),
|
2019-08-18 15:41:49 -07:00
|
|
|
(0, stake.stake, 0)
|
|
|
|
);
|
|
|
|
|
|
|
|
stake_history.add(
|
|
|
|
0u64, // entry for zero has my activating amount
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: 1_000,
|
|
|
|
activating: 1_000,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
// no entry for 1, so this stake gets shorted
|
|
|
|
);
|
|
|
|
// assert that this stake is broken, because above setup is broken
|
|
|
|
assert_eq!(
|
2020-11-11 13:11:57 -08:00
|
|
|
stake.stake_activating_and_deactivating(2, Some(&stake_history), true),
|
2019-08-18 15:41:49 -07:00
|
|
|
(increment, stake.stake - increment, 0)
|
|
|
|
);
|
|
|
|
|
|
|
|
// start over, test deactivation edge cases
|
|
|
|
let mut stake_history = StakeHistory::default();
|
|
|
|
|
|
|
|
stake_history.add(
|
|
|
|
stake.deactivation_epoch, // entry for zero doesn't have my de-activating amount
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: 1_000,
|
|
|
|
activating: 0,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
// assert that this stake is broken, because above setup is broken
|
|
|
|
assert_eq!(
|
|
|
|
stake.stake_activating_and_deactivating(
|
|
|
|
stake.deactivation_epoch + 1,
|
2020-11-11 13:11:57 -08:00
|
|
|
Some(&stake_history),
|
|
|
|
true,
|
2019-08-18 15:41:49 -07:00
|
|
|
),
|
|
|
|
(stake.stake, 0, stake.stake) // says "I'm still waiting for deactivation"
|
|
|
|
);
|
|
|
|
|
|
|
|
// put in my initial deactivating amount, but don't put in an entry for next
|
|
|
|
stake_history.add(
|
|
|
|
stake.deactivation_epoch, // entry for zero has my de-activating amount
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: 1_000,
|
|
|
|
deactivating: 1_000,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
// assert that this stake is broken, because above setup is broken
|
|
|
|
assert_eq!(
|
|
|
|
stake.stake_activating_and_deactivating(
|
|
|
|
stake.deactivation_epoch + 2,
|
2020-11-11 13:11:57 -08:00
|
|
|
Some(&stake_history),
|
|
|
|
true,
|
2019-08-18 15:41:49 -07:00
|
|
|
),
|
|
|
|
(stake.stake - increment, 0, stake.stake - increment) // hung, should be lower
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-11-11 13:11:57 -08:00
|
|
|
mod same_epoch_activation_then_deactivation {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
enum OldDeactivationBehavior {
|
|
|
|
Stuck,
|
|
|
|
Slow,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn do_test(
|
|
|
|
old_behavior: OldDeactivationBehavior,
|
|
|
|
fix_stake_deactivate: bool,
|
|
|
|
expected_stakes: &[(u64, u64, u64)],
|
|
|
|
) {
|
|
|
|
let cluster_stake = 1_000;
|
|
|
|
let activating_stake = 10_000;
|
|
|
|
let some_stake = 700;
|
|
|
|
let some_epoch = 0;
|
|
|
|
|
|
|
|
let stake = Delegation {
|
|
|
|
stake: some_stake,
|
|
|
|
activation_epoch: some_epoch,
|
|
|
|
deactivation_epoch: some_epoch,
|
|
|
|
..Delegation::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut stake_history = StakeHistory::default();
|
|
|
|
let cluster_deactivation_at_stake_modified_epoch = match old_behavior {
|
|
|
|
OldDeactivationBehavior::Stuck => 0,
|
|
|
|
OldDeactivationBehavior::Slow => 1000,
|
|
|
|
};
|
|
|
|
|
|
|
|
let stake_history_entries = vec![
|
|
|
|
(
|
|
|
|
cluster_stake,
|
|
|
|
activating_stake,
|
|
|
|
cluster_deactivation_at_stake_modified_epoch,
|
|
|
|
),
|
|
|
|
(cluster_stake, activating_stake, 1000),
|
|
|
|
(cluster_stake, activating_stake, 1000),
|
|
|
|
(cluster_stake, activating_stake, 100),
|
|
|
|
(cluster_stake, activating_stake, 100),
|
|
|
|
(cluster_stake, activating_stake, 100),
|
|
|
|
(cluster_stake, activating_stake, 100),
|
|
|
|
];
|
|
|
|
|
|
|
|
for (epoch, (effective, activating, deactivating)) in
|
|
|
|
stake_history_entries.into_iter().enumerate()
|
|
|
|
{
|
|
|
|
stake_history.add(
|
|
|
|
epoch as Epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
expected_stakes,
|
|
|
|
(0..expected_stakes.len())
|
|
|
|
.map(|epoch| stake.stake_activating_and_deactivating(
|
|
|
|
epoch as u64,
|
|
|
|
Some(&stake_history),
|
|
|
|
fix_stake_deactivate
|
|
|
|
))
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_old_behavior_slow() {
|
|
|
|
do_test(
|
|
|
|
OldDeactivationBehavior::Slow,
|
|
|
|
false,
|
|
|
|
&[
|
|
|
|
(0, 0, 0),
|
|
|
|
(13, 0, 13),
|
|
|
|
(10, 0, 10),
|
|
|
|
(8, 0, 8),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_old_behavior_stuck() {
|
|
|
|
do_test(
|
|
|
|
OldDeactivationBehavior::Stuck,
|
|
|
|
false,
|
|
|
|
&[
|
|
|
|
(0, 0, 0),
|
|
|
|
(17, 0, 17),
|
|
|
|
(17, 0, 17),
|
|
|
|
(17, 0, 17),
|
|
|
|
(17, 0, 17),
|
|
|
|
(17, 0, 17),
|
|
|
|
(17, 0, 17),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_new_behavior_previously_slow() {
|
|
|
|
// any stake accounts activated and deactivated at the same epoch
|
|
|
|
// shouldn't been activated (then deactivated) at all!
|
|
|
|
|
|
|
|
do_test(
|
|
|
|
OldDeactivationBehavior::Slow,
|
|
|
|
true,
|
|
|
|
&[
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_new_behavior_previously_stuck() {
|
|
|
|
// any stake accounts activated and deactivated at the same epoch
|
|
|
|
// shouldn't been activated (then deactivated) at all!
|
|
|
|
|
|
|
|
do_test(
|
|
|
|
OldDeactivationBehavior::Stuck,
|
|
|
|
true,
|
|
|
|
&[
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
(0, 0, 0),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-24 19:49:35 -08:00
|
|
|
#[test]
|
|
|
|
fn test_inflation_and_slashing_with_activating_and_deactivating_stake() {
|
|
|
|
// some really boring delegation and stake_history setup
|
|
|
|
let (delegated_stake, mut stake, stake_history) = {
|
|
|
|
let cluster_stake = 1_000;
|
|
|
|
let delegated_stake = 700;
|
|
|
|
|
|
|
|
let stake = Delegation {
|
|
|
|
stake: delegated_stake,
|
|
|
|
activation_epoch: 0,
|
|
|
|
deactivation_epoch: 4,
|
|
|
|
..Delegation::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut stake_history = StakeHistory::default();
|
|
|
|
stake_history.add(
|
|
|
|
0,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: cluster_stake,
|
|
|
|
activating: delegated_stake,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
let newly_effective_at_epoch1 = (cluster_stake as f64 * 0.25) as u64;
|
|
|
|
assert_eq!(newly_effective_at_epoch1, 250);
|
|
|
|
stake_history.add(
|
|
|
|
1,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: cluster_stake + newly_effective_at_epoch1,
|
|
|
|
activating: delegated_stake - newly_effective_at_epoch1,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
let newly_effective_at_epoch2 =
|
|
|
|
((cluster_stake + newly_effective_at_epoch1) as f64 * 0.25) as u64;
|
|
|
|
assert_eq!(newly_effective_at_epoch2, 312);
|
|
|
|
stake_history.add(
|
|
|
|
2,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: cluster_stake
|
|
|
|
+ newly_effective_at_epoch1
|
|
|
|
+ newly_effective_at_epoch2,
|
|
|
|
activating: delegated_stake
|
|
|
|
- newly_effective_at_epoch1
|
|
|
|
- newly_effective_at_epoch2,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
stake_history.add(
|
|
|
|
3,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: cluster_stake + delegated_stake,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
stake_history.add(
|
|
|
|
4,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: cluster_stake + delegated_stake,
|
|
|
|
deactivating: delegated_stake,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
let newly_not_effective_stake_at_epoch5 =
|
|
|
|
((cluster_stake + delegated_stake) as f64 * 0.25) as u64;
|
|
|
|
assert_eq!(newly_not_effective_stake_at_epoch5, 425);
|
|
|
|
stake_history.add(
|
|
|
|
5,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective: cluster_stake + delegated_stake
|
|
|
|
- newly_not_effective_stake_at_epoch5,
|
|
|
|
deactivating: delegated_stake - newly_not_effective_stake_at_epoch5,
|
|
|
|
..StakeHistoryEntry::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
(delegated_stake, stake, stake_history)
|
|
|
|
};
|
|
|
|
|
|
|
|
// helper closures
|
|
|
|
let calculate_each_staking_status = |stake: &Delegation, epoch_count: usize| -> Vec<_> {
|
|
|
|
(0..epoch_count)
|
|
|
|
.map(|epoch| {
|
|
|
|
stake.stake_activating_and_deactivating(
|
|
|
|
epoch as u64,
|
|
|
|
Some(&stake_history),
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
};
|
|
|
|
let adjust_staking_status = |rate: f64, status: &Vec<_>| {
|
|
|
|
status
|
|
|
|
.clone()
|
|
|
|
.into_iter()
|
|
|
|
.map(|(a, b, c)| {
|
|
|
|
(
|
|
|
|
(a as f64 * rate) as u64,
|
|
|
|
(b as f64 * rate) as u64,
|
|
|
|
(c as f64 * rate) as u64,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
};
|
|
|
|
|
|
|
|
let expected_staking_status_transition = vec![
|
|
|
|
(0, 700, 0),
|
|
|
|
(250, 450, 0),
|
|
|
|
(562, 138, 0),
|
|
|
|
(700, 0, 0),
|
|
|
|
(700, 0, 700),
|
|
|
|
(275, 0, 275),
|
|
|
|
(0, 0, 0),
|
|
|
|
];
|
|
|
|
let expected_staking_status_transition_base = vec![
|
|
|
|
(0, 700, 0),
|
|
|
|
(250, 450, 0),
|
|
|
|
(562, 138 + 1, 0), // +1 is needed for rounding
|
|
|
|
(700, 0, 0),
|
|
|
|
(700, 0, 700),
|
|
|
|
(275 + 1, 0, 275 + 1), // +1 is needed for rounding
|
|
|
|
(0, 0, 0),
|
|
|
|
];
|
|
|
|
|
|
|
|
// normal stake activating and deactivating transition test, just in case
|
|
|
|
assert_eq!(
|
|
|
|
expected_staking_status_transition,
|
|
|
|
calculate_each_staking_status(&stake, expected_staking_status_transition.len())
|
|
|
|
);
|
|
|
|
|
|
|
|
// 10% inflation rewards assuming some sizable epochs passed!
|
|
|
|
let rate = 1.10;
|
|
|
|
stake.stake = (delegated_stake as f64 * rate) as u64;
|
|
|
|
let expected_staking_status_transition =
|
|
|
|
adjust_staking_status(rate, &expected_staking_status_transition_base);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
expected_staking_status_transition,
|
|
|
|
calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
|
|
|
|
);
|
|
|
|
|
|
|
|
// 50% slashing!!!
|
|
|
|
let rate = 0.5;
|
|
|
|
stake.stake = (delegated_stake as f64 * rate) as u64;
|
|
|
|
let expected_staking_status_transition =
|
|
|
|
adjust_staking_status(rate, &expected_staking_status_transition_base);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
expected_staking_status_transition,
|
|
|
|
calculate_each_staking_status(&stake, expected_staking_status_transition_base.len()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-10-14 15:40:24 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stop_activating_after_deactivation() {
|
|
|
|
solana_logger::setup();
|
2019-11-25 13:14:32 -08:00
|
|
|
let stake = Delegation {
|
2019-10-14 15:40:24 -07:00
|
|
|
stake: 1_000,
|
|
|
|
activation_epoch: 0,
|
|
|
|
deactivation_epoch: 3,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-10-14 15:40:24 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
let base_stake = 1_000;
|
|
|
|
let mut stake_history = StakeHistory::default();
|
|
|
|
let mut effective = base_stake;
|
|
|
|
let other_activation = 100;
|
|
|
|
let mut other_activations = vec![0];
|
|
|
|
|
|
|
|
// Build a stake history where the test staker always consumes all of the available warm
|
|
|
|
// up and cool down stake. However, simulate other stakers beginning to activate during
|
|
|
|
// the test staker's deactivation.
|
|
|
|
for epoch in 0..=stake.deactivation_epoch + 1 {
|
|
|
|
let (activating, deactivating) = if epoch < stake.deactivation_epoch {
|
|
|
|
(stake.stake + base_stake - effective, 0)
|
|
|
|
} else {
|
|
|
|
let other_activation_sum: u64 = other_activations.iter().sum();
|
|
|
|
let deactivating = effective - base_stake - other_activation_sum;
|
|
|
|
(other_activation, deactivating)
|
|
|
|
};
|
|
|
|
|
|
|
|
stake_history.add(
|
|
|
|
epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2021-04-18 10:27:36 -07:00
|
|
|
let effective_rate_limited = (effective as f64 * stake.warmup_cooldown_rate) as u64;
|
2019-10-14 15:40:24 -07:00
|
|
|
if epoch < stake.deactivation_epoch {
|
2021-04-18 10:27:36 -07:00
|
|
|
effective += effective_rate_limited.min(activating);
|
2019-10-14 15:40:24 -07:00
|
|
|
other_activations.push(0);
|
|
|
|
} else {
|
2021-04-18 10:27:36 -07:00
|
|
|
effective -= effective_rate_limited.min(deactivating);
|
2019-10-14 15:40:24 -07:00
|
|
|
effective += other_activation;
|
|
|
|
other_activations.push(other_activation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for epoch in 0..=stake.deactivation_epoch + 1 {
|
|
|
|
let history = stake_history.get(&epoch).unwrap();
|
|
|
|
let other_activations: u64 = other_activations[..=epoch as usize].iter().sum();
|
|
|
|
let expected_stake = history.effective - base_stake - other_activations;
|
|
|
|
let (expected_activating, expected_deactivating) = if epoch < stake.deactivation_epoch {
|
|
|
|
(history.activating, 0)
|
|
|
|
} else {
|
|
|
|
(0, history.deactivating)
|
|
|
|
};
|
|
|
|
assert_eq!(
|
2020-11-11 13:11:57 -08:00
|
|
|
stake.stake_activating_and_deactivating(epoch, Some(&stake_history), true),
|
2019-10-14 15:40:24 -07:00
|
|
|
(expected_stake, expected_activating, expected_deactivating)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-18 15:41:49 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stake_warmup_cooldown_sub_integer_moves() {
|
2019-11-25 13:14:32 -08:00
|
|
|
let delegations = [Delegation {
|
2019-08-18 15:41:49 -07:00
|
|
|
stake: 2,
|
|
|
|
activation_epoch: 0, // activating at zero
|
|
|
|
deactivation_epoch: 5,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-18 15:41:49 -07:00
|
|
|
}];
|
|
|
|
// give 2 epochs of cooldown
|
|
|
|
let epochs = 7;
|
|
|
|
// make boostrap stake smaller than warmup so warmup/cooldownn
|
|
|
|
// increment is always smaller than 1
|
2019-11-25 13:14:32 -08:00
|
|
|
let bootstrap = (delegations[0].warmup_cooldown_rate * 100.0 / 2.0) as u64;
|
|
|
|
let stake_history =
|
|
|
|
create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations);
|
2019-08-18 15:41:49 -07:00
|
|
|
let mut max_stake = 0;
|
|
|
|
let mut min_stake = 2;
|
|
|
|
|
|
|
|
for epoch in 0..epochs {
|
2019-11-25 13:14:32 -08:00
|
|
|
let stake = delegations
|
2019-08-18 15:41:49 -07:00
|
|
|
.iter()
|
2020-11-11 13:11:57 -08:00
|
|
|
.map(|delegation| delegation.stake(epoch, Some(&stake_history), true))
|
2019-08-18 15:41:49 -07:00
|
|
|
.sum::<u64>();
|
|
|
|
max_stake = max_stake.max(stake);
|
|
|
|
min_stake = min_stake.min(stake);
|
|
|
|
}
|
|
|
|
assert_eq!(max_stake, 2);
|
|
|
|
assert_eq!(min_stake, 0);
|
|
|
|
}
|
|
|
|
|
2019-06-17 19:34:21 -07:00
|
|
|
#[test]
|
2019-08-18 15:41:49 -07:00
|
|
|
fn test_stake_warmup_cooldown() {
|
2019-11-25 13:14:32 -08:00
|
|
|
let delegations = [
|
|
|
|
Delegation {
|
2019-08-17 18:12:30 -07:00
|
|
|
// never deactivates
|
2019-08-12 20:59:57 -07:00
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: std::u64::MAX,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-12 20:59:57 -07:00
|
|
|
},
|
2019-11-25 13:14:32 -08:00
|
|
|
Delegation {
|
2019-08-12 20:59:57 -07:00
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 0,
|
|
|
|
deactivation_epoch: 9,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-12 20:59:57 -07:00
|
|
|
},
|
2019-11-25 13:14:32 -08:00
|
|
|
Delegation {
|
2019-08-12 20:59:57 -07:00
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 1,
|
|
|
|
deactivation_epoch: 6,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-12 20:59:57 -07:00
|
|
|
},
|
2019-11-25 13:14:32 -08:00
|
|
|
Delegation {
|
2019-08-12 20:59:57 -07:00
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 2,
|
|
|
|
deactivation_epoch: 5,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-12 20:59:57 -07:00
|
|
|
},
|
2019-11-25 13:14:32 -08:00
|
|
|
Delegation {
|
2019-08-12 20:59:57 -07:00
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 2,
|
|
|
|
deactivation_epoch: 4,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-12 20:59:57 -07:00
|
|
|
},
|
2019-11-25 13:14:32 -08:00
|
|
|
Delegation {
|
2019-08-12 20:59:57 -07:00
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 4,
|
|
|
|
deactivation_epoch: 4,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-12 20:59:57 -07:00
|
|
|
},
|
|
|
|
];
|
2019-08-17 18:12:30 -07:00
|
|
|
// chosen to ensure that the last activated stake (at 4) finishes
|
|
|
|
// warming up and cooling down
|
|
|
|
// a stake takes 2.0f64.log(1.0 + STAKE_WARMUP_RATE) epochs to warm up or cool down
|
|
|
|
// when all alone, but the above overlap a lot
|
2019-08-12 20:59:57 -07:00
|
|
|
let epochs = 20;
|
|
|
|
|
2019-11-25 13:14:32 -08:00
|
|
|
let stake_history = create_stake_history_from_delegations(None, 0..epochs, &delegations);
|
2019-08-12 20:59:57 -07:00
|
|
|
|
2019-11-25 13:14:32 -08:00
|
|
|
let mut prev_total_effective_stake = delegations
|
2019-08-12 20:59:57 -07:00
|
|
|
.iter()
|
2020-11-11 13:11:57 -08:00
|
|
|
.map(|delegation| delegation.stake(0, Some(&stake_history), true))
|
2019-08-12 20:59:57 -07:00
|
|
|
.sum::<u64>();
|
|
|
|
|
2019-08-17 18:12:30 -07:00
|
|
|
// uncomment and add ! for fun with graphing
|
|
|
|
// eprintln("\n{:8} {:8} {:8}", " epoch", " total", " delta");
|
|
|
|
for epoch in 1..epochs {
|
2019-11-25 13:14:32 -08:00
|
|
|
let total_effective_stake = delegations
|
2019-08-12 20:59:57 -07:00
|
|
|
.iter()
|
2020-11-11 13:11:57 -08:00
|
|
|
.map(|delegation| delegation.stake(epoch, Some(&stake_history), true))
|
2019-08-12 20:59:57 -07:00
|
|
|
.sum::<u64>();
|
|
|
|
|
2019-08-17 18:12:30 -07:00
|
|
|
let delta = if total_effective_stake > prev_total_effective_stake {
|
|
|
|
total_effective_stake - prev_total_effective_stake
|
|
|
|
} else {
|
|
|
|
prev_total_effective_stake - total_effective_stake
|
|
|
|
};
|
|
|
|
|
|
|
|
// uncomment and add ! for fun with graphing
|
|
|
|
//eprint("{:8} {:8} {:8} ", epoch, total_effective_stake, delta);
|
|
|
|
//(0..(total_effective_stake as usize / (stakes.len() * 5))).for_each(|_| eprint("#"));
|
|
|
|
//eprintln();
|
2019-08-12 20:59:57 -07:00
|
|
|
|
2019-08-15 14:35:48 -07:00
|
|
|
assert!(
|
2019-08-17 18:12:30 -07:00
|
|
|
delta
|
2019-11-25 13:14:32 -08:00
|
|
|
<= ((prev_total_effective_stake as f64 * Config::default().warmup_cooldown_rate) as u64)
|
2019-08-17 18:12:30 -07:00
|
|
|
.max(1)
|
2019-08-15 14:35:48 -07:00
|
|
|
);
|
2019-08-17 18:12:30 -07:00
|
|
|
|
2019-08-12 20:59:57 -07:00
|
|
|
prev_total_effective_stake = total_effective_stake;
|
2019-06-17 19:34:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-21 23:45:03 -07:00
|
|
|
#[test]
|
2019-10-31 11:07:27 -07:00
|
|
|
fn test_stake_initialize() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-06-21 23:45:03 -07:00
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account =
|
2021-03-09 13:06:07 -08:00
|
|
|
AccountSharedData::new_ref(stake_lamports, std::mem::size_of::<StakeState>(), &id());
|
2019-06-21 23:45:03 -07:00
|
|
|
|
2019-09-04 13:34:09 -07:00
|
|
|
// unsigned keyed account
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
2020-10-19 12:12:08 -07:00
|
|
|
let custodian = solana_sdk::pubkey::new_rand();
|
2019-10-31 11:07:27 -07:00
|
|
|
|
|
|
|
// not enough balance for rent...
|
2019-09-26 13:29:29 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.initialize(
|
2019-10-31 11:07:27 -07:00
|
|
|
&Authorized::default(),
|
|
|
|
&Lockup::default(),
|
|
|
|
&Rent {
|
|
|
|
lamports_per_byte_year: 42,
|
2019-12-09 21:56:43 -08:00
|
|
|
..Rent::free()
|
2019-12-03 11:24:01 -08:00
|
|
|
},
|
2019-09-26 13:29:29 -07:00
|
|
|
),
|
2019-10-31 11:07:27 -07:00
|
|
|
Err(InstructionError::InsufficientFunds)
|
2019-09-26 13:29:29 -07:00
|
|
|
);
|
2019-09-04 13:34:09 -07:00
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
// this one works, as is uninit
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.initialize(
|
|
|
|
&Authorized::auto(&stake_pubkey),
|
2019-11-25 15:11:55 -08:00
|
|
|
&Lockup {
|
|
|
|
epoch: 1,
|
2019-12-19 14:37:47 -08:00
|
|
|
unix_timestamp: 0,
|
2019-11-25 15:11:55 -08:00
|
|
|
custodian
|
|
|
|
},
|
2019-12-09 21:56:43 -08:00
|
|
|
&Rent::free(),
|
2019-10-31 11:07:27 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
// check that we see what we expect
|
2019-09-04 13:34:09 -07:00
|
|
|
assert_eq!(
|
2021-06-15 09:04:00 -07:00
|
|
|
from(&stake_keyed_account.account.borrow()).unwrap(),
|
2019-10-31 11:07:27 -07:00
|
|
|
StakeState::Initialized(Meta {
|
2019-11-25 15:11:55 -08:00
|
|
|
lockup: Lockup {
|
2019-12-19 14:37:47 -08:00
|
|
|
unix_timestamp: 0,
|
2019-11-25 15:11:55 -08:00
|
|
|
epoch: 1,
|
|
|
|
custodian
|
|
|
|
},
|
|
|
|
..Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
..Meta::default()
|
|
|
|
}
|
2019-10-31 11:07:27 -07:00
|
|
|
})
|
2019-09-04 13:34:09 -07:00
|
|
|
);
|
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
// 2nd time fails, can't move it from anything other than uninit->init
|
2019-09-04 13:34:09 -07:00
|
|
|
assert_eq!(
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_keyed_account.initialize(
|
|
|
|
&Authorized::default(),
|
|
|
|
&Lockup::default(),
|
2019-12-09 21:56:43 -08:00
|
|
|
&Rent::free()
|
2019-10-31 11:07:27 -07:00
|
|
|
),
|
2019-09-04 13:34:09 -07:00
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-11-23 12:13:38 -08:00
|
|
|
#[test]
|
|
|
|
fn test_initialize_incorrect_account_sizes() {
|
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref(
|
|
|
|
stake_lamports,
|
|
|
|
std::mem::size_of::<StakeState>() + 1,
|
|
|
|
&id(),
|
|
|
|
);
|
2020-11-23 12:13:38 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.initialize(
|
|
|
|
&Authorized::default(),
|
|
|
|
&Lockup::default(),
|
|
|
|
&Rent {
|
|
|
|
lamports_per_byte_year: 42,
|
|
|
|
..Rent::free()
|
|
|
|
},
|
|
|
|
),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref(
|
|
|
|
stake_lamports,
|
|
|
|
std::mem::size_of::<StakeState>() - 1,
|
|
|
|
&id(),
|
|
|
|
);
|
2020-11-23 12:13:38 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.initialize(
|
|
|
|
&Authorized::default(),
|
|
|
|
&Lockup::default(),
|
|
|
|
&Rent {
|
|
|
|
lamports_per_byte_year: 42,
|
|
|
|
..Rent::free()
|
|
|
|
},
|
|
|
|
),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-09-04 13:34:09 -07:00
|
|
|
#[test]
|
2020-01-29 17:59:14 -08:00
|
|
|
fn test_deactivate() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-09-04 13:34:09 -07:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-09-04 13:34:09 -07:00
|
|
|
stake_lamports,
|
2019-10-31 11:07:27 -07:00
|
|
|
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
2019-09-04 13:34:09 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
let clock = Clock {
|
2019-08-01 14:27:47 -07:00
|
|
|
epoch: 1,
|
2020-01-29 17:59:14 -08:00
|
|
|
..Clock::default()
|
2019-07-31 15:13:26 -07:00
|
|
|
};
|
2019-06-21 23:45:03 -07:00
|
|
|
|
|
|
|
// signed keyed account but not staked yet
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-10-14 15:02:24 -07:00
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
2019-06-21 23:45:03 -07:00
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.deactivate(&clock, &signers),
|
2019-06-21 23:45:03 -07:00
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Staking
|
2020-10-19 12:12:08 -07:00
|
|
|
let vote_pubkey = solana_sdk::pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_account = RefCell::new(vote_state::create_account(
|
2020-01-22 09:11:56 -08:00
|
|
|
&vote_pubkey,
|
2020-10-19 12:12:08 -07:00
|
|
|
&solana_sdk::pubkey::new_rand(),
|
2020-01-22 09:11:56 -08:00
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
2020-02-25 17:12:01 -08:00
|
|
|
vote_keyed_account
|
2020-12-21 15:19:04 -08:00
|
|
|
.set_state(&VoteStateVersions::new_current(VoteState::default()))
|
2020-02-25 17:12:01 -08:00
|
|
|
.unwrap();
|
2019-06-21 23:45:03 -07:00
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.delegate(
|
2019-09-12 19:03:28 -07:00
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
2020-01-29 17:59:14 -08:00
|
|
|
&StakeHistory::default(),
|
2019-09-12 19:03:28 -07:00
|
|
|
&Config::default(),
|
2021-05-20 22:31:55 -07:00
|
|
|
&signers,
|
|
|
|
true,
|
2019-09-12 19:03:28 -07:00
|
|
|
),
|
2019-06-21 23:45:03 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
2019-10-14 15:02:24 -07:00
|
|
|
// no signers fails
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
2019-09-12 19:03:28 -07:00
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.deactivate(&clock, &HashSet::default()),
|
2019-09-12 19:03:28 -07:00
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
2019-06-21 23:45:03 -07:00
|
|
|
// Deactivate after staking
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2020-01-29 17:59:14 -08:00
|
|
|
assert_eq!(stake_keyed_account.deactivate(&clock, &signers), Ok(()));
|
2019-10-15 12:50:31 -07:00
|
|
|
|
|
|
|
// verify that deactivate() only works once
|
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.deactivate(&clock, &signers),
|
2019-10-15 12:50:31 -07:00
|
|
|
Err(StakeError::AlreadyDeactivated.into())
|
|
|
|
);
|
2019-06-21 23:45:03 -07:00
|
|
|
}
|
|
|
|
|
2020-01-28 20:59:53 -08:00
|
|
|
#[test]
|
|
|
|
fn test_set_lockup() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2020-01-28 20:59:53 -08:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-01-28 20:59:53 -08:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
// wrong state, should fail
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
|
|
|
assert_eq!(
|
2021-05-20 14:04:07 -07:00
|
|
|
stake_keyed_account.set_lockup(&LockupArgs::default(), &HashSet::default(), None),
|
2020-01-28 20:59:53 -08:00
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
|
|
|
|
// initalize the stake
|
2020-10-19 12:12:08 -07:00
|
|
|
let custodian = solana_sdk::pubkey::new_rand();
|
2020-01-28 20:59:53 -08:00
|
|
|
stake_keyed_account
|
|
|
|
.initialize(
|
|
|
|
&Authorized::auto(&stake_pubkey),
|
|
|
|
&Lockup {
|
|
|
|
unix_timestamp: 1,
|
|
|
|
epoch: 1,
|
|
|
|
custodian,
|
|
|
|
},
|
|
|
|
&Rent::free(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
2021-05-20 14:04:07 -07:00
|
|
|
stake_keyed_account.set_lockup(&LockupArgs::default(), &HashSet::default(), None),
|
2020-01-28 20:59:53 -08:00
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
2020-03-02 12:28:43 -08:00
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: Some(1),
|
|
|
|
epoch: Some(1),
|
|
|
|
custodian: Some(custodian),
|
2020-01-28 20:59:53 -08:00
|
|
|
},
|
2021-05-20 14:04:07 -07:00
|
|
|
&vec![custodian].into_iter().collect(),
|
|
|
|
None
|
2020-01-28 20:59:53 -08:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// delegate stake
|
2020-10-19 12:12:08 -07:00
|
|
|
let vote_pubkey = solana_sdk::pubkey::new_rand();
|
2020-01-28 20:59:53 -08:00
|
|
|
let vote_account = RefCell::new(vote_state::create_account(
|
|
|
|
&vote_pubkey,
|
2020-10-19 12:12:08 -07:00
|
|
|
&solana_sdk::pubkey::new_rand(),
|
2020-01-28 20:59:53 -08:00
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
2020-02-25 17:12:01 -08:00
|
|
|
vote_keyed_account
|
2020-12-21 15:19:04 -08:00
|
|
|
.set_state(&VoteStateVersions::new_current(VoteState::default()))
|
2020-02-25 17:12:01 -08:00
|
|
|
.unwrap();
|
2020-01-28 20:59:53 -08:00
|
|
|
|
|
|
|
stake_keyed_account
|
2020-01-29 17:59:14 -08:00
|
|
|
.delegate(
|
2020-01-28 20:59:53 -08:00
|
|
|
&vote_keyed_account,
|
|
|
|
&Clock::default(),
|
2020-01-29 17:59:14 -08:00
|
|
|
&StakeHistory::default(),
|
2020-01-28 20:59:53 -08:00
|
|
|
&Config::default(),
|
|
|
|
&vec![stake_pubkey].into_iter().collect(),
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2020-01-28 20:59:53 -08:00
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
2020-03-02 12:28:43 -08:00
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: Some(1),
|
|
|
|
epoch: Some(1),
|
|
|
|
custodian: Some(custodian),
|
2020-01-28 20:59:53 -08:00
|
|
|
},
|
|
|
|
&HashSet::default(),
|
2021-05-20 14:04:07 -07:00
|
|
|
None
|
2020-01-28 20:59:53 -08:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
2020-03-02 12:28:43 -08:00
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: Some(1),
|
|
|
|
epoch: Some(1),
|
|
|
|
custodian: Some(custodian),
|
|
|
|
},
|
2021-05-20 14:04:07 -07:00
|
|
|
&vec![custodian].into_iter().collect(),
|
|
|
|
None
|
2020-03-02 12:28:43 -08:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-05-20 14:04:07 -07:00
|
|
|
fn test_optional_lockup_for_stake_program_v3_and_earlier() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2020-03-02 12:28:43 -08:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-03-02 12:28:43 -08:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let custodian = solana_sdk::pubkey::new_rand();
|
2020-03-02 12:28:43 -08:00
|
|
|
stake_keyed_account
|
|
|
|
.initialize(
|
|
|
|
&Authorized::auto(&stake_pubkey),
|
2020-01-28 20:59:53 -08:00
|
|
|
&Lockup {
|
|
|
|
unix_timestamp: 1,
|
|
|
|
epoch: 1,
|
|
|
|
custodian,
|
|
|
|
},
|
2020-03-02 12:28:43 -08:00
|
|
|
&Rent::free(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: None,
|
|
|
|
epoch: None,
|
|
|
|
custodian: None,
|
|
|
|
},
|
2021-05-20 14:04:07 -07:00
|
|
|
&vec![custodian].into_iter().collect(),
|
|
|
|
None
|
2020-03-02 12:28:43 -08:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: Some(2),
|
|
|
|
epoch: None,
|
|
|
|
custodian: None,
|
|
|
|
},
|
2021-05-20 14:04:07 -07:00
|
|
|
&vec![custodian].into_iter().collect(),
|
|
|
|
None
|
2020-03-02 12:28:43 -08:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
if let StakeState::Initialized(Meta { lockup, .. }) =
|
2021-06-15 09:04:00 -07:00
|
|
|
from(&stake_keyed_account.account.borrow()).unwrap()
|
2020-03-02 12:28:43 -08:00
|
|
|
{
|
|
|
|
assert_eq!(lockup.unix_timestamp, 2);
|
|
|
|
assert_eq!(lockup.epoch, 1);
|
|
|
|
assert_eq!(lockup.custodian, custodian);
|
|
|
|
} else {
|
2020-05-15 09:35:43 -07:00
|
|
|
panic!();
|
2020-03-02 12:28:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: None,
|
|
|
|
epoch: Some(3),
|
|
|
|
custodian: None,
|
|
|
|
},
|
2021-05-20 14:04:07 -07:00
|
|
|
&vec![custodian].into_iter().collect(),
|
|
|
|
None
|
2020-03-02 12:28:43 -08:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
if let StakeState::Initialized(Meta { lockup, .. }) =
|
2021-06-15 09:04:00 -07:00
|
|
|
from(&stake_keyed_account.account.borrow()).unwrap()
|
2020-03-02 12:28:43 -08:00
|
|
|
{
|
|
|
|
assert_eq!(lockup.unix_timestamp, 2);
|
|
|
|
assert_eq!(lockup.epoch, 3);
|
|
|
|
assert_eq!(lockup.custodian, custodian);
|
|
|
|
} else {
|
2020-05-15 09:35:43 -07:00
|
|
|
panic!();
|
2020-03-02 12:28:43 -08:00
|
|
|
}
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let new_custodian = solana_sdk::pubkey::new_rand();
|
2020-03-02 12:28:43 -08:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: None,
|
|
|
|
epoch: None,
|
|
|
|
custodian: Some(new_custodian),
|
|
|
|
},
|
2021-05-20 14:04:07 -07:00
|
|
|
&vec![custodian].into_iter().collect(),
|
|
|
|
None
|
2020-01-28 20:59:53 -08:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
2020-03-02 12:28:43 -08:00
|
|
|
|
|
|
|
if let StakeState::Initialized(Meta { lockup, .. }) =
|
2021-06-15 09:04:00 -07:00
|
|
|
from(&stake_keyed_account.account.borrow()).unwrap()
|
2020-03-02 12:28:43 -08:00
|
|
|
{
|
|
|
|
assert_eq!(lockup.unix_timestamp, 2);
|
|
|
|
assert_eq!(lockup.epoch, 3);
|
|
|
|
assert_eq!(lockup.custodian, new_custodian);
|
|
|
|
} else {
|
2020-05-15 09:35:43 -07:00
|
|
|
panic!();
|
2020-03-02 12:28:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs::default(),
|
2021-05-20 14:04:07 -07:00
|
|
|
&vec![custodian].into_iter().collect(),
|
|
|
|
None
|
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_optional_lockup_for_stake_program_v4() {
|
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
|
|
|
|
|
|
|
let custodian = solana_sdk::pubkey::new_rand();
|
|
|
|
stake_keyed_account
|
|
|
|
.initialize(
|
|
|
|
&Authorized::auto(&stake_pubkey),
|
|
|
|
&Lockup {
|
|
|
|
unix_timestamp: 1,
|
|
|
|
epoch: 1,
|
|
|
|
custodian,
|
|
|
|
},
|
|
|
|
&Rent::free(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// Lockup in force: authorized withdrawer cannot change it
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: Some(2),
|
|
|
|
epoch: None,
|
|
|
|
custodian: None
|
|
|
|
},
|
|
|
|
&vec![stake_pubkey].into_iter().collect(),
|
|
|
|
Some(&Clock::default())
|
2020-03-02 12:28:43 -08:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
2021-05-20 14:04:07 -07:00
|
|
|
|
|
|
|
// Lockup in force: custodian can change it
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: Some(2),
|
|
|
|
epoch: None,
|
|
|
|
custodian: None
|
|
|
|
},
|
|
|
|
&vec![custodian].into_iter().collect(),
|
|
|
|
Some(&Clock::default())
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// Lockup expired: custodian cannot change it
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: Some(3),
|
|
|
|
epoch: None,
|
|
|
|
custodian: None,
|
|
|
|
},
|
|
|
|
&vec![custodian].into_iter().collect(),
|
|
|
|
Some(&Clock {
|
|
|
|
unix_timestamp: UnixTimestamp::MAX,
|
|
|
|
epoch: Epoch::MAX,
|
|
|
|
..Clock::default()
|
|
|
|
})
|
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Lockup expired: authorized withdrawer can change it
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: Some(3),
|
|
|
|
epoch: None,
|
|
|
|
custodian: None,
|
|
|
|
},
|
|
|
|
&vec![stake_pubkey].into_iter().collect(),
|
|
|
|
Some(&Clock {
|
|
|
|
unix_timestamp: UnixTimestamp::MAX,
|
|
|
|
epoch: Epoch::MAX,
|
|
|
|
..Clock::default()
|
|
|
|
})
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
2020-01-28 20:59:53 -08:00
|
|
|
}
|
|
|
|
|
2019-06-21 22:28:34 -07:00
|
|
|
#[test]
|
|
|
|
fn test_withdraw_stake() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-06-21 22:28:34 -07:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake_lamports,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeState::Uninitialized,
|
2019-09-04 13:34:09 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
let mut clock = Clock::default();
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let to = solana_sdk::pubkey::new_rand();
|
2021-03-09 13:06:07 -08:00
|
|
|
let to_account = AccountSharedData::new_ref(1, 0, &system_program::id());
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2019-10-14 15:02:24 -07:00
|
|
|
// no signers, should fail
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake_lamports,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&to_keyed_account, // unsigned account as withdraw authority
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-06-21 22:28:34 -07:00
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
2019-08-07 20:29:22 -07:00
|
|
|
// signed keyed account and uninitialized should work
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake_lamports,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-08-07 20:29:22 -07:00
|
|
|
Ok(())
|
2019-06-21 22:28:34 -07:00
|
|
|
);
|
2021-05-03 08:45:54 -07:00
|
|
|
assert_eq!(stake_account.borrow().lamports(), 0);
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2019-08-07 20:29:22 -07:00
|
|
|
// reset balance
|
2021-04-30 11:44:15 -07:00
|
|
|
stake_account.borrow_mut().set_lamports(stake_lamports);
|
2019-08-07 20:29:22 -07:00
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
// lockup
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2020-10-19 12:12:08 -07:00
|
|
|
let custodian = solana_sdk::pubkey::new_rand();
|
2019-09-26 13:29:29 -07:00
|
|
|
stake_keyed_account
|
|
|
|
.initialize(
|
|
|
|
&Authorized::auto(&stake_pubkey),
|
2019-11-25 15:11:55 -08:00
|
|
|
&Lockup {
|
2019-12-19 14:37:47 -08:00
|
|
|
unix_timestamp: 0,
|
2019-11-25 15:11:55 -08:00
|
|
|
epoch: 0,
|
|
|
|
custodian,
|
|
|
|
},
|
2019-12-09 21:56:43 -08:00
|
|
|
&Rent::free(),
|
2019-09-26 13:29:29 -07:00
|
|
|
)
|
|
|
|
.unwrap();
|
2019-09-12 19:03:28 -07:00
|
|
|
|
|
|
|
// signed keyed account and locked up, more than available should fail
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake_lamports + 1,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-08-07 20:29:22 -07:00
|
|
|
Err(InstructionError::InsufficientFunds)
|
2019-06-21 22:28:34 -07:00
|
|
|
);
|
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
// Stake some lamports (available lamports for withdrawals will reduce to zero)
|
2020-10-19 12:12:08 -07:00
|
|
|
let vote_pubkey = solana_sdk::pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_account = RefCell::new(vote_state::create_account(
|
2020-01-22 09:11:56 -08:00
|
|
|
&vote_pubkey,
|
2020-10-19 12:12:08 -07:00
|
|
|
&solana_sdk::pubkey::new_rand(),
|
2020-01-22 09:11:56 -08:00
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
2020-02-25 17:12:01 -08:00
|
|
|
vote_keyed_account
|
2020-12-21 15:19:04 -08:00
|
|
|
.set_state(&VoteStateVersions::new_current(VoteState::default()))
|
2020-02-25 17:12:01 -08:00
|
|
|
.unwrap();
|
2020-07-31 12:37:53 -07:00
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.delegate(
|
2019-09-12 19:03:28 -07:00
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
2020-01-29 17:59:14 -08:00
|
|
|
&StakeHistory::default(),
|
2019-09-12 19:03:28 -07:00
|
|
|
&Config::default(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2019-09-12 19:03:28 -07:00
|
|
|
),
|
2019-06-21 22:28:34 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
// simulate rewards
|
2021-04-27 07:11:35 -07:00
|
|
|
stake_account.borrow_mut().checked_add_lamports(10).unwrap();
|
2019-09-11 09:48:29 -07:00
|
|
|
// withdrawal before deactivate works for rewards amount
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
10,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-06-21 22:28:34 -07:00
|
|
|
),
|
2019-08-07 20:29:22 -07:00
|
|
|
Ok(())
|
2019-06-21 22:28:34 -07:00
|
|
|
);
|
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
// simulate rewards
|
2021-04-27 07:11:35 -07:00
|
|
|
stake_account.borrow_mut().checked_add_lamports(10).unwrap();
|
2019-09-11 09:48:29 -07:00
|
|
|
// withdrawal of rewards fails if not in excess of stake
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
10 + 1,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-06-21 22:28:34 -07:00
|
|
|
),
|
2019-08-07 20:29:22 -07:00
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// deactivate the stake before withdrawal
|
2020-01-29 17:59:14 -08:00
|
|
|
assert_eq!(stake_keyed_account.deactivate(&clock, &signers), Ok(()));
|
2019-08-07 20:29:22 -07:00
|
|
|
// simulate time passing
|
2019-08-12 20:59:57 -07:00
|
|
|
clock.epoch += 100;
|
2019-08-07 20:29:22 -07:00
|
|
|
|
|
|
|
// Try to withdraw more than what's available
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-08-07 20:29:22 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake_lamports + 10 + 1,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-08-07 20:29:22 -07:00
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Try to withdraw all lamports
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-08-07 20:29:22 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake_lamports + 10,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-06-21 22:28:34 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2021-04-30 14:03:08 -07:00
|
|
|
assert_eq!(stake_account.borrow().lamports(), 0);
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
|
2021-02-12 16:35:44 -08:00
|
|
|
|
|
|
|
// overflow
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let authority_pubkey = Pubkey::new_unique();
|
|
|
|
let stake_pubkey = Pubkey::new_unique();
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2021-02-12 16:35:44 -08:00
|
|
|
1_000_000_000,
|
|
|
|
&StakeState::Initialized(Meta {
|
|
|
|
rent_exempt_reserve,
|
|
|
|
authorized: Authorized {
|
|
|
|
staker: authority_pubkey,
|
|
|
|
withdrawer: authority_pubkey,
|
|
|
|
},
|
|
|
|
lockup: Lockup::default(),
|
|
|
|
}),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let stake2_pubkey = Pubkey::new_unique();
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake2_account = AccountSharedData::new_ref_data_with_space(
|
2021-02-12 16:35:44 -08:00
|
|
|
1_000_000_000,
|
|
|
|
&StakeState::Initialized(Meta {
|
|
|
|
rent_exempt_reserve,
|
|
|
|
authorized: Authorized {
|
|
|
|
staker: authority_pubkey,
|
|
|
|
withdrawer: authority_pubkey,
|
|
|
|
},
|
|
|
|
lockup: Lockup::default(),
|
|
|
|
}),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake2_account");
|
|
|
|
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
let stake2_keyed_account = KeyedAccount::new(&stake2_pubkey, false, &stake2_account);
|
2021-03-09 13:06:07 -08:00
|
|
|
let authority_account = AccountSharedData::new_ref(42, 0, &system_program::id());
|
2021-02-12 16:35:44 -08:00
|
|
|
let authority_keyed_account =
|
|
|
|
KeyedAccount::new(&authority_pubkey, true, &authority_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
u64::MAX - 10,
|
|
|
|
&stake2_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&authority_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2021-02-12 16:35:44 -08:00
|
|
|
),
|
|
|
|
Err(InstructionError::InsufficientFunds),
|
|
|
|
);
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_withdraw_stake_before_warmup() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-06-21 22:28:34 -07:00
|
|
|
let total_lamports = 100;
|
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-09-04 13:34:09 -07:00
|
|
|
total_lamports,
|
2019-10-31 11:07:27 -07:00
|
|
|
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
2019-09-04 13:34:09 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
let clock = Clock::default();
|
|
|
|
let mut future = Clock::default();
|
2019-08-01 14:27:47 -07:00
|
|
|
future.epoch += 16;
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let to = solana_sdk::pubkey::new_rand();
|
2021-03-09 13:06:07 -08:00
|
|
|
let to_account = AccountSharedData::new_ref(1, 0, &system_program::id());
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2019-11-18 16:47:01 -08:00
|
|
|
// Stake some lamports (available lamports for withdrawals will reduce)
|
2020-10-19 12:12:08 -07:00
|
|
|
let vote_pubkey = solana_sdk::pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_account = RefCell::new(vote_state::create_account(
|
2020-01-22 09:11:56 -08:00
|
|
|
&vote_pubkey,
|
2020-10-19 12:12:08 -07:00
|
|
|
&solana_sdk::pubkey::new_rand(),
|
2020-01-22 09:11:56 -08:00
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
2020-02-25 17:12:01 -08:00
|
|
|
vote_keyed_account
|
2020-12-21 15:19:04 -08:00
|
|
|
.set_state(&VoteStateVersions::new_current(VoteState::default()))
|
2020-02-25 17:12:01 -08:00
|
|
|
.unwrap();
|
2019-10-14 15:02:24 -07:00
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.delegate(
|
2019-09-12 19:03:28 -07:00
|
|
|
&vote_keyed_account,
|
|
|
|
&future,
|
2020-01-29 17:59:14 -08:00
|
|
|
&StakeHistory::default(),
|
2019-09-12 19:03:28 -07:00
|
|
|
&Config::default(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2019-09-12 19:03:28 -07:00
|
|
|
),
|
2019-06-21 22:28:34 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
2019-11-25 13:14:32 -08:00
|
|
|
let stake_history = create_stake_history_from_delegations(
|
2019-08-12 20:59:57 -07:00
|
|
|
None,
|
|
|
|
0..future.epoch,
|
2021-06-15 09:04:00 -07:00
|
|
|
&[stake_from(&stake_keyed_account.account.borrow())
|
|
|
|
.unwrap()
|
|
|
|
.delegation],
|
2019-08-12 20:59:57 -07:00
|
|
|
);
|
|
|
|
|
2019-07-31 15:13:26 -07:00
|
|
|
// Try to withdraw stake
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports - stake_lamports + 1,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&stake_history,
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-06-21 22:28:34 -07:00
|
|
|
),
|
2019-07-31 15:13:26 -07:00
|
|
|
Err(InstructionError::InsufficientFunds)
|
2019-06-21 22:28:34 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_withdraw_stake_invalid_state() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-06-21 22:28:34 -07:00
|
|
|
let total_lamports = 100;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-09-04 13:34:09 -07:00
|
|
|
total_lamports,
|
|
|
|
&StakeState::RewardsPool,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
2019-10-03 16:24:50 -07:00
|
|
|
.expect("stake_account");
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let to = solana_sdk::pubkey::new_rand();
|
2021-03-09 13:06:07 -08:00
|
|
|
let to_account = AccountSharedData::new_ref(1, 0, &system_program::id());
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-09-04 13:34:09 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2020-01-29 17:59:14 -08:00
|
|
|
&Clock::default(),
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-09-04 13:34:09 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-09-26 13:29:29 -07:00
|
|
|
fn test_withdraw_lockup() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let custodian = solana_sdk::pubkey::new_rand();
|
2019-09-04 13:34:09 -07:00
|
|
|
let total_lamports = 100;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-09-04 13:34:09 -07:00
|
|
|
total_lamports,
|
2019-10-31 11:07:27 -07:00
|
|
|
&StakeState::Initialized(Meta {
|
2019-11-25 15:11:55 -08:00
|
|
|
lockup: Lockup {
|
2019-12-19 14:37:47 -08:00
|
|
|
unix_timestamp: 0,
|
2019-11-25 15:11:55 -08:00
|
|
|
epoch: 1,
|
|
|
|
custodian,
|
|
|
|
},
|
2019-10-31 11:07:27 -07:00
|
|
|
..Meta::auto(&stake_pubkey)
|
|
|
|
}),
|
2019-09-04 13:34:09 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let to = solana_sdk::pubkey::new_rand();
|
2021-03-09 13:06:07 -08:00
|
|
|
let to_account = AccountSharedData::new_ref(1, 0, &system_program::id());
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
let mut clock = Clock::default();
|
2019-10-14 15:02:24 -07:00
|
|
|
|
2019-09-16 17:47:42 -07:00
|
|
|
// lockup is still in force, can't withdraw
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-09-16 17:47:42 -07:00
|
|
|
Err(StakeError::LockupInForce.into())
|
|
|
|
);
|
|
|
|
|
2019-12-04 21:25:01 -08:00
|
|
|
{
|
2021-03-09 13:06:07 -08:00
|
|
|
let custodian_account = AccountSharedData::new_ref(1, 0, &system_program::id());
|
2020-07-31 12:37:53 -07:00
|
|
|
let custodian_keyed_account = KeyedAccount::new(&custodian, true, &custodian_account);
|
2019-12-04 21:25:01 -08:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-12-04 21:25:01 -08:00
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
Some(&custodian_keyed_account),
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-12-04 21:25:01 -08:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
2019-09-16 17:47:42 -07:00
|
|
|
// reset balance
|
2021-04-30 11:20:54 -07:00
|
|
|
stake_keyed_account
|
|
|
|
.account
|
|
|
|
.borrow_mut()
|
|
|
|
.set_lamports(total_lamports);
|
2019-09-04 13:34:09 -07:00
|
|
|
|
2019-09-16 17:47:42 -07:00
|
|
|
// lockup has expired
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-11-25 15:11:55 -08:00
|
|
|
clock.epoch += 1;
|
2019-09-04 13:34:09 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-09-04 13:34:09 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-09-04 13:34:09 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
2019-06-21 22:28:34 -07:00
|
|
|
);
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
|
|
|
|
2020-07-31 12:37:53 -07:00
|
|
|
#[test]
|
|
|
|
fn test_withdraw_identical_authorities() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2020-07-31 12:37:53 -07:00
|
|
|
let custodian = stake_pubkey;
|
|
|
|
let total_lamports = 100;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-07-31 12:37:53 -07:00
|
|
|
total_lamports,
|
|
|
|
&StakeState::Initialized(Meta {
|
|
|
|
lockup: Lockup {
|
|
|
|
unix_timestamp: 0,
|
|
|
|
epoch: 1,
|
|
|
|
custodian,
|
|
|
|
},
|
|
|
|
..Meta::auto(&stake_pubkey)
|
|
|
|
}),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let to = solana_sdk::pubkey::new_rand();
|
2021-03-09 13:06:07 -08:00
|
|
|
let to_account = AccountSharedData::new_ref(1, 0, &system_program::id());
|
2020-07-31 12:37:53 -07:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
|
|
|
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
|
|
|
let clock = Clock::default();
|
|
|
|
|
|
|
|
// lockup is still in force, even though custodian is the same as the withdraw authority
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports,
|
|
|
|
&to_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2020-07-31 12:37:53 -07:00
|
|
|
),
|
|
|
|
Err(StakeError::LockupInForce.into())
|
|
|
|
);
|
|
|
|
|
|
|
|
{
|
|
|
|
let custodian_keyed_account = KeyedAccount::new(&custodian, true, &stake_account);
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports,
|
|
|
|
&to_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&stake_keyed_account,
|
|
|
|
Some(&custodian_keyed_account),
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2020-07-31 12:37:53 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
|
2020-07-31 12:37:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-20 19:01:08 -07:00
|
|
|
#[test]
|
|
|
|
fn test_withdraw_rent_exempt() {
|
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let clock = Clock::default();
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let stake = 42;
|
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
|
|
|
stake + rent_exempt_reserve,
|
|
|
|
&StakeState::Initialized(Meta {
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::auto(&stake_pubkey)
|
|
|
|
}),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let to = solana_sdk::pubkey::new_rand();
|
|
|
|
let to_account = AccountSharedData::new_ref(1, 0, &system_program::id());
|
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
|
|
|
|
|
|
|
// Withdrawing account down to only rent-exempt reserve should succeed before feature, and
|
|
|
|
// fail after
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
stake,
|
|
|
|
&to_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
|
|
|
false,
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
stake_account
|
|
|
|
.borrow_mut()
|
|
|
|
.checked_add_lamports(stake)
|
|
|
|
.unwrap(); // top up account
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
stake,
|
|
|
|
&to_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Withdrawal that would leave less than rent-exempt reserve should fail
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
stake + 1,
|
|
|
|
&to_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
|
|
|
false,
|
|
|
|
),
|
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
stake + 1,
|
|
|
|
&to_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Withdrawal of complete account should succeed
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
stake + rent_exempt_reserve,
|
|
|
|
&to_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-01-22 12:21:31 -08:00
|
|
|
#[test]
|
|
|
|
fn test_stake_state_redeem_rewards() {
|
|
|
|
let mut vote_state = VoteState::default();
|
|
|
|
// assume stake.stake() is right
|
|
|
|
// bootstrap means fully-vested stake at epoch 0
|
|
|
|
let stake_lamports = 1;
|
2021-06-15 09:04:00 -07:00
|
|
|
let mut stake = new_stake(
|
2020-01-22 12:21:31 -08:00
|
|
|
stake_lamports,
|
|
|
|
&Pubkey::default(),
|
|
|
|
&vote_state,
|
|
|
|
std::u64::MAX,
|
|
|
|
&Config::default(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// this one can't collect now, credits_observed == vote_state.credits()
|
|
|
|
assert_eq!(
|
|
|
|
None,
|
2021-06-15 09:04:00 -07:00
|
|
|
redeem_stake_rewards(
|
|
|
|
&mut stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 1_000_000_000,
|
|
|
|
points: 1
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2020-01-22 12:21:31 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
// put 2 credits in at epoch 0
|
|
|
|
vote_state.increment_credits(0);
|
|
|
|
vote_state.increment_credits(0);
|
|
|
|
|
|
|
|
// this one should be able to collect exactly 2
|
|
|
|
assert_eq!(
|
2020-07-20 21:57:25 -07:00
|
|
|
Some((stake_lamports * 2, 0)),
|
2021-06-15 09:04:00 -07:00
|
|
|
redeem_stake_rewards(
|
|
|
|
&mut stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 1,
|
|
|
|
points: 1
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2020-01-22 12:21:31 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake.delegation.stake,
|
|
|
|
stake_lamports + (stake_lamports * 2)
|
|
|
|
);
|
|
|
|
assert_eq!(stake.credits_observed, 2);
|
|
|
|
}
|
|
|
|
|
2020-07-20 21:57:25 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stake_state_calculate_points_with_typical_values() {
|
|
|
|
let mut vote_state = VoteState::default();
|
|
|
|
|
|
|
|
// bootstrap means fully-vested stake at epoch 0 with
|
|
|
|
// 10_000_000 SOL is a big but not unreasaonable stake
|
2021-06-15 09:04:00 -07:00
|
|
|
let stake = new_stake(
|
2020-07-20 21:57:25 -07:00
|
|
|
native_token::sol_to_lamports(10_000_000f64),
|
|
|
|
&Pubkey::default(),
|
|
|
|
&vote_state,
|
|
|
|
std::u64::MAX,
|
|
|
|
&Config::default(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// this one can't collect now, credits_observed == vote_state.credits()
|
|
|
|
assert_eq!(
|
|
|
|
None,
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 1_000_000_000,
|
|
|
|
points: 1
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
let epoch_slots: u128 = 14 * 24 * 3600 * 160;
|
|
|
|
// put 193,536,000 credits in at epoch 0, typical for a 14-day epoch
|
|
|
|
// this loop takes a few seconds...
|
|
|
|
for _ in 0..epoch_slots {
|
|
|
|
vote_state.increment_credits(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// no overflow on points
|
|
|
|
assert_eq!(
|
|
|
|
u128::from(stake.delegation.stake) * epoch_slots,
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_points(&stake, &vote_state, None, &mut null_tracer(), true)
|
2020-07-20 21:57:25 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-04-07 21:45:28 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stake_state_calculate_rewards() {
|
|
|
|
let mut vote_state = VoteState::default();
|
2019-08-12 20:59:57 -07:00
|
|
|
// assume stake.stake() is right
|
|
|
|
// bootstrap means fully-vested stake at epoch 0
|
2021-06-15 09:04:00 -07:00
|
|
|
let mut stake = new_stake(
|
2019-11-12 12:33:40 -08:00
|
|
|
1,
|
|
|
|
&Pubkey::default(),
|
|
|
|
&vote_state,
|
|
|
|
std::u64::MAX,
|
|
|
|
&Config::default(),
|
|
|
|
);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
// this one can't collect now, credits_observed == vote_state.credits()
|
2019-08-12 20:59:57 -07:00
|
|
|
assert_eq!(
|
|
|
|
None,
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 1_000_000_000,
|
|
|
|
points: 1
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2019-08-12 20:59:57 -07:00
|
|
|
);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
// put 2 credits in at epoch 0
|
|
|
|
vote_state.increment_credits(0);
|
|
|
|
vote_state.increment_credits(0);
|
|
|
|
|
|
|
|
// this one should be able to collect exactly 2
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2020-07-20 21:57:25 -07:00
|
|
|
Some((stake.delegation.stake * 2, 0, 2)),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 2,
|
|
|
|
points: 2 // all his
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
stake.credits_observed = 1;
|
2020-01-21 19:08:40 -08:00
|
|
|
// this one should be able to collect exactly 1 (already observed one)
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2020-07-20 21:57:25 -07:00
|
|
|
Some((stake.delegation.stake, 0, 2)),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 1,
|
|
|
|
points: 1
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
|
|
|
|
2020-01-21 19:08:40 -08:00
|
|
|
// put 1 credit in epoch 1
|
|
|
|
vote_state.increment_credits(1);
|
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
stake.credits_observed = 2;
|
2020-01-21 19:08:40 -08:00
|
|
|
// this one should be able to collect the one just added
|
|
|
|
assert_eq!(
|
2020-07-20 21:57:25 -07:00
|
|
|
Some((stake.delegation.stake, 0, 3)),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 2,
|
|
|
|
points: 2
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2020-01-21 19:08:40 -08:00
|
|
|
);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
2020-01-21 19:08:40 -08:00
|
|
|
// put 1 credit in epoch 2
|
2019-06-21 20:43:24 -07:00
|
|
|
vote_state.increment_credits(2);
|
2020-01-21 19:08:40 -08:00
|
|
|
// this one should be able to collect 2 now
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2020-07-20 21:57:25 -07:00
|
|
|
Some((stake.delegation.stake * 2, 0, 4)),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 2,
|
|
|
|
points: 2
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
stake.credits_observed = 0;
|
|
|
|
// this one should be able to collect everything from t=0 a warmed up stake of 2
|
|
|
|
// (2 credits at stake of 1) + (1 credit at a stake of 2)
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2019-11-25 13:14:32 -08:00
|
|
|
Some((
|
2020-01-21 19:08:40 -08:00
|
|
|
stake.delegation.stake * 2 // epoch 0
|
2020-05-15 09:35:43 -07:00
|
|
|
+ stake.delegation.stake // epoch 1
|
|
|
|
+ stake.delegation.stake, // epoch 2
|
2020-07-20 21:57:25 -07:00
|
|
|
0,
|
2020-01-21 19:08:40 -08:00
|
|
|
4
|
2019-11-25 13:14:32 -08:00
|
|
|
)),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 4,
|
|
|
|
points: 4
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
// same as above, but is a really small commission out of 32 bits,
|
|
|
|
// verify that None comes back on small redemptions where no one gets paid
|
|
|
|
vote_state.commission = 1;
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2020-01-21 19:08:40 -08:00
|
|
|
None, // would be Some((0, 2 * 1 + 1 * 2, 4)),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 4,
|
|
|
|
points: 4
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2019-06-21 20:43:24 -07:00
|
|
|
);
|
2019-12-03 20:55:04 -08:00
|
|
|
vote_state.commission = 99;
|
2019-06-21 20:43:24 -07:00
|
|
|
assert_eq!(
|
2020-01-21 19:08:40 -08:00
|
|
|
None, // would be Some((0, 2 * 1 + 1 * 2, 4)),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-07-20 21:57:25 -07:00
|
|
|
&PointValue {
|
|
|
|
rewards: 4,
|
|
|
|
points: 4
|
|
|
|
},
|
|
|
|
&vote_state,
|
2020-11-07 23:43:50 -08:00
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
2020-11-11 13:11:57 -08:00
|
|
|
true,
|
2020-07-20 21:57:25 -07:00
|
|
|
)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
2020-11-18 17:34:51 -08:00
|
|
|
|
|
|
|
// now one with inflation disabled. no one gets paid, but we still need
|
2020-11-30 05:47:34 -08:00
|
|
|
// to advance the stake state's credits_observed field to prevent back-
|
2020-11-18 17:34:51 -08:00
|
|
|
// paying rewards when inflation is turned on.
|
|
|
|
assert_eq!(
|
|
|
|
Some((0, 0, 4)),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-11-18 17:34:51 -08:00
|
|
|
&PointValue {
|
|
|
|
rewards: 0,
|
|
|
|
points: 4
|
|
|
|
},
|
|
|
|
&vote_state,
|
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
);
|
2020-11-30 05:47:34 -08:00
|
|
|
|
|
|
|
// credits_observed remains at previous level when vote_state credits are
|
|
|
|
// not advancing and inflation is disabled
|
|
|
|
stake.credits_observed = 4;
|
|
|
|
assert_eq!(
|
|
|
|
Some((0, 0, 4)),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_rewards(
|
|
|
|
&stake,
|
2020-11-30 05:47:34 -08:00
|
|
|
&PointValue {
|
|
|
|
rewards: 0,
|
|
|
|
points: 4
|
|
|
|
},
|
|
|
|
&vote_state,
|
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// assert the previous behavior is preserved where fix_stake_deactivate=false
|
|
|
|
assert_eq!(
|
|
|
|
(0, 0),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_points_and_credits(
|
|
|
|
&stake,
|
|
|
|
&vote_state,
|
|
|
|
None,
|
|
|
|
&mut null_tracer(),
|
|
|
|
false
|
|
|
|
)
|
2020-11-30 05:47:34 -08:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
(0, 4),
|
2021-06-15 09:04:00 -07:00
|
|
|
calculate_stake_points_and_credits(&stake, &vote_state, None, &mut null_tracer(), true)
|
2020-11-30 05:47:34 -08:00
|
|
|
);
|
2019-04-07 21:45:28 -07:00
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
#[test]
|
|
|
|
fn test_authorize_uninit() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let new_authority = solana_sdk::pubkey::new_rand();
|
2019-10-31 11:07:27 -07:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::default(),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-08-21 11:28:01 -07:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&new_authority, true, &stake_account);
|
|
|
|
let signers = vec![new_authority].into_iter().collect();
|
2019-10-31 11:07:27 -07:00
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&new_authority,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2019-10-31 11:07:27 -07:00
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
#[test]
|
|
|
|
fn test_authorize_lockup() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_authority = solana_sdk::pubkey::new_rand();
|
2019-09-12 19:03:28 -07:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_lamports,
|
2020-08-21 11:28:01 -07:00
|
|
|
&StakeState::Initialized(Meta::auto(&stake_authority)),
|
2019-09-12 19:03:28 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let to = solana_sdk::pubkey::new_rand();
|
2021-03-09 13:06:07 -08:00
|
|
|
let to_account = AccountSharedData::new_ref(1, 0, &system_program::id());
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-09-12 19:03:28 -07:00
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
let clock = Clock::default();
|
2020-08-21 11:28:01 -07:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_authority, true, &stake_account);
|
2019-09-12 19:03:28 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey0 = solana_sdk::pubkey::new_rand();
|
2020-08-21 11:28:01 -07:00
|
|
|
let signers = vec![stake_authority].into_iter().collect();
|
2019-09-26 13:29:29 -07:00
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&stake_pubkey0,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2019-09-26 13:29:29 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&stake_pubkey0,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2019-09-26 13:29:29 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2019-10-31 11:07:27 -07:00
|
|
|
if let StakeState::Initialized(Meta { authorized, .. }) =
|
2021-06-15 09:04:00 -07:00
|
|
|
from(&stake_keyed_account.account.borrow()).unwrap()
|
2019-09-12 19:03:28 -07:00
|
|
|
{
|
2019-09-26 13:29:29 -07:00
|
|
|
assert_eq!(authorized.staker, stake_pubkey0);
|
|
|
|
assert_eq!(authorized.withdrawer, stake_pubkey0);
|
|
|
|
} else {
|
2020-05-15 09:35:43 -07:00
|
|
|
panic!();
|
2019-09-12 19:03:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// A second authorization signed by the stake_keyed_account should fail
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey1 = solana_sdk::pubkey::new_rand();
|
2019-09-12 19:03:28 -07:00
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&stake_pubkey1,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2019-09-12 19:03:28 -07:00
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
2019-10-14 15:02:24 -07:00
|
|
|
let signers0 = vec![stake_pubkey0].into_iter().collect();
|
2019-09-12 19:03:28 -07:00
|
|
|
|
|
|
|
// Test a second authorization by the newly authorized pubkey
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey2 = solana_sdk::pubkey::new_rand();
|
2019-09-12 19:03:28 -07:00
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers0,
|
|
|
|
&stake_pubkey2,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2019-09-12 19:03:28 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2019-10-31 11:07:27 -07:00
|
|
|
if let StakeState::Initialized(Meta { authorized, .. }) =
|
2021-06-15 09:04:00 -07:00
|
|
|
from(&stake_keyed_account.account.borrow()).unwrap()
|
2019-09-12 19:03:28 -07:00
|
|
|
{
|
2019-09-26 13:29:29 -07:00
|
|
|
assert_eq!(authorized.staker, stake_pubkey2);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers0,
|
|
|
|
&stake_pubkey2,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2019-09-26 13:29:29 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2019-10-31 11:07:27 -07:00
|
|
|
if let StakeState::Initialized(Meta { authorized, .. }) =
|
2021-06-15 09:04:00 -07:00
|
|
|
from(&stake_keyed_account.account.borrow()).unwrap()
|
2019-09-26 13:29:29 -07:00
|
|
|
{
|
|
|
|
assert_eq!(authorized.staker, stake_pubkey2);
|
2019-09-12 19:03:28 -07:00
|
|
|
}
|
|
|
|
|
2019-10-10 14:46:38 -07:00
|
|
|
// Test that withdrawal to account fails without authorized withdrawer
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
stake_lamports,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-10-10 14:46:38 -07:00
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account, // old signer
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-10-10 14:46:38 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
2020-07-31 12:37:53 -07:00
|
|
|
let stake_keyed_account2 = KeyedAccount::new(&stake_pubkey2, true, &stake_account);
|
|
|
|
|
2019-10-10 14:46:38 -07:00
|
|
|
// Test a successful action by the currently authorized withdrawer
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
2019-09-12 19:03:28 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
stake_lamports,
|
2020-01-22 17:54:06 -08:00
|
|
|
&to_keyed_account,
|
2019-09-12 19:03:28 -07:00
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
2020-07-31 12:37:53 -07:00
|
|
|
&stake_keyed_account2,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2019-09-12 19:03:28 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
|
2019-09-12 19:03:28 -07:00
|
|
|
}
|
|
|
|
|
2020-08-21 11:28:01 -07:00
|
|
|
#[test]
|
|
|
|
fn test_authorize_with_seed() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let base_pubkey = solana_sdk::pubkey::new_rand();
|
2020-08-21 11:28:01 -07:00
|
|
|
let seed = "42";
|
|
|
|
let withdrawer_pubkey = Pubkey::create_with_seed(&base_pubkey, &seed, &id()).unwrap();
|
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-08-21 11:28:01 -07:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Initialized(Meta::auto(&withdrawer_pubkey)),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let base_account = AccountSharedData::new_ref(1, 0, &id());
|
2020-08-21 11:28:01 -07:00
|
|
|
let base_keyed_account = KeyedAccount::new(&base_pubkey, true, &base_account);
|
|
|
|
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&withdrawer_pubkey, true, &stake_account);
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let new_authority = solana_sdk::pubkey::new_rand();
|
2020-08-21 11:28:01 -07:00
|
|
|
|
|
|
|
// Wrong seed
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize_with_seed(
|
|
|
|
&base_keyed_account,
|
|
|
|
&"",
|
|
|
|
&id(),
|
|
|
|
&new_authority,
|
|
|
|
StakeAuthorize::Staker,
|
2021-02-05 22:40:07 -08:00
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None,
|
2020-08-21 11:28:01 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Wrong base
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize_with_seed(
|
|
|
|
&stake_keyed_account,
|
|
|
|
&seed,
|
|
|
|
&id(),
|
|
|
|
&new_authority,
|
|
|
|
StakeAuthorize::Staker,
|
2021-02-05 22:40:07 -08:00
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None,
|
2020-08-21 11:28:01 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Set stake authority
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize_with_seed(
|
|
|
|
&base_keyed_account,
|
|
|
|
&seed,
|
|
|
|
&id(),
|
|
|
|
&new_authority,
|
|
|
|
StakeAuthorize::Staker,
|
2021-02-05 22:40:07 -08:00
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None,
|
2020-08-21 11:28:01 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// Set withdraw authority
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize_with_seed(
|
|
|
|
&base_keyed_account,
|
|
|
|
&seed,
|
|
|
|
&id(),
|
|
|
|
&new_authority,
|
|
|
|
StakeAuthorize::Withdrawer,
|
2021-02-05 22:40:07 -08:00
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None,
|
2020-08-21 11:28:01 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// No longer withdraw authority
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize_with_seed(
|
|
|
|
&stake_keyed_account,
|
|
|
|
&seed,
|
|
|
|
&id(),
|
|
|
|
&new_authority,
|
|
|
|
StakeAuthorize::Withdrawer,
|
2021-02-05 22:40:07 -08:00
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None,
|
2020-08-21 11:28:01 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-25 18:03:26 -08:00
|
|
|
#[test]
|
|
|
|
fn test_authorize_override() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let withdrawer_pubkey = solana_sdk::pubkey::new_rand();
|
2020-02-25 18:03:26 -08:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-02-25 18:03:26 -08:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Initialized(Meta::auto(&withdrawer_pubkey)),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&withdrawer_pubkey, true, &stake_account);
|
|
|
|
|
|
|
|
// Authorize a staker pubkey and move the withdrawer key into cold storage.
|
2020-10-19 12:12:08 -07:00
|
|
|
let new_authority = solana_sdk::pubkey::new_rand();
|
2020-02-25 18:03:26 -08:00
|
|
|
let signers = vec![withdrawer_pubkey].into_iter().collect();
|
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&new_authority,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2020-02-25 18:03:26 -08:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// Attack! The stake key (a hot key) is stolen and used to authorize a new staker.
|
2020-10-19 12:12:08 -07:00
|
|
|
let mallory_pubkey = solana_sdk::pubkey::new_rand();
|
2020-08-21 11:28:01 -07:00
|
|
|
let signers = vec![new_authority].into_iter().collect();
|
2020-02-25 18:03:26 -08:00
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&mallory_pubkey,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2020-02-25 18:03:26 -08:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// Verify the original staker no longer has access.
|
2020-10-19 12:12:08 -07:00
|
|
|
let new_stake_pubkey = solana_sdk::pubkey::new_rand();
|
2020-02-25 18:03:26 -08:00
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&new_stake_pubkey,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2020-02-25 18:03:26 -08:00
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Verify the withdrawer (pulled from cold storage) can save the day.
|
|
|
|
let signers = vec![withdrawer_pubkey].into_iter().collect();
|
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&new_stake_pubkey,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2020-02-25 18:03:26 -08:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// Attack! Verify the staker cannot be used to authorize a withdraw.
|
|
|
|
let signers = vec![new_stake_pubkey].into_iter().collect();
|
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&mallory_pubkey,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2020-02-25 18:03:26 -08:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
#[test]
|
|
|
|
fn test_split_source_uninitialized() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-10-31 11:07:27 -07:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
0,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, false, &split_stake_account);
|
2019-10-31 11:07:27 -07:00
|
|
|
|
|
|
|
// no signers should fail
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(
|
|
|
|
stake_lamports / 2,
|
2020-01-22 17:54:06 -08:00
|
|
|
&split_stake_keyed_account,
|
2019-10-31 11:07:27 -07:00
|
|
|
&HashSet::default() // no signers
|
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
// this should work
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
assert_eq!(
|
2020-01-22 17:54:06 -08:00
|
|
|
stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers),
|
2019-10-31 11:07:27 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-04-30 14:03:08 -07:00
|
|
|
stake_keyed_account.account.borrow().lamports(),
|
|
|
|
split_stake_keyed_account.account.borrow().lamports()
|
2019-10-31 11:07:27 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_split_split_not_uninitialized() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-10-31 11:07:27 -07:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_lamports,
|
2021-06-15 09:04:00 -07:00
|
|
|
&StakeState::Stake(Meta::auto(&stake_pubkey), just_stake(stake_lamports)),
|
2019-10-31 11:07:27 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
0,
|
|
|
|
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
2019-10-31 11:07:27 -07:00
|
|
|
assert_eq!(
|
2020-01-22 17:54:06 -08:00
|
|
|
stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers),
|
2019-10-31 11:07:27 -07:00
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
2021-06-15 09:04:00 -07:00
|
|
|
fn just_stake(stake: u64) -> Stake {
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake,
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
|
|
|
..Stake::default()
|
2019-11-25 13:14:32 -08:00
|
|
|
}
|
|
|
|
}
|
2019-10-31 11:07:27 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_split_more_than_staked() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-10-31 11:07:27 -07:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Stake(
|
|
|
|
Meta::auto(&stake_pubkey),
|
2021-06-15 09:04:00 -07:00
|
|
|
just_stake(stake_lamports / 2 - 1),
|
2019-10-31 11:07:27 -07:00
|
|
|
),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
0,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
2019-10-31 11:07:27 -07:00
|
|
|
assert_eq!(
|
2020-01-22 17:54:06 -08:00
|
|
|
stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers),
|
2019-10-31 11:07:27 -07:00
|
|
|
Err(StakeError::InsufficientStake.into())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_split_with_rent() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
2020-11-06 12:32:05 -08:00
|
|
|
let stake_lamports = 10_000_000;
|
|
|
|
let rent_exempt_reserve = 2_282_880;
|
2019-10-31 11:07:27 -07:00
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// test splitting both an Initialized stake and a Staked stake
|
|
|
|
for state in &[
|
|
|
|
StakeState::Initialized(meta),
|
2021-06-15 09:04:00 -07:00
|
|
|
StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)),
|
2019-10-31 11:07:27 -07:00
|
|
|
] {
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_lamports,
|
|
|
|
state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-10-31 11:07:27 -07:00
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
0,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
2019-10-31 11:07:27 -07:00
|
|
|
|
2020-11-23 18:48:56 -08:00
|
|
|
// not enough to make a non-zero stake account
|
2019-10-31 11:07:27 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(
|
2020-11-23 18:48:56 -08:00
|
|
|
rent_exempt_reserve,
|
2020-01-22 17:54:06 -08:00
|
|
|
&split_stake_keyed_account,
|
2019-10-31 11:07:27 -07:00
|
|
|
&signers
|
|
|
|
),
|
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
2020-11-23 18:48:56 -08:00
|
|
|
// doesn't leave enough for initial stake to be non-zero
|
2019-10-31 11:07:27 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(
|
2020-11-23 18:48:56 -08:00
|
|
|
stake_lamports - rent_exempt_reserve,
|
2020-01-22 17:54:06 -08:00
|
|
|
&split_stake_keyed_account,
|
2019-10-31 11:07:27 -07:00
|
|
|
&signers
|
|
|
|
),
|
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// split account already has way enough lamports
|
2021-04-30 14:17:05 -07:00
|
|
|
split_stake_keyed_account
|
|
|
|
.account
|
|
|
|
.borrow_mut()
|
|
|
|
.set_lamports(10_000_000);
|
2019-10-31 11:07:27 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(
|
2020-11-23 18:48:56 -08:00
|
|
|
stake_lamports - (rent_exempt_reserve + 1), // leave rent_exempt_reserve + 1 in original account
|
2020-01-22 17:54:06 -08:00
|
|
|
&split_stake_keyed_account,
|
2019-10-31 11:07:27 -07:00
|
|
|
&signers
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// verify no stake leakage in the case of a stake
|
|
|
|
if let StakeState::Stake(meta, stake) = state {
|
|
|
|
assert_eq!(
|
|
|
|
split_stake_keyed_account.state(),
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
*meta,
|
|
|
|
Stake {
|
2019-11-25 13:14:32 -08:00
|
|
|
delegation: Delegation {
|
2020-11-23 18:48:56 -08:00
|
|
|
stake: stake_lamports - rent_exempt_reserve - 1,
|
2019-11-25 13:14:32 -08:00
|
|
|
..stake.delegation
|
|
|
|
},
|
2019-10-31 11:07:27 -07:00
|
|
|
..*stake
|
|
|
|
}
|
|
|
|
))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
stake_keyed_account.account.borrow().lamports(),
|
2020-11-23 18:48:56 -08:00
|
|
|
rent_exempt_reserve + 1
|
2020-01-22 09:11:56 -08:00
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
split_stake_keyed_account.account.borrow().lamports(),
|
2020-11-23 18:48:56 -08:00
|
|
|
10_000_000 + stake_lamports - rent_exempt_reserve - 1
|
2019-10-31 11:07:27 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_split() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-10-31 11:07:27 -07:00
|
|
|
let stake_lamports = 42;
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-10-31 11:07:27 -07:00
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
|
|
|
// test splitting both an Initialized stake and a Staked stake
|
|
|
|
for state in &[
|
|
|
|
StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
2021-06-15 09:04:00 -07:00
|
|
|
StakeState::Stake(Meta::auto(&stake_pubkey), just_stake(stake_lamports)),
|
2019-10-31 11:07:27 -07:00
|
|
|
] {
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
0,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
2019-10-31 11:07:27 -07:00
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_lamports,
|
|
|
|
state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-10-31 11:07:27 -07:00
|
|
|
|
|
|
|
// split more than available fails
|
|
|
|
assert_eq!(
|
2020-01-22 17:54:06 -08:00
|
|
|
stake_keyed_account.split(stake_lamports + 1, &split_stake_keyed_account, &signers),
|
2019-10-31 11:07:27 -07:00
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// should work
|
|
|
|
assert_eq!(
|
2020-01-22 17:54:06 -08:00
|
|
|
stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers),
|
2019-10-31 11:07:27 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
// no lamport leakage
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
stake_keyed_account.account.borrow().lamports()
|
|
|
|
+ split_stake_keyed_account.account.borrow().lamports(),
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_lamports
|
|
|
|
);
|
|
|
|
|
|
|
|
match state {
|
|
|
|
StakeState::Initialized(_) => {
|
|
|
|
assert_eq!(Ok(*state), split_stake_keyed_account.state());
|
|
|
|
assert_eq!(Ok(*state), stake_keyed_account.state());
|
|
|
|
}
|
|
|
|
StakeState::Stake(meta, stake) => {
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
*meta,
|
|
|
|
Stake {
|
2019-11-25 13:14:32 -08:00
|
|
|
delegation: Delegation {
|
|
|
|
stake: stake_lamports / 2,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
2019-10-31 11:07:27 -07:00
|
|
|
..*stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
split_stake_keyed_account.state()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
*meta,
|
|
|
|
Stake {
|
2019-11-25 13:14:32 -08:00
|
|
|
delegation: Delegation {
|
|
|
|
stake: stake_lamports / 2,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
2019-10-31 11:07:27 -07:00
|
|
|
..*stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
stake_keyed_account.state()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset
|
2021-04-30 14:17:05 -07:00
|
|
|
stake_keyed_account
|
|
|
|
.account
|
|
|
|
.borrow_mut()
|
|
|
|
.set_lamports(stake_lamports);
|
2019-10-31 11:07:27 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 12:42:59 -08:00
|
|
|
#[test]
|
|
|
|
fn test_split_fake_stake_dest() {
|
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
|
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-16 12:42:59 -08:00
|
|
|
0,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&solana_sdk::pubkey::new_rand(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-16 12:42:59 -08:00
|
|
|
stake_lamports,
|
2021-06-15 09:04:00 -07:00
|
|
|
&StakeState::Stake(Meta::auto(&stake_pubkey), just_stake(stake_lamports)),
|
2020-11-16 12:42:59 -08:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers),
|
|
|
|
Err(InstructionError::IncorrectProgramId),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-11-06 12:32:05 -08:00
|
|
|
#[test]
|
|
|
|
fn test_split_to_account_with_rent_exempt_reserve() {
|
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let stake_lamports = rent_exempt_reserve * 3; // Enough to allow half to be split and remain rent-exempt
|
|
|
|
|
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve));
|
2020-11-06 12:32:05 -08:00
|
|
|
// Test various account prefunding, including empty, less than rent_exempt_reserve, exactly
|
|
|
|
// rent_exempt_reserve, and more than rent_exempt_reserve. The empty case is not covered in
|
|
|
|
// test_split, since that test uses a Meta with rent_exempt_reserve = 0
|
|
|
|
let split_lamport_balances = vec![0, 1, rent_exempt_reserve, rent_exempt_reserve + 1];
|
|
|
|
for initial_balance in split_lamport_balances {
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
initial_balance,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports,
|
|
|
|
&state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
|
|
|
// split more than available fails
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(stake_lamports + 1, &split_stake_keyed_account, &signers),
|
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// should work
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
// no lamport leakage
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
stake_keyed_account.account.borrow().lamports()
|
|
|
|
+ split_stake_keyed_account.account.borrow().lamports(),
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports + initial_balance
|
|
|
|
);
|
|
|
|
|
|
|
|
if let StakeState::Stake(meta, stake) = state {
|
|
|
|
let expected_stake =
|
|
|
|
stake_lamports / 2 - (rent_exempt_reserve.saturating_sub(initial_balance));
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
meta,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: stake_lamports / 2
|
|
|
|
- (rent_exempt_reserve.saturating_sub(initial_balance)),
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
split_stake_keyed_account.state()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
split_stake_keyed_account.account.borrow().lamports(),
|
2020-11-06 12:32:05 -08:00
|
|
|
expected_stake
|
|
|
|
+ rent_exempt_reserve
|
|
|
|
+ initial_balance.saturating_sub(rent_exempt_reserve)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
meta,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: stake_lamports / 2 - rent_exempt_reserve,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
stake_keyed_account.state()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_split_to_smaller_account_with_rent_exempt_reserve() {
|
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let stake_lamports = rent_exempt_reserve * 3; // Enough to allow half to be split and remain rent-exempt
|
|
|
|
|
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve));
|
2020-11-06 12:32:05 -08:00
|
|
|
|
|
|
|
let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve(
|
|
|
|
meta.rent_exempt_reserve,
|
|
|
|
std::mem::size_of::<StakeState>() as u64 + 100,
|
|
|
|
std::mem::size_of::<StakeState>() as u64,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Test various account prefunding, including empty, less than rent_exempt_reserve, exactly
|
|
|
|
// rent_exempt_reserve, and more than rent_exempt_reserve. The empty case is not covered in
|
|
|
|
// test_split, since that test uses a Meta with rent_exempt_reserve = 0
|
|
|
|
let split_lamport_balances = vec![
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
expected_rent_exempt_reserve,
|
|
|
|
expected_rent_exempt_reserve + 1,
|
|
|
|
];
|
|
|
|
for initial_balance in split_lamport_balances {
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
initial_balance,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports,
|
|
|
|
&state,
|
|
|
|
std::mem::size_of::<StakeState>() + 100,
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
|
|
|
// split more than available fails
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(stake_lamports + 1, &split_stake_keyed_account, &signers),
|
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// should work
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(stake_lamports / 2, &split_stake_keyed_account, &signers),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
// no lamport leakage
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
stake_keyed_account.account.borrow().lamports()
|
|
|
|
+ split_stake_keyed_account.account.borrow().lamports(),
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports + initial_balance
|
|
|
|
);
|
|
|
|
|
|
|
|
if let StakeState::Stake(meta, stake) = state {
|
|
|
|
let expected_split_meta = Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
rent_exempt_reserve: expected_rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
let expected_stake = stake_lamports / 2
|
|
|
|
- (expected_rent_exempt_reserve.saturating_sub(initial_balance));
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
expected_split_meta,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: expected_stake,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
split_stake_keyed_account.state()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
split_stake_keyed_account.account.borrow().lamports(),
|
2020-11-06 12:32:05 -08:00
|
|
|
expected_stake
|
|
|
|
+ expected_rent_exempt_reserve
|
|
|
|
+ initial_balance.saturating_sub(expected_rent_exempt_reserve)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
meta,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: stake_lamports / 2 - rent_exempt_reserve,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
stake_keyed_account.state()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-02-16 19:42:46 -08:00
|
|
|
fn test_split_to_larger_account() {
|
2020-11-06 12:32:05 -08:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
|
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve(
|
|
|
|
meta.rent_exempt_reserve,
|
|
|
|
std::mem::size_of::<StakeState>() as u64,
|
|
|
|
std::mem::size_of::<StakeState>() as u64 + 100,
|
|
|
|
);
|
|
|
|
let stake_lamports = expected_rent_exempt_reserve + 1;
|
2020-11-23 18:48:56 -08:00
|
|
|
let split_amount = stake_lamports - (rent_exempt_reserve + 1); // Enough so that split stake is > 0
|
2020-11-06 12:32:05 -08:00
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve));
|
2020-11-06 12:32:05 -08:00
|
|
|
|
|
|
|
let split_lamport_balances = vec![
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
expected_rent_exempt_reserve,
|
|
|
|
expected_rent_exempt_reserve + 1,
|
|
|
|
];
|
|
|
|
for initial_balance in split_lamport_balances {
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
initial_balance,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>() + 100,
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports,
|
|
|
|
&state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
2021-02-16 19:42:46 -08:00
|
|
|
// should always return error when splitting to larger account
|
|
|
|
let split_result =
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_keyed_account.split(split_amount, &split_stake_keyed_account, &signers);
|
2021-02-16 19:42:46 -08:00
|
|
|
assert_eq!(split_result, Err(InstructionError::InvalidAccountData));
|
2020-11-06 12:32:05 -08:00
|
|
|
|
2021-02-16 19:42:46 -08:00
|
|
|
// Splitting 100% of source should not make a difference
|
|
|
|
let split_result =
|
|
|
|
stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers);
|
|
|
|
assert_eq!(split_result, Err(InstructionError::InvalidAccountData));
|
2020-11-06 12:32:05 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-16 15:56:34 -08:00
|
|
|
#[test]
|
|
|
|
fn test_split_100_percent_of_source() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2020-11-06 12:32:05 -08:00
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let stake_lamports = rent_exempt_reserve * 3; // Arbitrary amount over rent_exempt_reserve
|
2019-12-16 15:56:34 -08:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-12-16 15:56:34 -08:00
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// test splitting both an Initialized stake and a Staked stake
|
|
|
|
for state in &[
|
|
|
|
StakeState::Initialized(meta),
|
2021-06-15 09:04:00 -07:00
|
|
|
StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)),
|
2019-12-16 15:56:34 -08:00
|
|
|
] {
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-12-16 15:56:34 -08:00
|
|
|
0,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
2019-12-16 15:56:34 -08:00
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-12-16 15:56:34 -08:00
|
|
|
stake_lamports,
|
|
|
|
state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-12-16 15:56:34 -08:00
|
|
|
|
|
|
|
// split 100% over to dest
|
|
|
|
assert_eq!(
|
2020-01-22 17:54:06 -08:00
|
|
|
stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers),
|
2019-12-16 15:56:34 -08:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// no lamport leakage
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
stake_keyed_account.account.borrow().lamports()
|
|
|
|
+ split_stake_keyed_account.account.borrow().lamports(),
|
2019-12-16 15:56:34 -08:00
|
|
|
stake_lamports
|
|
|
|
);
|
|
|
|
|
|
|
|
match state {
|
|
|
|
StakeState::Initialized(_) => {
|
|
|
|
assert_eq!(Ok(*state), split_stake_keyed_account.state());
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
|
2019-12-16 15:56:34 -08:00
|
|
|
}
|
|
|
|
StakeState::Stake(meta, stake) => {
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
*meta,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: stake_lamports - rent_exempt_reserve,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..*stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
split_stake_keyed_account.state()
|
|
|
|
);
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
|
2019-12-16 15:56:34 -08:00
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset
|
2021-04-30 14:17:05 -07:00
|
|
|
stake_keyed_account
|
|
|
|
.account
|
|
|
|
.borrow_mut()
|
|
|
|
.set_lamports(stake_lamports);
|
2019-12-16 15:56:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-06 12:32:05 -08:00
|
|
|
#[test]
|
|
|
|
fn test_split_100_percent_of_source_to_account_with_lamports() {
|
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let stake_lamports = rent_exempt_reserve * 3; // Arbitrary amount over rent_exempt_reserve
|
|
|
|
|
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
|
2021-06-15 09:04:00 -07:00
|
|
|
let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve));
|
2020-11-06 12:32:05 -08:00
|
|
|
// Test various account prefunding, including empty, less than rent_exempt_reserve, exactly
|
|
|
|
// rent_exempt_reserve, and more than rent_exempt_reserve. Technically, the empty case is
|
|
|
|
// covered in test_split_100_percent_of_source, but included here as well for readability
|
|
|
|
let split_lamport_balances = vec![0, 1, rent_exempt_reserve, rent_exempt_reserve + 1];
|
|
|
|
for initial_balance in split_lamport_balances {
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
initial_balance,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports,
|
|
|
|
&state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
|
|
|
// split 100% over to dest
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// no lamport leakage
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
stake_keyed_account.account.borrow().lamports()
|
|
|
|
+ split_stake_keyed_account.account.borrow().lamports(),
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports + initial_balance
|
|
|
|
);
|
|
|
|
|
|
|
|
if let StakeState::Stake(meta, stake) = state {
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
meta,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: stake_lamports - rent_exempt_reserve,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
split_stake_keyed_account.state()
|
|
|
|
);
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
|
2020-11-06 12:32:05 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_split_rent_exemptness() {
|
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let stake_lamports = rent_exempt_reserve + 1;
|
|
|
|
|
|
|
|
let split_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
for state in &[
|
|
|
|
StakeState::Initialized(meta),
|
2021-06-15 09:04:00 -07:00
|
|
|
StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)),
|
2020-11-06 12:32:05 -08:00
|
|
|
] {
|
2021-02-16 19:42:46 -08:00
|
|
|
// Test that splitting to a larger account fails
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
0,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>() + 10000,
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports,
|
|
|
|
&state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers),
|
2021-02-16 19:42:46 -08:00
|
|
|
Err(InstructionError::InvalidAccountData)
|
2020-11-06 12:32:05 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
// Test that splitting from a larger account to a smaller one works.
|
|
|
|
// Split amount should not matter, assuming other fund criteria are met
|
2021-03-09 13:06:07 -08:00
|
|
|
let split_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
0,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let split_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&split_stake_pubkey, true, &split_stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports,
|
|
|
|
&state,
|
|
|
|
std::mem::size_of::<StakeState>() + 100,
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(stake_lamports, &split_stake_keyed_account, &signers),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
split_stake_keyed_account.account.borrow().lamports(),
|
2020-11-06 12:32:05 -08:00
|
|
|
stake_lamports
|
|
|
|
);
|
|
|
|
|
2020-11-17 11:03:00 -08:00
|
|
|
let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve(
|
|
|
|
meta.rent_exempt_reserve,
|
|
|
|
std::mem::size_of::<StakeState>() as u64 + 100,
|
|
|
|
std::mem::size_of::<StakeState>() as u64,
|
|
|
|
);
|
|
|
|
let expected_split_meta = Meta {
|
|
|
|
authorized: Authorized::auto(&stake_pubkey),
|
|
|
|
rent_exempt_reserve: expected_rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
|
2020-11-06 12:32:05 -08:00
|
|
|
match state {
|
|
|
|
StakeState::Initialized(_) => {
|
2020-11-17 11:03:00 -08:00
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Initialized(expected_split_meta)),
|
|
|
|
split_stake_keyed_account.state()
|
|
|
|
);
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
|
2020-11-06 12:32:05 -08:00
|
|
|
}
|
2020-12-21 18:54:49 -08:00
|
|
|
StakeState::Stake(_meta, stake) => {
|
2020-11-23 09:11:10 -08:00
|
|
|
// Expected stake should reflect original stake amount so that extra lamports
|
|
|
|
// from the rent_exempt_reserve inequality do not magically activate
|
|
|
|
let expected_stake = stake_lamports - rent_exempt_reserve;
|
2020-11-06 12:32:05 -08:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
expected_split_meta,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: expected_stake,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..*stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
split_stake_keyed_account.state()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
split_stake_keyed_account.account.borrow().lamports(),
|
2020-11-23 09:11:10 -08:00
|
|
|
expected_stake
|
|
|
|
+ expected_rent_exempt_reserve
|
|
|
|
+ (rent_exempt_reserve - expected_rent_exempt_reserve)
|
2020-11-06 12:32:05 -08:00
|
|
|
);
|
2020-12-21 18:54:49 -08:00
|
|
|
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
|
2020-11-06 12:32:05 -08:00
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 17:22:47 -07:00
|
|
|
#[test]
|
|
|
|
fn test_merge() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let source_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let authorized_pubkey = solana_sdk::pubkey::new_rand();
|
2020-06-10 17:22:47 -07:00
|
|
|
let stake_lamports = 42;
|
|
|
|
|
|
|
|
let signers = vec![authorized_pubkey].into_iter().collect();
|
2021-04-19 09:48:48 -07:00
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-06-10 17:22:47 -07:00
|
|
|
|
|
|
|
for state in &[
|
|
|
|
StakeState::Initialized(Meta::auto(&authorized_pubkey)),
|
2021-06-15 09:04:00 -07:00
|
|
|
StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
2020-06-10 17:22:47 -07:00
|
|
|
] {
|
|
|
|
for source_state in &[
|
|
|
|
StakeState::Initialized(Meta::auto(&authorized_pubkey)),
|
2021-06-15 09:04:00 -07:00
|
|
|
StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
2020-06-10 17:22:47 -07:00
|
|
|
] {
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-06-10 17:22:47 -07:00
|
|
|
stake_lamports,
|
|
|
|
state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let source_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-06-10 17:22:47 -07:00
|
|
|
stake_lamports,
|
|
|
|
source_state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("source_stake_account");
|
|
|
|
let source_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account);
|
|
|
|
|
|
|
|
// Authorized staker signature required...
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-06-10 17:22:47 -07:00
|
|
|
&source_stake_keyed_account,
|
|
|
|
&Clock::default(),
|
|
|
|
&StakeHistory::default(),
|
2021-05-20 19:23:36 -07:00
|
|
|
&HashSet::new(),
|
|
|
|
false,
|
2020-06-10 17:22:47 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-06-10 17:22:47 -07:00
|
|
|
&source_stake_keyed_account,
|
|
|
|
&Clock::default(),
|
|
|
|
&StakeHistory::default(),
|
2021-05-20 19:23:36 -07:00
|
|
|
&signers,
|
|
|
|
false,
|
2020-06-10 17:22:47 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// check lamports
|
|
|
|
assert_eq!(
|
2021-05-03 08:45:54 -07:00
|
|
|
stake_keyed_account.account.borrow().lamports(),
|
2020-06-10 17:22:47 -07:00
|
|
|
stake_lamports * 2
|
|
|
|
);
|
2021-05-03 08:45:54 -07:00
|
|
|
assert_eq!(source_stake_keyed_account.account.borrow().lamports(), 0);
|
2020-12-21 18:54:49 -08:00
|
|
|
|
|
|
|
// check state
|
|
|
|
match state {
|
|
|
|
StakeState::Initialized(meta) => {
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.state(),
|
|
|
|
Ok(StakeState::Initialized(*meta)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
StakeState::Stake(meta, stake) => {
|
|
|
|
let expected_stake = stake.delegation.stake
|
|
|
|
+ source_state
|
|
|
|
.stake()
|
|
|
|
.map(|stake| stake.delegation.stake)
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
stake_lamports
|
|
|
|
- source_state.meta().unwrap().rent_exempt_reserve
|
|
|
|
});
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.state(),
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
*meta,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: expected_stake,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..*stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
source_stake_keyed_account.state(),
|
|
|
|
Ok(StakeState::Uninitialized)
|
|
|
|
);
|
2020-06-10 17:22:47 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-22 19:11:00 -08:00
|
|
|
#[test]
|
|
|
|
fn test_merge_self_fails() {
|
2021-04-19 09:48:48 -07:00
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-11-22 19:11:00 -08:00
|
|
|
let stake_address = Pubkey::new_unique();
|
|
|
|
let authority_pubkey = Pubkey::new_unique();
|
|
|
|
let signers = HashSet::from_iter(vec![authority_pubkey]);
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let stake_amount = 4242424242;
|
|
|
|
let stake_lamports = rent_exempt_reserve + stake_amount;
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::auto(&authority_pubkey)
|
|
|
|
};
|
|
|
|
let stake = Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: stake_amount,
|
|
|
|
activation_epoch: 0,
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
|
|
|
..Stake::default()
|
|
|
|
};
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-22 19:11:00 -08:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Stake(meta, stake),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_address, true, &stake_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-22 19:11:00 -08:00
|
|
|
&stake_keyed_account,
|
|
|
|
&Clock::default(),
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&signers,
|
2021-05-20 19:23:36 -07:00
|
|
|
false,
|
2020-11-22 19:11:00 -08:00
|
|
|
),
|
|
|
|
Err(InstructionError::InvalidArgument),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-06-10 17:22:47 -07:00
|
|
|
#[test]
|
|
|
|
fn test_merge_incorrect_authorized_staker() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let source_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let authorized_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let wrong_authorized_pubkey = solana_sdk::pubkey::new_rand();
|
2020-06-10 17:22:47 -07:00
|
|
|
let stake_lamports = 42;
|
|
|
|
|
|
|
|
let signers = vec![authorized_pubkey].into_iter().collect();
|
|
|
|
let wrong_signers = vec![wrong_authorized_pubkey].into_iter().collect();
|
2021-04-19 09:48:48 -07:00
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-06-10 17:22:47 -07:00
|
|
|
|
|
|
|
for state in &[
|
|
|
|
StakeState::Initialized(Meta::auto(&authorized_pubkey)),
|
2021-06-15 09:04:00 -07:00
|
|
|
StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
2020-06-10 17:22:47 -07:00
|
|
|
] {
|
|
|
|
for source_state in &[
|
|
|
|
StakeState::Initialized(Meta::auto(&wrong_authorized_pubkey)),
|
|
|
|
StakeState::Stake(
|
|
|
|
Meta::auto(&wrong_authorized_pubkey),
|
2021-06-15 09:04:00 -07:00
|
|
|
just_stake(stake_lamports),
|
2020-06-10 17:22:47 -07:00
|
|
|
),
|
|
|
|
] {
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-06-10 17:22:47 -07:00
|
|
|
stake_lamports,
|
|
|
|
state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let source_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-06-10 17:22:47 -07:00
|
|
|
stake_lamports,
|
|
|
|
source_state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("source_stake_account");
|
|
|
|
let source_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-06-10 17:22:47 -07:00
|
|
|
&source_stake_keyed_account,
|
|
|
|
&Clock::default(),
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&wrong_signers,
|
2021-05-20 19:23:36 -07:00
|
|
|
false,
|
2020-06-10 17:22:47 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-06-10 17:22:47 -07:00
|
|
|
&source_stake_keyed_account,
|
|
|
|
&Clock::default(),
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&signers,
|
2021-05-20 19:23:36 -07:00
|
|
|
false,
|
2020-06-10 17:22:47 -07:00
|
|
|
),
|
|
|
|
Err(StakeError::MergeMismatch.into())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_merge_invalid_account_data() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let source_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let authorized_pubkey = solana_sdk::pubkey::new_rand();
|
2020-06-10 17:22:47 -07:00
|
|
|
let stake_lamports = 42;
|
|
|
|
let signers = vec![authorized_pubkey].into_iter().collect();
|
2021-04-19 09:48:48 -07:00
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-06-10 17:22:47 -07:00
|
|
|
|
|
|
|
for state in &[
|
|
|
|
StakeState::Uninitialized,
|
|
|
|
StakeState::RewardsPool,
|
|
|
|
StakeState::Initialized(Meta::auto(&authorized_pubkey)),
|
2021-06-15 09:04:00 -07:00
|
|
|
StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
2020-06-10 17:22:47 -07:00
|
|
|
] {
|
|
|
|
for source_state in &[StakeState::Uninitialized, StakeState::RewardsPool] {
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-06-10 17:22:47 -07:00
|
|
|
stake_lamports,
|
|
|
|
state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let source_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-06-10 17:22:47 -07:00
|
|
|
stake_lamports,
|
|
|
|
source_state,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("source_stake_account");
|
|
|
|
let source_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-06-10 17:22:47 -07:00
|
|
|
&source_stake_keyed_account,
|
|
|
|
&Clock::default(),
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&signers,
|
2021-05-20 19:23:36 -07:00
|
|
|
false,
|
2020-06-10 17:22:47 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 12:42:59 -08:00
|
|
|
#[test]
|
|
|
|
fn test_merge_fake_stake_source() {
|
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let source_stake_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let authorized_pubkey = solana_sdk::pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
|
|
|
|
let signers = vec![authorized_pubkey].into_iter().collect();
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-16 12:42:59 -08:00
|
|
|
stake_lamports,
|
2021-06-15 09:04:00 -07:00
|
|
|
&StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
2020-11-16 12:42:59 -08:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
|
|
|
|
2021-03-09 13:06:07 -08:00
|
|
|
let source_stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-16 12:42:59 -08:00
|
|
|
stake_lamports,
|
2021-06-15 09:04:00 -07:00
|
|
|
&StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
2020-11-16 12:42:59 -08:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&solana_sdk::pubkey::new_rand(),
|
|
|
|
)
|
|
|
|
.expect("source_stake_account");
|
|
|
|
let source_stake_keyed_account =
|
|
|
|
KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account);
|
2021-04-19 09:48:48 -07:00
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-11-16 12:42:59 -08:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-16 12:42:59 -08:00
|
|
|
&source_stake_keyed_account,
|
|
|
|
&Clock::default(),
|
|
|
|
&StakeHistory::default(),
|
2021-05-20 19:23:36 -07:00
|
|
|
&signers,
|
|
|
|
false,
|
2020-11-16 12:42:59 -08:00
|
|
|
),
|
|
|
|
Err(InstructionError::IncorrectProgramId)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-06-10 17:22:47 -07:00
|
|
|
#[test]
|
|
|
|
fn test_merge_active_stake() {
|
2020-11-19 13:44:16 -08:00
|
|
|
let base_lamports = 4242424242;
|
|
|
|
let stake_address = Pubkey::new_unique();
|
|
|
|
let source_address = Pubkey::new_unique();
|
|
|
|
let authority_pubkey = Pubkey::new_unique();
|
|
|
|
let signers = HashSet::from_iter(vec![authority_pubkey]);
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let stake_amount = base_lamports;
|
|
|
|
let stake_lamports = rent_exempt_reserve + stake_amount;
|
|
|
|
let source_amount = base_lamports;
|
|
|
|
let source_lamports = rent_exempt_reserve + source_amount;
|
2020-06-10 17:22:47 -07:00
|
|
|
|
2020-11-19 13:44:16 -08:00
|
|
|
let meta = Meta {
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::auto(&authority_pubkey)
|
|
|
|
};
|
|
|
|
let mut stake = Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: stake_amount,
|
|
|
|
activation_epoch: 0,
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
|
|
|
..Stake::default()
|
|
|
|
};
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-19 13:44:16 -08:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Stake(meta, stake),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_address, true, &stake_account);
|
2020-06-10 17:22:47 -07:00
|
|
|
|
2020-11-19 13:44:16 -08:00
|
|
|
let source_activation_epoch = 2;
|
|
|
|
let mut source_stake = Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: source_amount,
|
|
|
|
activation_epoch: source_activation_epoch,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..stake
|
|
|
|
};
|
2021-03-09 13:06:07 -08:00
|
|
|
let source_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-19 13:44:16 -08:00
|
|
|
source_lamports,
|
|
|
|
&StakeState::Stake(meta, source_stake),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("source_account");
|
|
|
|
let source_keyed_account = KeyedAccount::new(&source_address, true, &source_account);
|
2020-06-10 17:22:47 -07:00
|
|
|
|
2020-11-19 13:44:16 -08:00
|
|
|
let mut clock = Clock::default();
|
|
|
|
let mut stake_history = StakeHistory::default();
|
2021-04-19 09:48:48 -07:00
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-06-10 17:22:47 -07:00
|
|
|
|
2020-11-19 13:44:16 -08:00
|
|
|
clock.epoch = 0;
|
|
|
|
let mut effective = base_lamports;
|
|
|
|
let mut activating = stake_amount;
|
|
|
|
let mut deactivating = 0;
|
|
|
|
stake_history.add(
|
|
|
|
clock.epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
fn try_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
invoke_context: &dyn InvokeContext,
|
2020-11-19 13:44:16 -08:00
|
|
|
stake_account: &KeyedAccount,
|
|
|
|
source_account: &KeyedAccount,
|
|
|
|
clock: &Clock,
|
|
|
|
stake_history: &StakeHistory,
|
|
|
|
signers: &HashSet<Pubkey>,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
let test_stake_account = stake_account.account.clone();
|
|
|
|
let test_stake_keyed =
|
|
|
|
KeyedAccount::new(stake_account.unsigned_key(), true, &test_stake_account);
|
|
|
|
let test_source_account = source_account.account.clone();
|
|
|
|
let test_source_keyed =
|
|
|
|
KeyedAccount::new(source_account.unsigned_key(), true, &test_source_account);
|
|
|
|
|
2021-01-21 09:59:24 -08:00
|
|
|
let result = test_stake_keyed.merge(
|
|
|
|
invoke_context,
|
|
|
|
&test_source_keyed,
|
|
|
|
clock,
|
|
|
|
stake_history,
|
|
|
|
signers,
|
2021-05-20 19:23:36 -07:00
|
|
|
false,
|
2021-01-21 09:59:24 -08:00
|
|
|
);
|
2020-12-21 18:54:49 -08:00
|
|
|
if result.is_ok() {
|
|
|
|
assert_eq!(test_source_keyed.state(), Ok(StakeState::Uninitialized),);
|
|
|
|
}
|
|
|
|
result
|
2020-11-19 13:44:16 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// stake activation epoch, source initialized succeeds
|
|
|
|
assert!(try_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&stake_keyed_account,
|
|
|
|
&source_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&signers
|
|
|
|
)
|
|
|
|
.is_ok(),);
|
|
|
|
assert!(try_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&source_keyed_account,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&signers
|
|
|
|
)
|
|
|
|
.is_ok(),);
|
|
|
|
|
|
|
|
// both activating fails
|
|
|
|
loop {
|
|
|
|
clock.epoch += 1;
|
|
|
|
if clock.epoch == source_activation_epoch {
|
|
|
|
activating += source_amount;
|
|
|
|
}
|
|
|
|
let delta =
|
|
|
|
activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
|
|
|
|
effective += delta;
|
|
|
|
activating -= delta;
|
|
|
|
stake_history.add(
|
|
|
|
clock.epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if stake_amount == stake.stake(clock.epoch, Some(&stake_history), true)
|
|
|
|
&& source_amount == source_stake.stake(clock.epoch, Some(&stake_history), true)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
try_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&stake_keyed_account,
|
|
|
|
&source_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&signers
|
|
|
|
)
|
|
|
|
.unwrap_err(),
|
|
|
|
InstructionError::from(StakeError::MergeTransientStake),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
try_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&source_keyed_account,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&signers
|
|
|
|
)
|
|
|
|
.unwrap_err(),
|
|
|
|
InstructionError::from(StakeError::MergeTransientStake),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// Both fully activated works
|
|
|
|
assert!(try_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&stake_keyed_account,
|
|
|
|
&source_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&signers
|
|
|
|
)
|
|
|
|
.is_ok(),);
|
|
|
|
|
|
|
|
// deactivate setup for deactivation
|
|
|
|
let source_deactivation_epoch = clock.epoch + 1;
|
|
|
|
let stake_deactivation_epoch = clock.epoch + 2;
|
|
|
|
|
|
|
|
// active/deactivating and deactivating/inactive mismatches fail
|
|
|
|
loop {
|
|
|
|
clock.epoch += 1;
|
|
|
|
let delta =
|
|
|
|
deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
|
|
|
|
effective -= delta;
|
|
|
|
deactivating -= delta;
|
|
|
|
if clock.epoch == stake_deactivation_epoch {
|
|
|
|
deactivating += stake_amount;
|
|
|
|
stake = Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
deactivation_epoch: stake_deactivation_epoch,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..stake
|
|
|
|
};
|
|
|
|
stake_keyed_account
|
|
|
|
.set_state(&StakeState::Stake(meta, stake))
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
if clock.epoch == source_deactivation_epoch {
|
|
|
|
deactivating += source_amount;
|
|
|
|
source_stake = Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
deactivation_epoch: source_deactivation_epoch,
|
|
|
|
..source_stake.delegation
|
|
|
|
},
|
|
|
|
..source_stake
|
|
|
|
};
|
|
|
|
source_keyed_account
|
|
|
|
.set_state(&StakeState::Stake(meta, source_stake))
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
stake_history.add(
|
|
|
|
clock.epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if 0 == stake.stake(clock.epoch, Some(&stake_history), true)
|
|
|
|
&& 0 == source_stake.stake(clock.epoch, Some(&stake_history), true)
|
|
|
|
{
|
|
|
|
break;
|
2020-06-10 17:22:47 -07:00
|
|
|
}
|
2020-11-19 13:44:16 -08:00
|
|
|
assert_eq!(
|
|
|
|
try_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&stake_keyed_account,
|
|
|
|
&source_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&signers
|
|
|
|
)
|
|
|
|
.unwrap_err(),
|
|
|
|
InstructionError::from(StakeError::MergeTransientStake),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
try_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&source_keyed_account,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&signers
|
|
|
|
)
|
|
|
|
.unwrap_err(),
|
|
|
|
InstructionError::from(StakeError::MergeTransientStake),
|
|
|
|
);
|
2020-06-10 17:22:47 -07:00
|
|
|
}
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
// Both fully deactivated works
|
|
|
|
assert!(try_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&stake_keyed_account,
|
|
|
|
&source_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&signers
|
|
|
|
)
|
|
|
|
.is_ok(),);
|
2020-06-10 17:22:47 -07:00
|
|
|
}
|
|
|
|
|
2019-12-19 14:37:47 -08:00
|
|
|
#[test]
|
|
|
|
fn test_lockup_is_expired() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let custodian = solana_sdk::pubkey::new_rand();
|
2019-12-19 14:37:47 -08:00
|
|
|
let lockup = Lockup {
|
|
|
|
epoch: 1,
|
|
|
|
unix_timestamp: 1,
|
|
|
|
custodian,
|
|
|
|
};
|
|
|
|
// neither time
|
2021-05-19 07:31:47 -07:00
|
|
|
assert!(lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 0,
|
|
|
|
unix_timestamp: 0,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
None
|
|
|
|
));
|
2019-12-19 14:37:47 -08:00
|
|
|
// not timestamp
|
2021-05-19 07:31:47 -07:00
|
|
|
assert!(lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 2,
|
|
|
|
unix_timestamp: 0,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
None
|
|
|
|
));
|
2019-12-19 14:37:47 -08:00
|
|
|
// not epoch
|
2021-05-19 07:31:47 -07:00
|
|
|
assert!(lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 0,
|
|
|
|
unix_timestamp: 2,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
None
|
|
|
|
));
|
2019-12-19 14:37:47 -08:00
|
|
|
// both, no custodian
|
2021-05-19 07:31:47 -07:00
|
|
|
assert!(!lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 1,
|
|
|
|
unix_timestamp: 1,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
None
|
|
|
|
));
|
2019-12-19 14:37:47 -08:00
|
|
|
// neither, but custodian
|
2021-05-19 07:31:47 -07:00
|
|
|
assert!(!lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 0,
|
|
|
|
unix_timestamp: 0,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
Some(&custodian),
|
|
|
|
));
|
2019-12-19 14:37:47 -08:00
|
|
|
}
|
|
|
|
|
2020-01-20 12:33:27 -08:00
|
|
|
#[test]
|
|
|
|
#[ignore]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_dbg_stake_minimum_balance() {
|
|
|
|
let minimum_balance = Rent::default().minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
panic!(
|
|
|
|
"stake minimum_balance: {} lamports, {} SOL",
|
|
|
|
minimum_balance,
|
|
|
|
minimum_balance as f64 / solana_sdk::native_token::LAMPORTS_PER_SOL as f64
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
#[test]
|
|
|
|
fn test_authorize_delegated_stake() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
2019-09-12 19:03:28 -07:00
|
|
|
let stake_lamports = 42;
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_lamports,
|
2019-10-31 11:07:27 -07:00
|
|
|
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
2019-09-12 19:03:28 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
let clock = Clock::default();
|
2019-09-12 19:03:28 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let vote_pubkey = solana_sdk::pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_account = RefCell::new(vote_state::create_account(
|
2020-01-22 09:11:56 -08:00
|
|
|
&vote_pubkey,
|
2020-10-19 12:12:08 -07:00
|
|
|
&solana_sdk::pubkey::new_rand(),
|
2020-01-22 09:11:56 -08:00
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
2020-01-22 17:54:06 -08:00
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
2019-09-12 19:03:28 -07:00
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-10-14 15:02:24 -07:00
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_keyed_account
|
2020-01-29 17:59:14 -08:00
|
|
|
.delegate(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&Config::default(),
|
|
|
|
&signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2020-01-29 17:59:14 -08:00
|
|
|
)
|
2019-09-12 19:03:28 -07:00
|
|
|
.unwrap();
|
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
// deactivate, so we can re-delegate
|
|
|
|
stake_keyed_account.deactivate(&clock, &signers).unwrap();
|
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let new_staker_pubkey = solana_sdk::pubkey::new_rand();
|
2019-09-12 19:03:28 -07:00
|
|
|
assert_eq!(
|
2021-02-05 22:40:07 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&signers,
|
|
|
|
&new_staker_pubkey,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
false,
|
|
|
|
&Clock::default(),
|
|
|
|
None
|
|
|
|
),
|
2019-09-12 19:03:28 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2021-06-15 09:04:00 -07:00
|
|
|
let authorized = authorized_from(&stake_keyed_account.try_account_ref().unwrap()).unwrap();
|
2019-09-26 13:29:29 -07:00
|
|
|
assert_eq!(authorized.staker, new_staker_pubkey);
|
2019-09-12 19:03:28 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let other_pubkey = solana_sdk::pubkey::new_rand();
|
2019-10-14 15:02:24 -07:00
|
|
|
let other_signers = vec![other_pubkey].into_iter().collect();
|
2019-09-12 19:03:28 -07:00
|
|
|
|
|
|
|
// Use unsigned stake_keyed_account to test other signers
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
|
2019-09-12 19:03:28 -07:00
|
|
|
|
2020-10-19 12:12:08 -07:00
|
|
|
let new_voter_pubkey = solana_sdk::pubkey::new_rand();
|
2019-09-12 19:03:28 -07:00
|
|
|
let vote_state = VoteState::default();
|
2020-01-22 17:54:06 -08:00
|
|
|
let new_vote_account = RefCell::new(vote_state::create_account(
|
2020-01-22 09:11:56 -08:00
|
|
|
&new_voter_pubkey,
|
2020-10-19 12:12:08 -07:00
|
|
|
&solana_sdk::pubkey::new_rand(),
|
2020-01-22 09:11:56 -08:00
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
2020-01-22 17:54:06 -08:00
|
|
|
let new_vote_keyed_account = KeyedAccount::new(&new_voter_pubkey, false, &new_vote_account);
|
2019-09-12 19:03:28 -07:00
|
|
|
new_vote_keyed_account.set_state(&vote_state).unwrap();
|
|
|
|
|
|
|
|
// Random other account should fail
|
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.delegate(
|
2019-09-12 19:03:28 -07:00
|
|
|
&new_vote_keyed_account,
|
|
|
|
&clock,
|
2020-01-29 17:59:14 -08:00
|
|
|
&StakeHistory::default(),
|
2019-09-12 19:03:28 -07:00
|
|
|
&Config::default(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&other_signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2019-09-12 19:03:28 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
2019-10-14 15:02:24 -07:00
|
|
|
let new_signers = vec![new_staker_pubkey].into_iter().collect();
|
2019-09-12 19:03:28 -07:00
|
|
|
// Authorized staker should succeed
|
|
|
|
assert_eq!(
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_keyed_account.delegate(
|
2019-09-12 19:03:28 -07:00
|
|
|
&new_vote_keyed_account,
|
|
|
|
&clock,
|
2020-01-29 17:59:14 -08:00
|
|
|
&StakeHistory::default(),
|
2019-09-12 19:03:28 -07:00
|
|
|
&Config::default(),
|
2021-05-20 22:31:55 -07:00
|
|
|
&new_signers,
|
|
|
|
true,
|
2019-09-12 19:03:28 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
2021-06-15 09:04:00 -07:00
|
|
|
let stake = stake_from(&stake_keyed_account.try_account_ref().unwrap()).unwrap();
|
2019-11-25 13:14:32 -08:00
|
|
|
assert_eq!(stake.delegation.voter_pubkey, new_voter_pubkey);
|
2019-09-12 19:03:28 -07:00
|
|
|
|
|
|
|
// Test another staking action
|
2020-01-29 17:59:14 -08:00
|
|
|
assert_eq!(stake_keyed_account.deactivate(&clock, &new_signers), Ok(()));
|
2019-09-12 19:03:28 -07:00
|
|
|
}
|
2020-11-02 19:53:27 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_redelegate_consider_balance_changes() {
|
|
|
|
let initial_lamports = 4242424242;
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let withdrawer_pubkey = Pubkey::new_unique();
|
|
|
|
let stake_lamports = rent_exempt_reserve + initial_lamports;
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::auto(&withdrawer_pubkey)
|
|
|
|
};
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-02 19:53:27 -08:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Initialized(meta),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&withdrawer_pubkey, true, &stake_account);
|
|
|
|
|
|
|
|
let vote_pubkey = Pubkey::new_unique();
|
|
|
|
let vote_account = RefCell::new(vote_state::create_account(
|
|
|
|
&vote_pubkey,
|
|
|
|
&Pubkey::new_unique(),
|
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
|
|
|
|
|
|
|
let signers = HashSet::from_iter(vec![withdrawer_pubkey]);
|
|
|
|
let config = Config::default();
|
|
|
|
let stake_history = StakeHistory::default();
|
|
|
|
let mut clock = Clock::default();
|
|
|
|
stake_keyed_account
|
|
|
|
.delegate(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&config,
|
|
|
|
&signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2020-11-02 19:53:27 -08:00
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
clock.epoch += 1;
|
|
|
|
stake_keyed_account.deactivate(&clock, &signers).unwrap();
|
|
|
|
|
|
|
|
clock.epoch += 1;
|
2021-05-20 22:31:55 -07:00
|
|
|
// Once deactivated, we withdraw stake to new keyed account
|
2020-11-02 19:53:27 -08:00
|
|
|
let to = Pubkey::new_unique();
|
2021-03-09 13:06:07 -08:00
|
|
|
let to_account = AccountSharedData::new_ref(1, 0, &system_program::id());
|
2020-11-02 19:53:27 -08:00
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
|
|
|
let withdraw_lamports = initial_lamports / 2;
|
|
|
|
stake_keyed_account
|
|
|
|
.withdraw(
|
|
|
|
withdraw_lamports,
|
|
|
|
&to_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&stake_keyed_account,
|
|
|
|
None,
|
2021-05-20 19:01:08 -07:00
|
|
|
true,
|
2020-11-02 19:53:27 -08:00
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let expected_balance = rent_exempt_reserve + initial_lamports - withdraw_lamports;
|
|
|
|
assert_eq!(stake_keyed_account.lamports().unwrap(), expected_balance);
|
|
|
|
|
|
|
|
clock.epoch += 1;
|
|
|
|
stake_keyed_account
|
|
|
|
.delegate(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&config,
|
|
|
|
&signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2020-11-02 19:53:27 -08:00
|
|
|
)
|
|
|
|
.unwrap();
|
2021-06-15 09:04:00 -07:00
|
|
|
let stake = stake_from(&stake_account.borrow()).unwrap();
|
2020-11-02 19:53:27 -08:00
|
|
|
assert_eq!(
|
|
|
|
stake.delegation.stake,
|
|
|
|
stake_keyed_account.lamports().unwrap() - rent_exempt_reserve,
|
|
|
|
);
|
|
|
|
|
|
|
|
clock.epoch += 1;
|
|
|
|
stake_keyed_account.deactivate(&clock, &signers).unwrap();
|
|
|
|
|
|
|
|
// Out of band deposit
|
2021-04-27 07:11:35 -07:00
|
|
|
stake_keyed_account
|
|
|
|
.try_account_ref_mut()
|
|
|
|
.unwrap()
|
|
|
|
.checked_add_lamports(withdraw_lamports)
|
|
|
|
.unwrap();
|
2020-11-02 19:53:27 -08:00
|
|
|
|
|
|
|
clock.epoch += 1;
|
|
|
|
stake_keyed_account
|
|
|
|
.delegate(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history,
|
|
|
|
&config,
|
|
|
|
&signers,
|
2021-05-20 22:31:55 -07:00
|
|
|
true,
|
2020-11-02 19:53:27 -08:00
|
|
|
)
|
|
|
|
.unwrap();
|
2021-06-15 09:04:00 -07:00
|
|
|
let stake = stake_from(&stake_account.borrow()).unwrap();
|
2020-11-02 19:53:27 -08:00
|
|
|
assert_eq!(
|
|
|
|
stake.delegation.stake,
|
|
|
|
stake_keyed_account.lamports().unwrap() - rent_exempt_reserve,
|
|
|
|
);
|
|
|
|
}
|
2020-11-06 12:32:05 -08:00
|
|
|
|
2020-11-19 12:15:06 -08:00
|
|
|
#[test]
|
|
|
|
fn test_meta_rewrite_rent_exempt_reserve() {
|
|
|
|
let right_data_len = std::mem::size_of::<StakeState>() as u64;
|
|
|
|
let rent = Rent::default();
|
|
|
|
let expected_rent_exempt_reserve = rent.minimum_balance(right_data_len as usize);
|
|
|
|
|
|
|
|
let test_cases = [
|
|
|
|
(
|
|
|
|
right_data_len + 100,
|
|
|
|
Some((
|
|
|
|
rent.minimum_balance(right_data_len as usize + 100),
|
|
|
|
expected_rent_exempt_reserve,
|
|
|
|
)),
|
|
|
|
), // large data_len, too small rent exempt
|
|
|
|
(right_data_len, None), // correct
|
|
|
|
(
|
|
|
|
right_data_len - 100,
|
|
|
|
Some((
|
|
|
|
rent.minimum_balance(right_data_len as usize - 100),
|
|
|
|
expected_rent_exempt_reserve,
|
|
|
|
)),
|
|
|
|
), // small data_len, too large rent exempt
|
|
|
|
];
|
|
|
|
for (data_len, expected_rewrite) in &test_cases {
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(*data_len as usize);
|
|
|
|
let mut meta = Meta {
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
let actual_rewrite = meta.rewrite_rent_exempt_reserve(&rent, right_data_len as usize);
|
|
|
|
assert_eq!(actual_rewrite, *expected_rewrite);
|
|
|
|
assert_eq!(meta.rent_exempt_reserve, expected_rent_exempt_reserve);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_stake_rewrite_stake() {
|
|
|
|
let right_data_len = std::mem::size_of::<StakeState>() as u64;
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(right_data_len as usize);
|
|
|
|
let expected_stake = 1000;
|
|
|
|
let account_balance = rent_exempt_reserve + expected_stake;
|
|
|
|
|
|
|
|
let test_cases = [
|
|
|
|
(9999, Some((9999, expected_stake))), // large stake
|
|
|
|
(1000, None), // correct
|
|
|
|
(42, Some((42, expected_stake))), // small stake
|
|
|
|
];
|
|
|
|
for (staked_amount, expected_rewrite) in &test_cases {
|
|
|
|
let mut delegation = Delegation {
|
|
|
|
stake: *staked_amount,
|
|
|
|
..Delegation::default()
|
|
|
|
};
|
|
|
|
let actual_rewrite = delegation.rewrite_stake(account_balance, rent_exempt_reserve);
|
|
|
|
assert_eq!(actual_rewrite, *expected_rewrite);
|
|
|
|
assert_eq!(delegation.stake, expected_stake);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum ExpectedRewriteResult {
|
|
|
|
NotRewritten,
|
|
|
|
Rewritten,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_rewrite_stakes_initialized() {
|
|
|
|
let right_data_len = std::mem::size_of::<StakeState>();
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(right_data_len as usize);
|
|
|
|
let expected_stake = 1000;
|
|
|
|
let account_balance = rent_exempt_reserve + expected_stake;
|
|
|
|
|
|
|
|
let test_cases = [
|
|
|
|
(1, ExpectedRewriteResult::Rewritten),
|
|
|
|
(0, ExpectedRewriteResult::NotRewritten),
|
|
|
|
];
|
|
|
|
for (offset, expected_rewrite) in &test_cases {
|
|
|
|
let meta = Meta {
|
|
|
|
rent_exempt_reserve: rent_exempt_reserve + offset,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
2021-03-09 13:06:07 -08:00
|
|
|
let mut account = AccountSharedData::new(account_balance, right_data_len, &id());
|
2020-11-19 12:15:06 -08:00
|
|
|
account.set_state(&StakeState::Initialized(meta)).unwrap();
|
|
|
|
let result = rewrite_stakes(&mut account, &rent);
|
|
|
|
match expected_rewrite {
|
|
|
|
ExpectedRewriteResult::NotRewritten => assert!(result.is_err()),
|
|
|
|
ExpectedRewriteResult::Rewritten => assert!(result.is_ok()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_rewrite_stakes_stake() {
|
|
|
|
let right_data_len = std::mem::size_of::<StakeState>();
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(right_data_len as usize);
|
|
|
|
let expected_stake = 1000;
|
|
|
|
let account_balance = rent_exempt_reserve + expected_stake;
|
|
|
|
|
|
|
|
let test_cases = [
|
|
|
|
(1, 9999, ExpectedRewriteResult::Rewritten), // bad meta, bad stake
|
|
|
|
(1, 1000, ExpectedRewriteResult::Rewritten), // bad meta, good stake
|
|
|
|
(0, 9999, ExpectedRewriteResult::Rewritten), // good meta, bad stake
|
|
|
|
(0, 1000, ExpectedRewriteResult::NotRewritten), // good meta, good stake
|
|
|
|
];
|
|
|
|
for (offset, staked_amount, expected_rewrite) in &test_cases {
|
|
|
|
let meta = Meta {
|
|
|
|
rent_exempt_reserve: rent_exempt_reserve + offset,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
let stake = Stake {
|
|
|
|
delegation: (Delegation {
|
|
|
|
stake: *staked_amount,
|
|
|
|
..Delegation::default()
|
|
|
|
}),
|
|
|
|
..Stake::default()
|
|
|
|
};
|
2021-03-09 13:06:07 -08:00
|
|
|
let mut account = AccountSharedData::new(account_balance, right_data_len, &id());
|
2020-11-19 12:15:06 -08:00
|
|
|
account.set_state(&StakeState::Stake(meta, stake)).unwrap();
|
|
|
|
let result = rewrite_stakes(&mut account, &rent);
|
|
|
|
match expected_rewrite {
|
|
|
|
ExpectedRewriteResult::NotRewritten => assert!(result.is_err()),
|
|
|
|
ExpectedRewriteResult::Rewritten => assert!(result.is_ok()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-06 12:32:05 -08:00
|
|
|
#[test]
|
|
|
|
fn test_calculate_lamports_per_byte_year() {
|
|
|
|
let rent = Rent::default();
|
|
|
|
let data_len = 200u64;
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(data_len as usize);
|
|
|
|
assert_eq!(
|
|
|
|
calculate_split_rent_exempt_reserve(rent_exempt_reserve, data_len, data_len),
|
|
|
|
rent_exempt_reserve
|
|
|
|
);
|
|
|
|
|
|
|
|
let larger_data = 4008u64;
|
|
|
|
let larger_rent_exempt_reserve = rent.minimum_balance(larger_data as usize);
|
|
|
|
assert_eq!(
|
|
|
|
calculate_split_rent_exempt_reserve(rent_exempt_reserve, data_len, larger_data),
|
|
|
|
larger_rent_exempt_reserve
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
calculate_split_rent_exempt_reserve(larger_rent_exempt_reserve, larger_data, data_len),
|
|
|
|
rent_exempt_reserve
|
|
|
|
);
|
|
|
|
|
|
|
|
let even_larger_data = solana_sdk::system_instruction::MAX_PERMITTED_DATA_LENGTH;
|
|
|
|
let even_larger_rent_exempt_reserve = rent.minimum_balance(even_larger_data as usize);
|
|
|
|
assert_eq!(
|
|
|
|
calculate_split_rent_exempt_reserve(rent_exempt_reserve, data_len, even_larger_data),
|
|
|
|
even_larger_rent_exempt_reserve
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
calculate_split_rent_exempt_reserve(
|
|
|
|
even_larger_rent_exempt_reserve,
|
|
|
|
even_larger_data,
|
|
|
|
data_len
|
|
|
|
),
|
|
|
|
rent_exempt_reserve
|
|
|
|
);
|
|
|
|
}
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_things_can_merge() {
|
|
|
|
let good_stake = Stake {
|
|
|
|
credits_observed: 4242,
|
|
|
|
delegation: Delegation {
|
|
|
|
voter_pubkey: Pubkey::new_unique(),
|
|
|
|
stake: 424242424242,
|
|
|
|
activation_epoch: 42,
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
|
|
|
};
|
2021-04-19 09:48:48 -07:00
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
let identical = good_stake;
|
2021-01-21 09:59:24 -08:00
|
|
|
assert!(
|
|
|
|
MergeKind::active_stakes_can_merge(&invoke_context, &good_stake, &identical).is_ok()
|
|
|
|
);
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
let bad_credits_observed = Stake {
|
|
|
|
credits_observed: good_stake.credits_observed + 1,
|
|
|
|
..good_stake
|
|
|
|
};
|
2021-01-21 09:59:24 -08:00
|
|
|
assert!(MergeKind::active_stakes_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&good_stake,
|
|
|
|
&bad_credits_observed
|
|
|
|
)
|
|
|
|
.is_err());
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
let good_delegation = good_stake.delegation;
|
|
|
|
let different_stake_ok = Delegation {
|
|
|
|
stake: good_delegation.stake + 1,
|
|
|
|
..good_delegation
|
|
|
|
};
|
2021-01-21 09:59:24 -08:00
|
|
|
assert!(MergeKind::active_delegations_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&good_delegation,
|
|
|
|
&different_stake_ok
|
|
|
|
)
|
|
|
|
.is_ok());
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
let different_activation_epoch_ok = Delegation {
|
|
|
|
activation_epoch: good_delegation.activation_epoch + 1,
|
|
|
|
..good_delegation
|
|
|
|
};
|
|
|
|
assert!(MergeKind::active_delegations_can_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&good_delegation,
|
|
|
|
&different_activation_epoch_ok
|
|
|
|
)
|
|
|
|
.is_ok());
|
|
|
|
|
|
|
|
let bad_voter = Delegation {
|
|
|
|
voter_pubkey: Pubkey::new_unique(),
|
|
|
|
..good_delegation
|
|
|
|
};
|
2021-01-21 09:59:24 -08:00
|
|
|
assert!(MergeKind::active_delegations_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&good_delegation,
|
|
|
|
&bad_voter
|
|
|
|
)
|
|
|
|
.is_err());
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
let bad_warmup_cooldown_rate = Delegation {
|
|
|
|
warmup_cooldown_rate: good_delegation.warmup_cooldown_rate + f64::EPSILON,
|
|
|
|
..good_delegation
|
|
|
|
};
|
|
|
|
assert!(MergeKind::active_delegations_can_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&good_delegation,
|
|
|
|
&bad_warmup_cooldown_rate
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
assert!(MergeKind::active_delegations_can_merge(
|
2021-01-21 09:59:24 -08:00
|
|
|
&invoke_context,
|
2020-11-19 13:44:16 -08:00
|
|
|
&bad_warmup_cooldown_rate,
|
|
|
|
&good_delegation
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
|
|
|
|
let bad_deactivation_epoch = Delegation {
|
|
|
|
deactivation_epoch: 43,
|
|
|
|
..good_delegation
|
|
|
|
};
|
2021-01-21 09:59:24 -08:00
|
|
|
assert!(MergeKind::active_delegations_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&good_delegation,
|
|
|
|
&bad_deactivation_epoch
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
assert!(MergeKind::active_delegations_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&bad_deactivation_epoch,
|
|
|
|
&good_delegation
|
|
|
|
)
|
|
|
|
.is_err());
|
2021-05-20 19:23:36 -07:00
|
|
|
}
|
2020-11-19 13:44:16 -08:00
|
|
|
|
2021-05-20 19:23:36 -07:00
|
|
|
#[test]
|
|
|
|
fn test_metas_can_merge_pre_v4() {
|
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-11-19 13:44:16 -08:00
|
|
|
// Identical Metas can merge
|
2021-05-20 19:23:36 -07:00
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&Meta::default(),
|
|
|
|
&Meta::default(),
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
.is_ok());
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
let mismatched_rent_exempt_reserve_ok = Meta {
|
|
|
|
rent_exempt_reserve: 42,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
assert_ne!(
|
|
|
|
mismatched_rent_exempt_reserve_ok.rent_exempt_reserve,
|
|
|
|
Meta::default().rent_exempt_reserve
|
|
|
|
);
|
2021-01-21 09:59:24 -08:00
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&Meta::default(),
|
2021-05-20 19:23:36 -07:00
|
|
|
&mismatched_rent_exempt_reserve_ok,
|
|
|
|
None,
|
2021-01-21 09:59:24 -08:00
|
|
|
)
|
|
|
|
.is_ok());
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&mismatched_rent_exempt_reserve_ok,
|
2021-05-20 19:23:36 -07:00
|
|
|
&Meta::default(),
|
|
|
|
None,
|
2021-01-21 09:59:24 -08:00
|
|
|
)
|
|
|
|
.is_ok());
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
let mismatched_authorized_fails = Meta {
|
|
|
|
authorized: Authorized {
|
|
|
|
staker: Pubkey::new_unique(),
|
|
|
|
withdrawer: Pubkey::new_unique(),
|
|
|
|
},
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
assert_ne!(
|
|
|
|
mismatched_authorized_fails.authorized,
|
|
|
|
Meta::default().authorized
|
|
|
|
);
|
2021-01-21 09:59:24 -08:00
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&Meta::default(),
|
2021-05-20 19:23:36 -07:00
|
|
|
&mismatched_authorized_fails,
|
|
|
|
None,
|
2021-01-21 09:59:24 -08:00
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&mismatched_authorized_fails,
|
2021-05-20 19:23:36 -07:00
|
|
|
&Meta::default(),
|
|
|
|
None,
|
2021-01-21 09:59:24 -08:00
|
|
|
)
|
|
|
|
.is_err());
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
let mismatched_lockup_fails = Meta {
|
|
|
|
lockup: Lockup {
|
|
|
|
unix_timestamp: 424242424,
|
|
|
|
epoch: 42,
|
|
|
|
custodian: Pubkey::new_unique(),
|
|
|
|
},
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
assert_ne!(mismatched_lockup_fails.lockup, Meta::default().lockup);
|
2021-01-21 09:59:24 -08:00
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&Meta::default(),
|
2021-05-20 19:23:36 -07:00
|
|
|
&mismatched_lockup_fails,
|
|
|
|
None,
|
2021-01-21 09:59:24 -08:00
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&mismatched_lockup_fails,
|
2021-05-20 19:23:36 -07:00
|
|
|
&Meta::default(),
|
|
|
|
None,
|
2021-01-21 09:59:24 -08:00
|
|
|
)
|
|
|
|
.is_err());
|
2020-11-19 13:44:16 -08:00
|
|
|
}
|
|
|
|
|
2021-05-20 22:58:16 -07:00
|
|
|
#[test]
|
|
|
|
fn test_metas_can_merge_v4() {
|
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
|
|
|
// Identical Metas can merge
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&Meta::default(),
|
|
|
|
&Meta::default(),
|
|
|
|
Some(&Clock::default())
|
|
|
|
)
|
|
|
|
.is_ok());
|
|
|
|
|
|
|
|
let mismatched_rent_exempt_reserve_ok = Meta {
|
|
|
|
rent_exempt_reserve: 42,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
assert_ne!(
|
|
|
|
mismatched_rent_exempt_reserve_ok.rent_exempt_reserve,
|
|
|
|
Meta::default().rent_exempt_reserve,
|
|
|
|
);
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&Meta::default(),
|
|
|
|
&mismatched_rent_exempt_reserve_ok,
|
|
|
|
Some(&Clock::default())
|
|
|
|
)
|
|
|
|
.is_ok());
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&mismatched_rent_exempt_reserve_ok,
|
|
|
|
&Meta::default(),
|
|
|
|
Some(&Clock::default())
|
|
|
|
)
|
|
|
|
.is_ok());
|
|
|
|
|
|
|
|
let mismatched_authorized_fails = Meta {
|
|
|
|
authorized: Authorized {
|
|
|
|
staker: Pubkey::new_unique(),
|
|
|
|
withdrawer: Pubkey::new_unique(),
|
|
|
|
},
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
assert_ne!(
|
|
|
|
mismatched_authorized_fails.authorized,
|
|
|
|
Meta::default().authorized,
|
|
|
|
);
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&Meta::default(),
|
|
|
|
&mismatched_authorized_fails,
|
|
|
|
Some(&Clock::default())
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&mismatched_authorized_fails,
|
|
|
|
&Meta::default(),
|
|
|
|
Some(&Clock::default())
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
|
|
|
|
let lockup1_timestamp = 42;
|
|
|
|
let lockup2_timestamp = 4242;
|
|
|
|
let lockup1_epoch = 4;
|
|
|
|
let lockup2_epoch = 42;
|
|
|
|
let metas_with_lockup1 = Meta {
|
|
|
|
lockup: Lockup {
|
|
|
|
unix_timestamp: lockup1_timestamp,
|
|
|
|
epoch: lockup1_epoch,
|
|
|
|
custodian: Pubkey::new_unique(),
|
|
|
|
},
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
let metas_with_lockup2 = Meta {
|
|
|
|
lockup: Lockup {
|
|
|
|
unix_timestamp: lockup2_timestamp,
|
|
|
|
epoch: lockup2_epoch,
|
|
|
|
custodian: Pubkey::new_unique(),
|
|
|
|
},
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Mismatched lockups fail when both in force
|
|
|
|
assert_ne!(metas_with_lockup1.lockup, Meta::default().lockup);
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&metas_with_lockup1,
|
|
|
|
&metas_with_lockup2,
|
|
|
|
Some(&Clock::default())
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&metas_with_lockup2,
|
|
|
|
&metas_with_lockup1,
|
|
|
|
Some(&Clock::default())
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
|
|
|
|
let clock = Clock {
|
|
|
|
epoch: lockup1_epoch + 1,
|
|
|
|
unix_timestamp: lockup1_timestamp + 1,
|
|
|
|
..Clock::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Mismatched lockups fail when either in force
|
|
|
|
assert_ne!(metas_with_lockup1.lockup, Meta::default().lockup);
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&metas_with_lockup1,
|
|
|
|
&metas_with_lockup2,
|
|
|
|
Some(&clock)
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&metas_with_lockup2,
|
|
|
|
&metas_with_lockup1,
|
|
|
|
Some(&clock)
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
|
|
|
|
let clock = Clock {
|
|
|
|
epoch: lockup2_epoch + 1,
|
|
|
|
unix_timestamp: lockup2_timestamp + 1,
|
|
|
|
..Clock::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Mismatched lockups succeed when both expired
|
|
|
|
assert_ne!(metas_with_lockup1.lockup, Meta::default().lockup);
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&metas_with_lockup1,
|
|
|
|
&metas_with_lockup2,
|
|
|
|
Some(&clock)
|
|
|
|
)
|
|
|
|
.is_ok());
|
|
|
|
assert!(MergeKind::metas_can_merge(
|
|
|
|
&invoke_context,
|
|
|
|
&metas_with_lockup2,
|
|
|
|
&metas_with_lockup1,
|
|
|
|
Some(&clock)
|
|
|
|
)
|
|
|
|
.is_ok());
|
|
|
|
}
|
|
|
|
|
2020-11-19 13:44:16 -08:00
|
|
|
#[test]
|
|
|
|
fn test_merge_kind_get_if_mergeable() {
|
|
|
|
let authority_pubkey = Pubkey::new_unique();
|
|
|
|
let initial_lamports = 4242424242;
|
|
|
|
let rent = Rent::default();
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
|
|
|
let stake_lamports = rent_exempt_reserve + initial_lamports;
|
|
|
|
|
|
|
|
let meta = Meta {
|
|
|
|
rent_exempt_reserve,
|
|
|
|
..Meta::auto(&authority_pubkey)
|
|
|
|
};
|
2021-03-09 13:06:07 -08:00
|
|
|
let stake_account = AccountSharedData::new_ref_data_with_space(
|
2020-11-19 13:44:16 -08:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Uninitialized,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
let stake_keyed_account = KeyedAccount::new(&authority_pubkey, true, &stake_account);
|
|
|
|
let mut clock = Clock::default();
|
|
|
|
let mut stake_history = StakeHistory::default();
|
2021-04-19 09:48:48 -07:00
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
// Uninitialized state fails
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
MergeKind::get_if_mergeable(
|
|
|
|
&invoke_context,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history
|
|
|
|
)
|
|
|
|
.unwrap_err(),
|
2020-11-19 13:44:16 -08:00
|
|
|
InstructionError::InvalidAccountData
|
|
|
|
);
|
|
|
|
|
|
|
|
// RewardsPool state fails
|
|
|
|
stake_keyed_account
|
|
|
|
.set_state(&StakeState::RewardsPool)
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
MergeKind::get_if_mergeable(
|
|
|
|
&invoke_context,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history
|
|
|
|
)
|
|
|
|
.unwrap_err(),
|
2020-11-19 13:44:16 -08:00
|
|
|
InstructionError::InvalidAccountData
|
|
|
|
);
|
|
|
|
|
|
|
|
// Initialized state succeeds
|
|
|
|
stake_keyed_account
|
|
|
|
.set_state(&StakeState::Initialized(meta))
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
MergeKind::get_if_mergeable(
|
|
|
|
&invoke_context,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history
|
|
|
|
)
|
|
|
|
.unwrap(),
|
2020-11-19 13:44:16 -08:00
|
|
|
MergeKind::Inactive(meta, stake_lamports)
|
|
|
|
);
|
|
|
|
|
|
|
|
clock.epoch = 0;
|
|
|
|
let mut effective = 2 * initial_lamports;
|
|
|
|
let mut activating = 0;
|
|
|
|
let mut deactivating = 0;
|
|
|
|
stake_history.add(
|
|
|
|
clock.epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
clock.epoch += 1;
|
|
|
|
activating = initial_lamports;
|
|
|
|
stake_history.add(
|
|
|
|
clock.epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let stake = Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: initial_lamports,
|
|
|
|
activation_epoch: 1,
|
|
|
|
deactivation_epoch: 5,
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
|
|
|
..Stake::default()
|
|
|
|
};
|
|
|
|
stake_keyed_account
|
|
|
|
.set_state(&StakeState::Stake(meta, stake))
|
|
|
|
.unwrap();
|
|
|
|
// activation_epoch succeeds
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
MergeKind::get_if_mergeable(
|
|
|
|
&invoke_context,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history
|
|
|
|
)
|
|
|
|
.unwrap(),
|
2020-11-19 13:44:16 -08:00
|
|
|
MergeKind::ActivationEpoch(meta, stake),
|
|
|
|
);
|
|
|
|
|
|
|
|
// all paritially activated, transient epochs fail
|
|
|
|
loop {
|
|
|
|
clock.epoch += 1;
|
|
|
|
let delta =
|
|
|
|
activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
|
|
|
|
effective += delta;
|
|
|
|
activating -= delta;
|
|
|
|
stake_history.add(
|
|
|
|
clock.epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if activating == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
MergeKind::get_if_mergeable(
|
|
|
|
&invoke_context,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history
|
|
|
|
)
|
|
|
|
.unwrap_err(),
|
2020-11-19 13:44:16 -08:00
|
|
|
InstructionError::from(StakeError::MergeTransientStake),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// all epochs for which we're fully active succeed
|
|
|
|
while clock.epoch < stake.delegation.deactivation_epoch - 1 {
|
|
|
|
clock.epoch += 1;
|
|
|
|
stake_history.add(
|
|
|
|
clock.epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
MergeKind::get_if_mergeable(
|
|
|
|
&invoke_context,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history
|
|
|
|
)
|
|
|
|
.unwrap(),
|
2020-11-19 13:44:16 -08:00
|
|
|
MergeKind::FullyActive(meta, stake),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
clock.epoch += 1;
|
|
|
|
deactivating = stake.delegation.stake;
|
|
|
|
stake_history.add(
|
|
|
|
clock.epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
// deactivation epoch fails, fully transient/deactivating
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
MergeKind::get_if_mergeable(
|
|
|
|
&invoke_context,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history
|
|
|
|
)
|
|
|
|
.unwrap_err(),
|
2020-11-19 13:44:16 -08:00
|
|
|
InstructionError::from(StakeError::MergeTransientStake),
|
|
|
|
);
|
|
|
|
|
|
|
|
// all transient, deactivating epochs fail
|
|
|
|
loop {
|
|
|
|
clock.epoch += 1;
|
|
|
|
let delta =
|
|
|
|
deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
|
|
|
|
effective -= delta;
|
|
|
|
deactivating -= delta;
|
|
|
|
stake_history.add(
|
|
|
|
clock.epoch,
|
|
|
|
StakeHistoryEntry {
|
|
|
|
effective,
|
|
|
|
activating,
|
|
|
|
deactivating,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if deactivating == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
MergeKind::get_if_mergeable(
|
|
|
|
&invoke_context,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history
|
|
|
|
)
|
|
|
|
.unwrap_err(),
|
2020-11-19 13:44:16 -08:00
|
|
|
InstructionError::from(StakeError::MergeTransientStake),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// first fully-deactivated epoch succeeds
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
MergeKind::get_if_mergeable(
|
|
|
|
&invoke_context,
|
|
|
|
&stake_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&stake_history
|
|
|
|
)
|
|
|
|
.unwrap(),
|
2020-11-19 13:44:16 -08:00
|
|
|
MergeKind::Inactive(meta, stake_lamports),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_merge_kind_merge() {
|
|
|
|
let lamports = 424242;
|
|
|
|
let meta = Meta {
|
|
|
|
rent_exempt_reserve: 42,
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
let stake = Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: 4242,
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
|
|
|
..Stake::default()
|
|
|
|
};
|
|
|
|
let inactive = MergeKind::Inactive(Meta::default(), lamports);
|
|
|
|
let activation_epoch = MergeKind::ActivationEpoch(meta, stake);
|
|
|
|
let fully_active = MergeKind::FullyActive(meta, stake);
|
2021-04-19 09:48:48 -07:00
|
|
|
let invoke_context = MockInvokeContext::new(vec![]);
|
2020-11-19 13:44:16 -08:00
|
|
|
|
|
|
|
assert_eq!(
|
2021-01-21 09:59:24 -08:00
|
|
|
inactive
|
|
|
|
.clone()
|
2021-05-20 19:23:36 -07:00
|
|
|
.merge(&invoke_context, inactive.clone(), None)
|
2021-01-21 09:59:24 -08:00
|
|
|
.unwrap(),
|
|
|
|
None
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
inactive
|
|
|
|
.clone()
|
2021-05-20 19:23:36 -07:00
|
|
|
.merge(&invoke_context, activation_epoch.clone(), None)
|
2021-01-21 09:59:24 -08:00
|
|
|
.unwrap(),
|
2020-11-19 13:44:16 -08:00
|
|
|
None
|
|
|
|
);
|
2021-01-21 09:59:24 -08:00
|
|
|
assert!(inactive
|
|
|
|
.clone()
|
2021-05-20 19:23:36 -07:00
|
|
|
.merge(&invoke_context, fully_active.clone(), None)
|
2021-01-21 09:59:24 -08:00
|
|
|
.is_err());
|
2020-11-19 13:44:16 -08:00
|
|
|
assert!(activation_epoch
|
|
|
|
.clone()
|
2021-05-20 19:23:36 -07:00
|
|
|
.merge(&invoke_context, fully_active.clone(), None)
|
2021-01-21 09:59:24 -08:00
|
|
|
.is_err());
|
|
|
|
assert!(fully_active
|
|
|
|
.clone()
|
2021-05-20 19:23:36 -07:00
|
|
|
.merge(&invoke_context, inactive.clone(), None)
|
2020-11-19 13:44:16 -08:00
|
|
|
.is_err());
|
|
|
|
assert!(fully_active
|
|
|
|
.clone()
|
2021-05-20 19:23:36 -07:00
|
|
|
.merge(&invoke_context, activation_epoch.clone(), None)
|
2020-11-19 13:44:16 -08:00
|
|
|
.is_err());
|
|
|
|
|
2021-01-21 09:59:24 -08:00
|
|
|
let new_state = activation_epoch
|
|
|
|
.clone()
|
2021-05-20 19:23:36 -07:00
|
|
|
.merge(&invoke_context, inactive, None)
|
2021-01-21 09:59:24 -08:00
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
2020-11-19 13:44:16 -08:00
|
|
|
let delegation = new_state.delegation().unwrap();
|
|
|
|
assert_eq!(delegation.stake, stake.delegation.stake + lamports);
|
|
|
|
|
|
|
|
let new_state = activation_epoch
|
|
|
|
.clone()
|
2021-05-20 19:23:36 -07:00
|
|
|
.merge(&invoke_context, activation_epoch, None)
|
2020-11-19 13:44:16 -08:00
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
|
|
|
let delegation = new_state.delegation().unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
delegation.stake,
|
|
|
|
2 * stake.delegation.stake + meta.rent_exempt_reserve
|
|
|
|
);
|
|
|
|
|
2021-01-21 09:59:24 -08:00
|
|
|
let new_state = fully_active
|
|
|
|
.clone()
|
2021-05-20 19:23:36 -07:00
|
|
|
.merge(&invoke_context, fully_active, None)
|
2021-01-21 09:59:24 -08:00
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
2020-11-19 13:44:16 -08:00
|
|
|
let delegation = new_state.delegation().unwrap();
|
|
|
|
assert_eq!(delegation.stake, 2 * stake.delegation.stake);
|
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|