2019-04-01 16:45:53 -07:00
|
|
|
//! Stake state
|
|
|
|
//! * delegate stakes to vote accounts
|
|
|
|
//! * keep track of rewards
|
|
|
|
//! * own mining pools
|
|
|
|
|
2019-09-09 18:17:32 -07:00
|
|
|
use crate::{config::Config, id, stake_instruction::StakeError};
|
2019-04-01 16:45:53 -07:00
|
|
|
use serde_derive::{Deserialize, Serialize};
|
2019-08-12 20:59:57 -07:00
|
|
|
use solana_sdk::{
|
|
|
|
account::{Account, KeyedAccount},
|
|
|
|
account_utils::State,
|
2019-09-06 14:30:56 -07:00
|
|
|
clock::{Epoch, Slot},
|
2019-08-12 20:59:57 -07:00
|
|
|
instruction::InstructionError,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
sysvar::{
|
|
|
|
self,
|
|
|
|
stake_history::{StakeHistory, StakeHistoryEntry},
|
|
|
|
},
|
|
|
|
};
|
2019-04-01 16:45:53 -07:00
|
|
|
use solana_vote_api::vote_state::VoteState;
|
|
|
|
|
2019-06-14 11:38:37 -07:00
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
2019-09-09 18:17:32 -07:00
|
|
|
#[allow(clippy::large_enum_variant)]
|
2019-04-01 16:45:53 -07:00
|
|
|
pub enum StakeState {
|
2019-05-21 07:32:38 -07:00
|
|
|
Uninitialized,
|
2019-09-12 19:03:28 -07:00
|
|
|
Lockup(Lockup),
|
2019-06-17 19:34:21 -07:00
|
|
|
Stake(Stake),
|
2019-06-20 12:22:29 -07:00
|
|
|
RewardsPool,
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for StakeState {
|
|
|
|
fn default() -> Self {
|
2019-05-21 07:32:38 -07:00
|
|
|
StakeState::Uninitialized
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|
|
|
|
}
|
2019-04-07 21:45:28 -07:00
|
|
|
|
|
|
|
impl StakeState {
|
2019-05-16 08:23:31 -07:00
|
|
|
// utility function, used by Stakes, tests
|
|
|
|
pub fn from(account: &Account) -> Option<StakeState> {
|
|
|
|
account.state().ok()
|
|
|
|
}
|
|
|
|
|
2019-06-19 11:54:52 -07:00
|
|
|
pub fn stake_from(account: &Account) -> Option<Stake> {
|
2019-06-17 19:34:21 -07:00
|
|
|
Self::from(account).and_then(|state: Self| state.stake())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn stake(&self) -> Option<Stake> {
|
|
|
|
match self {
|
|
|
|
StakeState::Stake(stake) => Some(stake.clone()),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
|
|
pub struct Lockup {
|
|
|
|
/// slot height at which this stake will allow withdrawal
|
|
|
|
pub slot: Slot,
|
|
|
|
/// alternate signer that is enabled to act on the Stake account after lockup
|
|
|
|
pub authorized_pubkey: Pubkey,
|
|
|
|
}
|
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
|
|
pub struct Stake {
|
2019-09-12 19:03:28 -07:00
|
|
|
/// alternate signer that is enabled to act on the Stake account
|
|
|
|
pub authorized_pubkey: Pubkey,
|
2019-09-09 18:17:32 -07:00
|
|
|
/// most recently delegated vote account pubkey
|
2019-06-21 20:43:24 -07:00
|
|
|
pub voter_pubkey: Pubkey,
|
2019-09-09 18:17:32 -07:00
|
|
|
/// the epoch when voter_pubkey was most recently set
|
|
|
|
pub voter_pubkey_epoch: Epoch,
|
|
|
|
/// credits observed is credits from vote account state when delegated or redeemed
|
2019-06-21 20:43:24 -07:00
|
|
|
pub credits_observed: u64,
|
2019-09-09 18:17:32 -07:00
|
|
|
/// activated stake amount, set at delegate_stake() time
|
|
|
|
pub stake: u64,
|
|
|
|
/// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
|
|
|
|
pub activation_epoch: Epoch,
|
|
|
|
/// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
|
|
|
|
pub deactivation_epoch: Epoch,
|
|
|
|
/// stake config (warmup, etc.)
|
2019-08-15 14:35:48 -07:00
|
|
|
pub config: Config,
|
2019-09-09 18:17:32 -07:00
|
|
|
/// the Slot at which this stake becomes available for withdrawal
|
2019-09-04 13:34:09 -07:00
|
|
|
pub lockup: Slot,
|
2019-09-09 18:17:32 -07:00
|
|
|
/// history of prior delegates and the epoch ranges for which
|
|
|
|
/// they were set, circular buffer
|
|
|
|
pub prior_delegates: [(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES],
|
|
|
|
/// next pointer
|
|
|
|
pub prior_delegates_idx: usize,
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
2019-08-12 20:59:57 -07:00
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
const MAX_PRIOR_DELEGATES: usize = 32; // this is how many epochs a stake is exposed to a slashing condition
|
2019-09-09 18:17:32 -07:00
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
impl Default for Stake {
|
|
|
|
fn default() -> Self {
|
2019-08-12 20:59:57 -07:00
|
|
|
Self {
|
2019-09-12 19:03:28 -07:00
|
|
|
authorized_pubkey: Pubkey::default(),
|
2019-06-21 20:43:24 -07:00
|
|
|
voter_pubkey: Pubkey::default(),
|
2019-09-09 18:17:32 -07:00
|
|
|
voter_pubkey_epoch: 0,
|
2019-06-21 20:43:24 -07:00
|
|
|
credits_observed: 0,
|
|
|
|
stake: 0,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 0,
|
|
|
|
deactivation_epoch: std::u64::MAX,
|
2019-08-15 14:35:48 -07:00
|
|
|
config: Config::default(),
|
2019-09-04 13:34:09 -07:00
|
|
|
lockup: 0,
|
2019-09-09 18:17:32 -07:00
|
|
|
prior_delegates: <[(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES]>::default(),
|
2019-09-11 09:48:29 -07:00
|
|
|
prior_delegates_idx: MAX_PRIOR_DELEGATES - 1,
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Stake {
|
2019-08-18 15:41:49 -07:00
|
|
|
fn is_bootstrap(&self) -> bool {
|
2019-08-17 18:12:30 -07:00
|
|
|
self.activation_epoch == std::u64::MAX
|
2019-08-12 20:59:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
2019-08-17 18:12:30 -07:00
|
|
|
self.stake_activating_and_deactivating(epoch, history).0
|
2019-08-12 20:59:57 -07:00
|
|
|
}
|
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
pub fn voter_pubkey(&self, epoch: Epoch) -> &Pubkey {
|
|
|
|
let prior_delegate_pubkey = &self.prior_delegates[self.prior_delegates_idx].0;
|
|
|
|
// next epoch from re-delegation, or no redelegations
|
|
|
|
if epoch > self.voter_pubkey_epoch || *prior_delegate_pubkey == Pubkey::default() {
|
|
|
|
&self.voter_pubkey
|
|
|
|
} else {
|
|
|
|
assert!(epoch <= self.prior_delegates[self.prior_delegates_idx].2);
|
|
|
|
prior_delegate_pubkey
|
|
|
|
}
|
2019-09-09 18:17:32 -07:00
|
|
|
}
|
|
|
|
|
2019-08-18 15:41:49 -07:00
|
|
|
fn stake_activating_and_deactivating(
|
2019-08-17 18:12:30 -07:00
|
|
|
&self,
|
|
|
|
epoch: Epoch,
|
|
|
|
history: Option<&StakeHistory>,
|
|
|
|
) -> (u64, u64, u64) {
|
|
|
|
// first, calculate an effective stake and activating number
|
|
|
|
let (stake, activating) = self.stake_and_activating(epoch, history);
|
|
|
|
|
|
|
|
// then de-activate some portion if necessary
|
|
|
|
if epoch < self.deactivation_epoch {
|
|
|
|
(stake, activating, 0) // not deactivated
|
|
|
|
} else if epoch == self.deactivation_epoch {
|
|
|
|
(stake, 0, stake.min(self.stake)) // can only deactivate what's activated
|
|
|
|
} else if let Some((history, mut entry)) = history.and_then(|history| {
|
|
|
|
history
|
|
|
|
.get(&self.deactivation_epoch)
|
|
|
|
.map(|entry| (history, entry))
|
|
|
|
}) {
|
|
|
|
// && epoch > self.deactivation_epoch
|
|
|
|
let mut effective_stake = stake;
|
|
|
|
let mut next_epoch = self.deactivation_epoch;
|
|
|
|
|
|
|
|
// loop from my activation epoch until the current epoch
|
|
|
|
// summing up my entitlement
|
|
|
|
loop {
|
|
|
|
if entry.deactivating == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// I'm trying to get to zero, how much of the deactivation in stake
|
|
|
|
// this account is entitled to take
|
|
|
|
let weight = effective_stake as f64 / entry.deactivating as f64;
|
|
|
|
|
|
|
|
// portion of activating stake in this epoch I'm entitled to
|
|
|
|
effective_stake = effective_stake.saturating_sub(
|
|
|
|
((weight * entry.effective as f64 * self.config.cooldown_rate) as u64).max(1),
|
|
|
|
);
|
|
|
|
|
|
|
|
if effective_stake == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_epoch += 1;
|
|
|
|
if next_epoch >= epoch {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if let Some(next_entry) = history.get(&next_epoch) {
|
|
|
|
entry = next_entry;
|
2019-08-12 20:59:57 -07:00
|
|
|
} else {
|
2019-08-17 18:12:30 -07:00
|
|
|
break;
|
2019-08-12 20:59:57 -07:00
|
|
|
}
|
|
|
|
}
|
2019-08-17 18:12:30 -07:00
|
|
|
(effective_stake, 0, effective_stake)
|
2019-06-21 20:43:24 -07:00
|
|
|
} else {
|
2019-08-17 18:12:30 -07:00
|
|
|
// no history or I've dropped out of history, so fully deactivated
|
|
|
|
(0, 0, 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn stake_and_activating(&self, epoch: Epoch, history: Option<&StakeHistory>) -> (u64, u64) {
|
|
|
|
if self.is_bootstrap() {
|
|
|
|
(self.stake, 0)
|
|
|
|
} else if epoch == self.activation_epoch {
|
|
|
|
(0, self.stake)
|
|
|
|
} else if epoch < self.activation_epoch {
|
2019-08-12 20:59:57 -07:00
|
|
|
(0, 0)
|
2019-08-17 18:12:30 -07:00
|
|
|
} else if let Some((history, mut entry)) = history.and_then(|history| {
|
|
|
|
history
|
|
|
|
.get(&self.activation_epoch)
|
|
|
|
.map(|entry| (history, entry))
|
|
|
|
}) {
|
|
|
|
// && !is_bootstrap() && epoch > self.activation_epoch
|
|
|
|
let mut effective_stake = 0;
|
|
|
|
let mut next_epoch = self.activation_epoch;
|
|
|
|
|
|
|
|
// loop from my activation epoch until the current epoch
|
|
|
|
// summing up my entitlement
|
|
|
|
loop {
|
|
|
|
if entry.activating == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// how much of the growth in stake this account is
|
|
|
|
// entitled to take
|
|
|
|
let weight = (self.stake - effective_stake) as f64 / entry.activating as f64;
|
|
|
|
|
|
|
|
// portion of activating stake in this epoch I'm entitled to
|
|
|
|
effective_stake +=
|
|
|
|
((weight * entry.effective as f64 * self.config.warmup_rate) as u64).max(1);
|
|
|
|
|
|
|
|
if effective_stake >= self.stake {
|
|
|
|
effective_stake = self.stake;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_epoch += 1;
|
|
|
|
if next_epoch >= epoch {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if let Some(next_entry) = history.get(&next_epoch) {
|
|
|
|
entry = next_entry;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(effective_stake, self.stake - effective_stake)
|
|
|
|
} else {
|
|
|
|
// no history or I've dropped out of history, so assume fully activated
|
|
|
|
(self.stake, 0)
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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:
|
|
|
|
/// * voter_rewards to be distributed
|
|
|
|
/// * staker_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
|
2019-08-18 15:41:49 -07:00
|
|
|
fn calculate_rewards(
|
2019-06-21 20:43:24 -07:00
|
|
|
&self,
|
|
|
|
point_value: f64,
|
2019-04-07 21:45:28 -07:00
|
|
|
vote_state: &VoteState,
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_history: Option<&StakeHistory>,
|
2019-06-21 20:43:24 -07:00
|
|
|
) -> Option<(u64, u64, u64)> {
|
|
|
|
if self.credits_observed >= vote_state.credits() {
|
2019-04-07 21:45:28 -07:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
let mut credits_observed = self.credits_observed;
|
|
|
|
let mut total_rewards = 0f64;
|
|
|
|
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
|
|
|
|
// figure out how much this stake has seen that
|
|
|
|
// for which the vote account has a record
|
|
|
|
let epoch_credits = if self.credits_observed < *prev_credits {
|
|
|
|
// the staker observed the entire epoch
|
|
|
|
credits - prev_credits
|
|
|
|
} else if self.credits_observed < *credits {
|
|
|
|
// the staker registered sometime during the epoch, partial credit
|
|
|
|
credits - credits_observed
|
|
|
|
} else {
|
|
|
|
// the staker has already observed/redeemed this epoch, or activated
|
|
|
|
// after this epoch
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
2019-08-12 20:59:57 -07:00
|
|
|
total_rewards +=
|
|
|
|
(self.stake(*epoch, stake_history) * epoch_credits) as f64 * point_value;
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
// don't want to assume anything about order of the iterator...
|
2019-08-12 20:59:57 -07:00
|
|
|
credits_observed = credits_observed.max(*credits);
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
2019-04-07 21:45:28 -07:00
|
|
|
// don't bother trying to collect fractional lamports
|
|
|
|
if total_rewards < 1f64 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(total_rewards);
|
|
|
|
|
|
|
|
if (voter_rewards < 1f64 || staker_rewards < 1f64) && is_split {
|
|
|
|
// don't bother trying to collect fractional lamports
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
Some((
|
|
|
|
voter_rewards as u64,
|
|
|
|
staker_rewards as u64,
|
|
|
|
credits_observed,
|
|
|
|
))
|
2019-04-07 21:45:28 -07:00
|
|
|
}
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2019-08-12 20:59:57 -07:00
|
|
|
fn new_bootstrap(stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState) -> Self {
|
2019-08-18 15:41:49 -07:00
|
|
|
Self::new(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake,
|
2019-09-12 19:03:28 -07:00
|
|
|
&Pubkey::default(),
|
2019-08-18 15:41:49 -07:00
|
|
|
voter_pubkey,
|
|
|
|
vote_state,
|
|
|
|
std::u64::MAX,
|
|
|
|
&Config::default(),
|
2019-09-04 13:34:09 -07:00
|
|
|
0,
|
2019-08-18 15:41:49 -07:00
|
|
|
)
|
2019-08-12 20:59:57 -07:00
|
|
|
}
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
fn redelegate(
|
|
|
|
&mut self,
|
|
|
|
voter_pubkey: &Pubkey,
|
|
|
|
vote_state: &VoteState,
|
|
|
|
epoch: Epoch,
|
|
|
|
) -> Result<(), StakeError> {
|
|
|
|
// remember old delegate,
|
|
|
|
if epoch != self.voter_pubkey_epoch {
|
|
|
|
self.prior_delegates_idx += 1;
|
|
|
|
self.prior_delegates_idx %= MAX_PRIOR_DELEGATES;
|
|
|
|
|
|
|
|
self.prior_delegates[self.prior_delegates_idx] =
|
|
|
|
(self.voter_pubkey, self.voter_pubkey_epoch, epoch);
|
|
|
|
}
|
|
|
|
self.voter_pubkey = *voter_pubkey;
|
|
|
|
self.voter_pubkey_epoch = epoch;
|
|
|
|
self.credits_observed = vote_state.credits();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-15 14:35:48 -07:00
|
|
|
fn new(
|
|
|
|
stake: u64,
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_pubkey: &Pubkey,
|
2019-08-15 14:35:48 -07:00
|
|
|
voter_pubkey: &Pubkey,
|
|
|
|
vote_state: &VoteState,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: Epoch,
|
2019-08-15 14:35:48 -07:00
|
|
|
config: &Config,
|
2019-09-04 13:34:09 -07:00
|
|
|
lockup: Slot,
|
2019-08-15 14:35:48 -07:00
|
|
|
) -> Self {
|
2019-08-12 20:59:57 -07:00
|
|
|
Self {
|
|
|
|
stake,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch,
|
2019-09-12 19:03:28 -07:00
|
|
|
authorized_pubkey: *stake_pubkey,
|
2019-08-12 20:59:57 -07:00
|
|
|
voter_pubkey: *voter_pubkey,
|
2019-09-11 09:48:29 -07:00
|
|
|
voter_pubkey_epoch: activation_epoch,
|
2019-08-12 20:59:57 -07:00
|
|
|
credits_observed: vote_state.credits(),
|
2019-08-15 14:35:48 -07:00
|
|
|
config: *config,
|
2019-09-04 13:34:09 -07:00
|
|
|
lockup,
|
2019-08-12 20:59:57 -07:00
|
|
|
..Stake::default()
|
|
|
|
}
|
2019-06-17 19:34:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn deactivate(&mut self, epoch: u64) {
|
2019-08-17 18:12:30 -07:00
|
|
|
self.deactivation_epoch = epoch;
|
2019-06-17 19:34:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
trait Authorized {
|
|
|
|
fn check_authorized(
|
|
|
|
&self,
|
|
|
|
stake_pubkey_signer: Option<&Pubkey>,
|
|
|
|
other_signers: &[KeyedAccount],
|
|
|
|
) -> Result<(), InstructionError>;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Authorized for Lockup {
|
|
|
|
fn check_authorized(
|
|
|
|
&self,
|
|
|
|
stake_pubkey_signer: Option<&Pubkey>,
|
|
|
|
other_signers: &[KeyedAccount],
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
let authorized = Some(&self.authorized_pubkey);
|
|
|
|
if stake_pubkey_signer != authorized
|
|
|
|
&& other_signers
|
|
|
|
.iter()
|
|
|
|
.all(|account| account.signer_key() != authorized)
|
|
|
|
{
|
|
|
|
return Err(InstructionError::MissingRequiredSignature);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Authorized for Stake {
|
|
|
|
fn check_authorized(
|
|
|
|
&self,
|
|
|
|
stake_pubkey_signer: Option<&Pubkey>,
|
|
|
|
other_signers: &[KeyedAccount],
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
let authorized = Some(&self.authorized_pubkey);
|
|
|
|
if stake_pubkey_signer != authorized
|
|
|
|
&& other_signers
|
|
|
|
.iter()
|
|
|
|
.all(|account| account.signer_key() != authorized)
|
|
|
|
{
|
|
|
|
return Err(InstructionError::MissingRequiredSignature);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-01 16:45:53 -07:00
|
|
|
pub trait StakeAccount {
|
2019-09-04 13:34:09 -07:00
|
|
|
fn lockup(&mut self, slot: Slot) -> Result<(), InstructionError>;
|
2019-09-12 19:03:28 -07:00
|
|
|
fn authorize(
|
|
|
|
&mut self,
|
|
|
|
authorized_pubkey: &Pubkey,
|
|
|
|
other_signers: &[KeyedAccount],
|
|
|
|
) -> Result<(), InstructionError>;
|
2019-06-10 12:17:29 -07:00
|
|
|
fn delegate_stake(
|
|
|
|
&mut self,
|
|
|
|
vote_account: &KeyedAccount,
|
2019-07-12 16:38:15 -07:00
|
|
|
clock: &sysvar::clock::Clock,
|
2019-08-15 14:35:48 -07:00
|
|
|
config: &Config,
|
2019-09-12 19:03:28 -07:00
|
|
|
other_signers: &[KeyedAccount],
|
2019-06-10 12:17:29 -07:00
|
|
|
) -> Result<(), InstructionError>;
|
2019-08-09 12:55:21 -07:00
|
|
|
fn deactivate_stake(
|
|
|
|
&mut self,
|
|
|
|
vote_account: &KeyedAccount,
|
|
|
|
clock: &sysvar::clock::Clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
other_signers: &[KeyedAccount],
|
2019-08-09 12:55:21 -07:00
|
|
|
) -> Result<(), InstructionError>;
|
2019-04-01 16:45:53 -07:00
|
|
|
fn redeem_vote_credits(
|
|
|
|
&mut self,
|
2019-04-07 21:45:28 -07:00
|
|
|
vote_account: &mut KeyedAccount,
|
2019-06-21 20:43:24 -07:00
|
|
|
rewards_account: &mut KeyedAccount,
|
2019-07-12 16:38:15 -07:00
|
|
|
rewards: &sysvar::rewards::Rewards,
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_history: &sysvar::stake_history::StakeHistory,
|
2019-04-01 16:45:53 -07:00
|
|
|
) -> Result<(), InstructionError>;
|
2019-06-21 22:28:34 -07:00
|
|
|
fn withdraw(
|
|
|
|
&mut self,
|
|
|
|
lamports: u64,
|
|
|
|
to: &mut KeyedAccount,
|
2019-07-12 16:38:15 -07:00
|
|
|
clock: &sysvar::clock::Clock,
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_history: &sysvar::stake_history::StakeHistory,
|
2019-09-12 19:03:28 -07:00
|
|
|
other_signers: &[KeyedAccount],
|
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-04 13:34:09 -07:00
|
|
|
fn lockup(&mut self, lockup: Slot) -> Result<(), InstructionError> {
|
|
|
|
if let StakeState::Uninitialized = self.state()? {
|
2019-09-12 19:03:28 -07:00
|
|
|
self.set_state(&StakeState::Lockup(Lockup {
|
|
|
|
slot: lockup,
|
|
|
|
authorized_pubkey: *self.unsigned_key(),
|
|
|
|
}))
|
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called
|
|
|
|
/// multiple times, but will implicitly withdraw authorization from the previously authorized
|
|
|
|
/// staker. The default staker is the owner of the stake account's pubkey.
|
|
|
|
fn authorize(
|
|
|
|
&mut self,
|
|
|
|
authorized_pubkey: &Pubkey,
|
|
|
|
other_signers: &[KeyedAccount],
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
let stake_state = self.state()?;
|
|
|
|
if let StakeState::Stake(mut stake) = stake_state {
|
|
|
|
stake.check_authorized(self.signer_key(), other_signers)?;
|
|
|
|
stake.authorized_pubkey = *authorized_pubkey;
|
|
|
|
self.set_state(&StakeState::Stake(stake))
|
|
|
|
} else if let StakeState::Lockup(mut lockup) = stake_state {
|
|
|
|
lockup.check_authorized(self.signer_key(), other_signers)?;
|
|
|
|
lockup.authorized_pubkey = *authorized_pubkey;
|
2019-09-04 13:34:09 -07:00
|
|
|
self.set_state(&StakeState::Lockup(lockup))
|
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
2019-06-10 12:17:29 -07:00
|
|
|
fn delegate_stake(
|
|
|
|
&mut self,
|
|
|
|
vote_account: &KeyedAccount,
|
2019-07-12 16:38:15 -07:00
|
|
|
clock: &sysvar::clock::Clock,
|
2019-08-15 14:35:48 -07:00
|
|
|
config: &Config,
|
2019-09-12 19:03:28 -07:00
|
|
|
other_signers: &[KeyedAccount],
|
2019-06-10 12:17:29 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2019-09-04 13:34:09 -07:00
|
|
|
if let StakeState::Lockup(lockup) = self.state()? {
|
2019-09-12 19:03:28 -07:00
|
|
|
lockup.check_authorized(self.signer_key(), other_signers)?;
|
2019-08-12 20:59:57 -07:00
|
|
|
let stake = Stake::new(
|
2019-09-11 09:48:29 -07:00
|
|
|
self.account.lamports,
|
2019-09-12 19:03:28 -07:00
|
|
|
&lockup.authorized_pubkey,
|
2019-06-17 19:34:21 -07:00
|
|
|
vote_account.unsigned_key(),
|
|
|
|
&vote_account.state()?,
|
2019-08-01 14:27:47 -07:00
|
|
|
clock.epoch,
|
2019-08-15 14:35:48 -07:00
|
|
|
config,
|
2019-09-12 19:03:28 -07:00
|
|
|
lockup.slot,
|
2019-06-17 19:34:21 -07:00
|
|
|
);
|
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
self.set_state(&StakeState::Stake(stake))
|
|
|
|
} else if let StakeState::Stake(mut stake) = self.state()? {
|
2019-09-12 19:03:28 -07:00
|
|
|
stake.check_authorized(self.signer_key(), other_signers)?;
|
2019-09-11 09:48:29 -07:00
|
|
|
stake.redelegate(
|
|
|
|
vote_account.unsigned_key(),
|
|
|
|
&vote_account.state()?,
|
|
|
|
clock.epoch,
|
|
|
|
)?;
|
2019-06-17 19:34:21 -07:00
|
|
|
self.set_state(&StakeState::Stake(stake))
|
2019-04-01 16:45:53 -07:00
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
2019-08-09 12:55:21 -07:00
|
|
|
fn deactivate_stake(
|
|
|
|
&mut self,
|
|
|
|
_vote_account: &KeyedAccount, // TODO: used in slashing
|
|
|
|
clock: &sysvar::clock::Clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
other_signers: &[KeyedAccount],
|
2019-08-09 12:55:21 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2019-06-21 20:43:24 -07:00
|
|
|
if let StakeState::Stake(mut stake) = self.state()? {
|
2019-09-12 19:03:28 -07:00
|
|
|
stake.check_authorized(self.signer_key(), other_signers)?;
|
2019-08-01 14:27:47 -07:00
|
|
|
stake.deactivate(clock.epoch);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
self.set_state(&StakeState::Stake(stake))
|
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
fn redeem_vote_credits(
|
|
|
|
&mut self,
|
2019-04-07 21:45:28 -07:00
|
|
|
vote_account: &mut KeyedAccount,
|
2019-06-21 20:43:24 -07:00
|
|
|
rewards_account: &mut KeyedAccount,
|
2019-07-12 16:38:15 -07:00
|
|
|
rewards: &sysvar::rewards::Rewards,
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_history: &sysvar::stake_history::StakeHistory,
|
2019-04-01 16:45:53 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2019-06-21 20:43:24 -07:00
|
|
|
if let (StakeState::Stake(mut stake), StakeState::RewardsPool) =
|
|
|
|
(self.state()?, rewards_account.state()?)
|
2019-04-01 16:45:53 -07:00
|
|
|
{
|
|
|
|
let vote_state: VoteState = vote_account.state()?;
|
|
|
|
|
2019-09-09 18:17:32 -07:00
|
|
|
// the only valid use of current voter_pubkey, redelegation breaks
|
|
|
|
// rewards redemption for previous voter_pubkey
|
2019-06-17 19:34:21 -07:00
|
|
|
if stake.voter_pubkey != *vote_account.unsigned_key() {
|
2019-04-01 16:45:53 -07:00
|
|
|
return Err(InstructionError::InvalidArgument);
|
|
|
|
}
|
|
|
|
|
2019-08-12 20:59:57 -07:00
|
|
|
if let Some((stakers_reward, voters_reward, credits_observed)) = stake
|
|
|
|
.calculate_rewards(
|
|
|
|
rewards.validator_point_value,
|
|
|
|
&vote_state,
|
|
|
|
Some(stake_history),
|
|
|
|
)
|
2019-06-21 20:43:24 -07:00
|
|
|
{
|
|
|
|
if rewards_account.account.lamports < (stakers_reward + voters_reward) {
|
2019-04-07 21:45:28 -07:00
|
|
|
return Err(InstructionError::UnbalancedInstruction);
|
|
|
|
}
|
2019-06-21 20:43:24 -07:00
|
|
|
rewards_account.account.lamports -= stakers_reward + voters_reward;
|
|
|
|
|
|
|
|
self.account.lamports += stakers_reward;
|
2019-04-07 21:45:28 -07:00
|
|
|
vote_account.account.lamports += voters_reward;
|
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
stake.credits_observed = credits_observed;
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
self.set_state(&StakeState::Stake(stake))
|
2019-04-07 21:45:28 -07:00
|
|
|
} else {
|
|
|
|
// not worth collecting
|
2019-09-06 10:55:03 -07:00
|
|
|
Err(StakeError::NoCreditsToRedeem.into())
|
2019-04-07 21:45:28 -07:00
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
2019-06-21 22:28:34 -07:00
|
|
|
fn withdraw(
|
|
|
|
&mut self,
|
|
|
|
lamports: u64,
|
|
|
|
to: &mut KeyedAccount,
|
2019-07-12 16:38:15 -07:00
|
|
|
clock: &sysvar::clock::Clock,
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_history: &sysvar::stake_history::StakeHistory,
|
2019-09-12 19:03:28 -07:00
|
|
|
other_signers: &[KeyedAccount],
|
2019-06-21 22:28:34 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2019-09-04 13:34:09 -07:00
|
|
|
fn transfer(
|
|
|
|
from: &mut Account,
|
|
|
|
to: &mut Account,
|
|
|
|
lamports: u64,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
if lamports > from.lamports {
|
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
from.lamports -= lamports;
|
|
|
|
to.lamports += lamports;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-06-21 22:28:34 -07:00
|
|
|
match self.state()? {
|
2019-08-12 20:59:57 -07:00
|
|
|
StakeState::Stake(stake) => {
|
2019-09-12 19:03:28 -07:00
|
|
|
stake.check_authorized(self.signer_key(), other_signers)?;
|
2019-08-17 18:12:30 -07:00
|
|
|
// if we have a deactivation epoch and we're in cooldown
|
|
|
|
let staked = if clock.epoch >= stake.deactivation_epoch {
|
2019-08-12 20:59:57 -07:00
|
|
|
stake.stake(clock.epoch, Some(stake_history))
|
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
|
|
|
|
// de-activated, because in the future the exposeed stake
|
|
|
|
// might be higher than stake.stake(), 'cuz warmup
|
2019-06-21 22:28:34 -07:00
|
|
|
stake.stake
|
|
|
|
};
|
2019-08-17 18:12:30 -07:00
|
|
|
|
2019-06-21 22:28:34 -07:00
|
|
|
if lamports > self.account.lamports.saturating_sub(staked) {
|
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
}
|
2019-09-04 13:34:09 -07:00
|
|
|
StakeState::Lockup(lockup) => {
|
2019-09-12 19:03:28 -07:00
|
|
|
lockup.check_authorized(self.signer_key(), other_signers)?;
|
|
|
|
if lockup.slot > clock.slot {
|
2019-06-21 22:28:34 -07:00
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
}
|
2019-09-12 19:03:28 -07:00
|
|
|
StakeState::Uninitialized => {
|
|
|
|
if self.signer_key().is_none() {
|
|
|
|
return Err(InstructionError::MissingRequiredSignature);
|
|
|
|
}
|
|
|
|
}
|
2019-09-04 13:34:09 -07:00
|
|
|
_ => return Err(InstructionError::InvalidAccountData),
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
2019-09-04 13:34:09 -07:00
|
|
|
transfer(&mut self.account, &mut to.account, lamports)
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|
|
|
|
|
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>,
|
|
|
|
) -> StakeHistoryEntry
|
|
|
|
where
|
|
|
|
I: Iterator<Item = &'a Stake>,
|
|
|
|
{
|
|
|
|
// 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| {
|
|
|
|
add(sum, stake.stake_activating_and_deactivating(epoch, history))
|
|
|
|
});
|
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-05-07 17:08:49 -07:00
|
|
|
// utility function, used by Bank, tests, genesis
|
2019-08-15 18:58:46 -07:00
|
|
|
pub fn create_account(voter_pubkey: &Pubkey, vote_account: &Account, lamports: u64) -> Account {
|
2019-05-07 17:08:49 -07:00
|
|
|
let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id());
|
|
|
|
|
2019-08-15 18:58:46 -07:00
|
|
|
let vote_state = VoteState::from(vote_account).expect("vote_state");
|
|
|
|
|
2019-05-07 17:08:49 -07:00
|
|
|
stake_account
|
2019-08-12 20:59:57 -07:00
|
|
|
.set_state(&StakeState::Stake(Stake::new_bootstrap(
|
|
|
|
lamports,
|
|
|
|
voter_pubkey,
|
2019-08-15 18:58:46 -07:00
|
|
|
&vote_state,
|
2019-08-12 20:59:57 -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::*;
|
|
|
|
use crate::id;
|
2019-09-11 09:48:29 -07:00
|
|
|
use solana_sdk::{account::Account, pubkey::Pubkey, system_program};
|
2019-05-21 21:45:38 -07:00
|
|
|
use solana_vote_api::vote_state;
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-08-18 15:41:49 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stake_state_stake_from_fail() {
|
|
|
|
let mut stake_account = Account::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
|
|
|
|
2019-08-18 15:41:49 -07:00
|
|
|
assert_eq!(StakeState::stake_from(&stake_account), None);
|
|
|
|
}
|
2019-08-12 20:59:57 -07:00
|
|
|
|
2019-08-18 15:41:49 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stake_is_bootstrap() {
|
|
|
|
assert_eq!(
|
|
|
|
Stake {
|
|
|
|
activation_epoch: std::u64::MAX,
|
|
|
|
..Stake::default()
|
|
|
|
}
|
|
|
|
.is_bootstrap(),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Stake {
|
|
|
|
activation_epoch: 0,
|
|
|
|
..Stake::default()
|
|
|
|
}
|
|
|
|
.is_bootstrap(),
|
|
|
|
false
|
|
|
|
);
|
2019-08-12 20:59:57 -07:00
|
|
|
}
|
|
|
|
|
2019-04-01 16:45:53 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stake_delegate_stake() {
|
2019-07-31 15:13:26 -07:00
|
|
|
let clock = sysvar::clock::Clock {
|
2019-08-01 14:27:47 -07:00
|
|
|
epoch: 1,
|
2019-07-31 15:13:26 -07:00
|
|
|
..sysvar::clock::Clock::default()
|
|
|
|
};
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
let vote_pubkey = 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
|
|
|
}
|
|
|
|
|
2019-04-10 17:52:47 -07:00
|
|
|
let mut vote_account =
|
|
|
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
2019-04-01 16:45:53 -07:00
|
|
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
|
|
|
vote_keyed_account.set_state(&vote_state).unwrap();
|
|
|
|
|
|
|
|
let stake_pubkey = Pubkey::default();
|
2019-06-10 12:17:29 -07:00
|
|
|
let stake_lamports = 42;
|
2019-09-04 13:34:09 -07:00
|
|
|
let mut stake_account = Account::new_data_with_space(
|
|
|
|
stake_lamports,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeState::Lockup(Lockup {
|
|
|
|
slot: 0,
|
|
|
|
authorized_pubkey: stake_pubkey,
|
|
|
|
}),
|
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
|
2019-04-01 16:45:53 -07:00
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
|
|
|
|
2019-05-07 17:08:49 -07:00
|
|
|
{
|
|
|
|
let stake_state: StakeState = stake_keyed_account.state().unwrap();
|
2019-09-12 19:03:28 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_state,
|
|
|
|
StakeState::Lockup(Lockup {
|
|
|
|
slot: 0,
|
|
|
|
authorized_pubkey: stake_pubkey
|
|
|
|
})
|
|
|
|
);
|
2019-05-07 17:08:49 -07:00
|
|
|
}
|
|
|
|
|
2019-04-01 16:45:53 -07:00
|
|
|
assert_eq!(
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_keyed_account.delegate_stake(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&Config::default(),
|
|
|
|
&[]
|
|
|
|
),
|
2019-04-01 16:45:53 -07:00
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
2019-06-17 19:34:21 -07:00
|
|
|
// signed keyed account
|
2019-04-01 16:45:53 -07:00
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
assert!(stake_keyed_account
|
2019-09-12 19:03:28 -07:00
|
|
|
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[])
|
2019-04-01 16:45:53 -07:00
|
|
|
.is_ok());
|
|
|
|
|
2019-06-17 19:34:21 -07:00
|
|
|
// verify that delegate_stake() looks right, compare against hand-rolled
|
2019-09-11 09:48:29 -07:00
|
|
|
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
|
2019-04-01 16:45:53 -07:00
|
|
|
assert_eq!(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake,
|
|
|
|
Stake {
|
2019-09-12 19:03:28 -07:00
|
|
|
authorized_pubkey: stake_pubkey,
|
2019-09-11 09:48:29 -07:00
|
|
|
voter_pubkey: vote_pubkey,
|
|
|
|
voter_pubkey_epoch: clock.epoch,
|
2019-06-10 12:17:29 -07:00
|
|
|
credits_observed: vote_state.credits(),
|
|
|
|
stake: stake_lamports,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: clock.epoch,
|
|
|
|
deactivation_epoch: std::u64::MAX,
|
2019-09-09 18:17:32 -07:00
|
|
|
..Stake::default()
|
2019-09-11 09:48:29 -07:00
|
|
|
}
|
2019-06-17 19:34:21 -07:00
|
|
|
);
|
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
// verify that voter_pubkey() is right for all epochs, even ones that don't count (like 0)
|
|
|
|
for epoch in 0..=clock.epoch + 1 {
|
|
|
|
assert_eq!(stake.voter_pubkey(epoch), &vote_pubkey);
|
|
|
|
}
|
2019-05-07 17:08:49 -07:00
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
// verify that delegate_stake can be called twice, 2nd is redelegate
|
|
|
|
assert!(stake_keyed_account
|
2019-09-12 19:03:28 -07:00
|
|
|
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[])
|
2019-09-11 09:48:29 -07:00
|
|
|
.is_ok());
|
|
|
|
|
|
|
|
// verify that non-stakes fail delegate_stake()
|
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
|
2019-09-12 19:03:28 -07:00
|
|
|
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[])
|
2019-04-01 16:45:53 -07:00
|
|
|
.is_err());
|
|
|
|
}
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
#[test]
|
|
|
|
fn test_stake_redelegate() {
|
|
|
|
// what a freshly delegated stake looks like
|
|
|
|
let mut stake = Stake {
|
|
|
|
voter_pubkey: Pubkey::new_rand(),
|
|
|
|
voter_pubkey_epoch: 0,
|
|
|
|
..Stake::default()
|
|
|
|
};
|
|
|
|
// verify that redelegation any number of times since first delegation works just fine,
|
|
|
|
// and that the stake is delegated to the most recent vote account
|
|
|
|
for epoch in 0..=MAX_PRIOR_DELEGATES + 1 {
|
|
|
|
let voter_pubkey = Pubkey::new_rand();
|
|
|
|
let _ignored = stake.redelegate(&voter_pubkey, &VoteState::default(), 0);
|
|
|
|
assert_eq!(stake.voter_pubkey(epoch as u64), &voter_pubkey);
|
|
|
|
}
|
|
|
|
|
|
|
|
// get a new voter_pubkey
|
|
|
|
let voter_pubkey = Pubkey::new_rand();
|
|
|
|
// save off old voter_pubkey
|
|
|
|
let prior_voter_pubkey = stake.voter_pubkey;
|
|
|
|
|
|
|
|
// actually redelegate in epoch 1
|
|
|
|
let _ignored = stake.redelegate(&voter_pubkey, &VoteState::default(), 1);
|
|
|
|
// verify that delegation is delayed
|
|
|
|
assert_eq!(stake.voter_pubkey(0 as u64), &prior_voter_pubkey);
|
|
|
|
assert_eq!(stake.voter_pubkey(1 as u64), &prior_voter_pubkey);
|
|
|
|
assert_eq!(stake.voter_pubkey(2 as u64), &voter_pubkey);
|
|
|
|
|
|
|
|
// verify that prior_delegates wraps around safely...
|
|
|
|
for epoch in 0..=MAX_PRIOR_DELEGATES + 1 {
|
|
|
|
let voter_pubkey = Pubkey::new_rand();
|
|
|
|
let prior_voter_pubkey = stake.voter_pubkey;
|
|
|
|
let _ignored = stake.redelegate(&voter_pubkey, &VoteState::default(), epoch as u64);
|
|
|
|
assert_eq!(stake.voter_pubkey(epoch as u64), &prior_voter_pubkey);
|
|
|
|
assert_eq!(stake.voter_pubkey((epoch + 1) as u64), &voter_pubkey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-18 15:41:49 -07:00
|
|
|
fn create_stake_history_from_stakes(
|
|
|
|
bootstrap: Option<u64>,
|
|
|
|
epochs: std::ops::Range<Epoch>,
|
|
|
|
stakes: &[Stake],
|
|
|
|
) -> StakeHistory {
|
|
|
|
let mut stake_history = StakeHistory::default();
|
|
|
|
|
|
|
|
let bootstrap_stake = if let Some(bootstrap) = bootstrap {
|
|
|
|
vec![Stake {
|
|
|
|
activation_epoch: std::u64::MAX,
|
|
|
|
stake: bootstrap,
|
|
|
|
..Stake::default()
|
|
|
|
}]
|
|
|
|
} else {
|
|
|
|
vec![]
|
|
|
|
};
|
|
|
|
|
|
|
|
for epoch in epochs {
|
|
|
|
let entry = new_stake_history_entry(
|
|
|
|
epoch,
|
|
|
|
stakes.iter().chain(bootstrap_stake.iter()),
|
|
|
|
Some(&stake_history),
|
|
|
|
);
|
|
|
|
stake_history.add(epoch, entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
stake_history
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_stake_activating_and_deactivating() {
|
|
|
|
let stake = Stake {
|
|
|
|
stake: 1_000,
|
|
|
|
activation_epoch: 0, // activating at zero
|
|
|
|
deactivation_epoch: 5,
|
|
|
|
..Stake::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// save this off so stake.config.warmup_rate changes don't break this test
|
|
|
|
let increment = (1_000 as f64 * stake.config.warmup_rate) as u64;
|
|
|
|
|
|
|
|
let mut stake_history = StakeHistory::default();
|
|
|
|
// assert that this stake follows step function if there's no history
|
|
|
|
assert_eq!(
|
|
|
|
stake.stake_activating_and_deactivating(stake.activation_epoch, Some(&stake_history)),
|
|
|
|
(0, stake.stake, 0)
|
|
|
|
);
|
|
|
|
for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
|
|
|
|
assert_eq!(
|
|
|
|
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)),
|
|
|
|
(stake.stake, 0, 0)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// assert that this stake is full deactivating
|
|
|
|
assert_eq!(
|
|
|
|
stake.stake_activating_and_deactivating(stake.deactivation_epoch, Some(&stake_history)),
|
|
|
|
(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,
|
|
|
|
Some(&stake_history)
|
|
|
|
),
|
|
|
|
(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!(
|
|
|
|
stake.stake_activating_and_deactivating(1, Some(&stake_history)),
|
|
|
|
(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!(
|
|
|
|
stake.stake_activating_and_deactivating(2, Some(&stake_history)),
|
|
|
|
(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,
|
|
|
|
Some(&stake_history)
|
|
|
|
),
|
|
|
|
(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,
|
|
|
|
Some(&stake_history)
|
|
|
|
),
|
|
|
|
(stake.stake - increment, 0, stake.stake - increment) // hung, should be lower
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_stake_warmup_cooldown_sub_integer_moves() {
|
|
|
|
let stakes = [Stake {
|
|
|
|
stake: 2,
|
|
|
|
activation_epoch: 0, // activating at zero
|
|
|
|
deactivation_epoch: 5,
|
|
|
|
..Stake::default()
|
|
|
|
}];
|
|
|
|
// give 2 epochs of cooldown
|
|
|
|
let epochs = 7;
|
|
|
|
// make boostrap stake smaller than warmup so warmup/cooldownn
|
|
|
|
// increment is always smaller than 1
|
|
|
|
let bootstrap = (stakes[0].config.warmup_rate * 100.0 / 2.0) as u64;
|
|
|
|
let stake_history = create_stake_history_from_stakes(Some(bootstrap), 0..epochs, &stakes);
|
|
|
|
let mut max_stake = 0;
|
|
|
|
let mut min_stake = 2;
|
|
|
|
|
|
|
|
for epoch in 0..epochs {
|
|
|
|
let stake = stakes
|
|
|
|
.iter()
|
|
|
|
.map(|stake| stake.stake(epoch, Some(&stake_history)))
|
|
|
|
.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-08-12 20:59:57 -07:00
|
|
|
let stakes = [
|
|
|
|
Stake {
|
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-08-12 20:59:57 -07:00
|
|
|
..Stake::default()
|
|
|
|
},
|
|
|
|
Stake {
|
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 0,
|
|
|
|
deactivation_epoch: 9,
|
2019-08-12 20:59:57 -07:00
|
|
|
..Stake::default()
|
|
|
|
},
|
|
|
|
Stake {
|
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 1,
|
|
|
|
deactivation_epoch: 6,
|
2019-08-12 20:59:57 -07:00
|
|
|
..Stake::default()
|
|
|
|
},
|
|
|
|
Stake {
|
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 2,
|
|
|
|
deactivation_epoch: 5,
|
2019-08-12 20:59:57 -07:00
|
|
|
..Stake::default()
|
|
|
|
},
|
|
|
|
Stake {
|
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 2,
|
|
|
|
deactivation_epoch: 4,
|
2019-08-12 20:59:57 -07:00
|
|
|
..Stake::default()
|
|
|
|
},
|
|
|
|
Stake {
|
|
|
|
stake: 1_000,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 4,
|
|
|
|
deactivation_epoch: 4,
|
2019-08-12 20:59:57 -07:00
|
|
|
..Stake::default()
|
|
|
|
},
|
|
|
|
];
|
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;
|
|
|
|
|
|
|
|
let stake_history = create_stake_history_from_stakes(None, 0..epochs, &stakes);
|
|
|
|
|
|
|
|
let mut prev_total_effective_stake = stakes
|
|
|
|
.iter()
|
|
|
|
.map(|stake| stake.stake(0, Some(&stake_history)))
|
|
|
|
.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-08-12 20:59:57 -07:00
|
|
|
let total_effective_stake = stakes
|
|
|
|
.iter()
|
|
|
|
.map(|stake| stake.stake(epoch, Some(&stake_history)))
|
|
|
|
.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
|
|
|
|
<= ((prev_total_effective_stake as f64 * Config::default().warmup_rate) as u64)
|
|
|
|
.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-09-04 13:34:09 -07:00
|
|
|
fn test_stake_lockup() {
|
2019-06-21 23:45:03 -07:00
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let mut stake_account =
|
|
|
|
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
|
|
|
|
|
2019-09-04 13:34:09 -07:00
|
|
|
// unsigned keyed account
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
|
|
|
assert_eq!(stake_keyed_account.lockup(1), Ok(()));
|
|
|
|
|
|
|
|
// first time works, as is uninit
|
|
|
|
assert_eq!(
|
|
|
|
StakeState::from(&stake_keyed_account.account).unwrap(),
|
2019-09-12 19:03:28 -07:00
|
|
|
StakeState::Lockup(Lockup {
|
|
|
|
slot: 1,
|
|
|
|
authorized_pubkey: stake_pubkey
|
|
|
|
})
|
2019-09-04 13:34:09 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
// 2nd time fails, can't move it from anything other than uninit->lockup
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.lockup(1),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_deactivate_stake() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let mut stake_account = Account::new_data_with_space(
|
|
|
|
stake_lamports,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeState::Lockup(Lockup {
|
|
|
|
slot: 0,
|
|
|
|
authorized_pubkey: stake_pubkey,
|
|
|
|
}),
|
2019-09-04 13:34:09 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2019-07-31 15:13:26 -07:00
|
|
|
let clock = sysvar::clock::Clock {
|
2019-08-01 14:27:47 -07:00
|
|
|
epoch: 1,
|
2019-07-31 15:13:26 -07:00
|
|
|
..sysvar::clock::Clock::default()
|
|
|
|
};
|
2019-06-21 23:45:03 -07:00
|
|
|
|
2019-08-09 12:55:21 -07:00
|
|
|
let vote_pubkey = Pubkey::new_rand();
|
|
|
|
let mut vote_account =
|
|
|
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
|
|
|
|
2019-06-21 23:45:03 -07:00
|
|
|
// signed keyed account but not staked yet
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
assert_eq!(
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]),
|
2019-06-21 23:45:03 -07:00
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Staking
|
|
|
|
let vote_pubkey = Pubkey::new_rand();
|
|
|
|
let mut vote_account =
|
|
|
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
|
|
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
|
|
|
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
|
|
|
assert_eq!(
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_keyed_account.delegate_stake(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&Config::default(),
|
|
|
|
&[]
|
|
|
|
),
|
2019-06-21 23:45:03 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
// unsigned keyed account
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
2019-06-21 23:45:03 -07:00
|
|
|
// Deactivate after staking
|
2019-09-12 19:03:28 -07:00
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
2019-08-09 12:55:21 -07:00
|
|
|
assert_eq!(
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]),
|
2019-08-09 12:55:21 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2019-06-21 23:45:03 -07:00
|
|
|
}
|
|
|
|
|
2019-06-21 22:28:34 -07:00
|
|
|
#[test]
|
|
|
|
fn test_withdraw_stake() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2019-09-04 13:34:09 -07:00
|
|
|
let mut stake_account = Account::new_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
|
|
|
|
2019-07-31 15:13:26 -07:00
|
|
|
let mut clock = sysvar::clock::Clock::default();
|
2019-06-21 22:28:34 -07:00
|
|
|
|
|
|
|
let to = Pubkey::new_rand();
|
|
|
|
let mut to_account = Account::new(1, 0, &system_program::id());
|
|
|
|
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
|
|
|
|
|
2019-08-07 20:29:22 -07:00
|
|
|
// unsigned keyed account should fail
|
2019-06-21 22:28:34 -07:00
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake_lamports,
|
2019-08-12 20:59:57 -07:00
|
|
|
&mut to_keyed_account,
|
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
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
|
2019-06-21 22:28:34 -07:00
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
stake_lamports,
|
2019-08-12 20:59:57 -07:00
|
|
|
&mut to_keyed_account,
|
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
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
|
|
|
);
|
2019-08-07 20:29:22 -07:00
|
|
|
assert_eq!(stake_account.lamports, 0);
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2019-08-07 20:29:22 -07:00
|
|
|
// reset balance
|
2019-09-11 09:48:29 -07:00
|
|
|
stake_account.lamports = stake_lamports;
|
2019-08-07 20:29:22 -07:00
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
// lockup
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
stake_keyed_account.lockup(0).unwrap();
|
|
|
|
|
|
|
|
// signed keyed account and locked up, more than available should fail
|
2019-06-21 22:28:34 -07:00
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
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,
|
2019-08-12 20:59:57 -07:00
|
|
|
&mut to_keyed_account,
|
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
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)
|
2019-06-21 22:28:34 -07:00
|
|
|
let vote_pubkey = Pubkey::new_rand();
|
|
|
|
let mut vote_account =
|
|
|
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
|
|
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
|
|
|
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
|
|
|
assert_eq!(
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_keyed_account.delegate_stake(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&Config::default(),
|
|
|
|
&[]
|
|
|
|
),
|
2019-06-21 22:28:34 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
2019-09-11 09:48:29 -07:00
|
|
|
// simulate rewards
|
|
|
|
stake_account.lamports += 10;
|
|
|
|
// withdrawal before deactivate works for rewards amount
|
2019-08-07 20:29:22 -07:00
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
2019-09-11 09:48:29 -07:00
|
|
|
10,
|
2019-06-21 22:28:34 -07:00
|
|
|
&mut to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
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
|
|
|
|
stake_account.lamports += 10;
|
|
|
|
// withdrawal of rewards fails if not in excess of stake
|
2019-08-07 20:29:22 -07:00
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_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,
|
2019-06-21 22:28:34 -07:00
|
|
|
&mut to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
2019-06-21 22:28:34 -07:00
|
|
|
),
|
2019-08-07 20:29:22 -07:00
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// deactivate the stake before withdrawal
|
2019-08-09 12:55:21 -07:00
|
|
|
assert_eq!(
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]),
|
2019-08-09 12:55:21 -07:00
|
|
|
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
|
|
|
|
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,
|
2019-08-12 20:59:57 -07:00
|
|
|
&mut to_keyed_account,
|
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-08-07 20:29:22 -07:00
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Try to withdraw all lamports
|
|
|
|
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,
|
2019-08-12 20:59:57 -07:00
|
|
|
&mut to_keyed_account,
|
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-06-21 22:28:34 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2019-08-07 20:29:22 -07:00
|
|
|
assert_eq!(stake_account.lamports, 0);
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_withdraw_stake_before_warmup() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let total_lamports = 100;
|
|
|
|
let stake_lamports = 42;
|
2019-09-04 13:34:09 -07:00
|
|
|
let mut stake_account = Account::new_data_with_space(
|
|
|
|
total_lamports,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeState::Lockup(Lockup {
|
|
|
|
slot: 0,
|
|
|
|
authorized_pubkey: 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
|
|
|
|
2019-07-12 16:38:15 -07:00
|
|
|
let clock = sysvar::clock::Clock::default();
|
|
|
|
let mut future = sysvar::clock::Clock::default();
|
2019-08-01 14:27:47 -07:00
|
|
|
future.epoch += 16;
|
2019-06-21 22:28:34 -07:00
|
|
|
|
|
|
|
let to = Pubkey::new_rand();
|
|
|
|
let mut to_account = Account::new(1, 0, &system_program::id());
|
|
|
|
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
|
|
|
|
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
|
2019-08-12 20:59:57 -07:00
|
|
|
// Stake some lamports (available lampoorts for withdrawals will reduce)
|
2019-06-21 22:28:34 -07:00
|
|
|
let vote_pubkey = Pubkey::new_rand();
|
|
|
|
let mut vote_account =
|
|
|
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
|
|
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
|
|
|
vote_keyed_account.set_state(&VoteState::default()).unwrap();
|
|
|
|
assert_eq!(
|
2019-09-12 19:03:28 -07:00
|
|
|
stake_keyed_account.delegate_stake(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&future,
|
|
|
|
&Config::default(),
|
|
|
|
&[]
|
|
|
|
),
|
2019-06-21 22:28:34 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
2019-08-12 20:59:57 -07:00
|
|
|
let stake_history = create_stake_history_from_stakes(
|
|
|
|
None,
|
|
|
|
0..future.epoch,
|
|
|
|
&[StakeState::stake_from(&stake_keyed_account.account).unwrap()],
|
|
|
|
);
|
|
|
|
|
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,
|
|
|
|
&mut to_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&stake_history,
|
|
|
|
&[],
|
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() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let total_lamports = 100;
|
2019-09-04 13:34:09 -07:00
|
|
|
let mut stake_account = Account::new_data_with_space(
|
|
|
|
total_lamports,
|
|
|
|
&StakeState::RewardsPool,
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");;
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2019-09-04 13:34:09 -07:00
|
|
|
let to = Pubkey::new_rand();
|
|
|
|
let mut to_account = Account::new(1, 0, &system_program::id());
|
|
|
|
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports,
|
|
|
|
&mut to_keyed_account,
|
|
|
|
&sysvar::clock::Clock::default(),
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
2019-09-04 13:34:09 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_withdraw_lockout() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let total_lamports = 100;
|
|
|
|
let mut stake_account = Account::new_data_with_space(
|
|
|
|
total_lamports,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeState::Lockup(Lockup {
|
|
|
|
slot: 1,
|
|
|
|
authorized_pubkey: 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
|
|
|
|
|
|
|
let to = Pubkey::new_rand();
|
|
|
|
let mut to_account = Account::new(1, 0, &system_program::id());
|
|
|
|
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
|
|
|
|
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
|
2019-09-04 13:34:09 -07:00
|
|
|
let mut clock = sysvar::clock::Clock::default();
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports,
|
|
|
|
&mut to_keyed_account,
|
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-09-04 13:34:09 -07:00
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
clock.slot += 1;
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
total_lamports,
|
|
|
|
&mut to_keyed_account,
|
|
|
|
&clock,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeHistory::default(),
|
|
|
|
&[],
|
2019-09-04 13:34:09 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
2019-06-21 22:28:34 -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
|
|
|
|
let mut stake = Stake::new_bootstrap(1, &Pubkey::default(), &vote_state);
|
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,
|
|
|
|
stake.calculate_rewards(1_000_000_000.0, &vote_state, None)
|
|
|
|
);
|
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 can't collect now, no epoch credits have been saved off
|
2019-08-12 20:59:57 -07:00
|
|
|
// even though point value is huuge
|
|
|
|
assert_eq!(
|
|
|
|
None,
|
|
|
|
stake.calculate_rewards(1_000_000_000_000.0, &vote_state, None)
|
|
|
|
);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
// put 1 credit in epoch 1, pushes the 2 above into a redeemable state
|
|
|
|
vote_state.increment_credits(1);
|
|
|
|
|
|
|
|
// this one should be able to collect exactly 2
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
Some((0, stake.stake * 2, 2)),
|
|
|
|
stake.calculate_rewards(1.0, &vote_state, None)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
stake.credits_observed = 1;
|
|
|
|
// this one should be able to collect exactly 1 (only observed one)
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
Some((0, stake.stake * 1, 2)),
|
|
|
|
stake.calculate_rewards(1.0, &vote_state, None)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
stake.credits_observed = 2;
|
|
|
|
// this one should be able to collect none because credits_observed >= credits in a
|
|
|
|
// redeemable state (the 2 credits in epoch 0)
|
2019-08-12 20:59:57 -07:00
|
|
|
assert_eq!(None, stake.calculate_rewards(1.0, &vote_state, None));
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
// put 1 credit in epoch 2, pushes the 1 for epoch 1 to redeemable
|
|
|
|
vote_state.increment_credits(2);
|
2019-08-12 20:59:57 -07:00
|
|
|
// this one should be able to collect 1 now, one credit by a stake of 1
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2019-08-12 20:59:57 -07:00
|
|
|
Some((0, stake.stake * 1, 3)),
|
|
|
|
stake.calculate_rewards(1.0, &vote_state, None)
|
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-08-12 20:59:57 -07:00
|
|
|
Some((0, stake.stake * 1 + stake.stake * 2, 3)),
|
|
|
|
stake.calculate_rewards(1.0, &vote_state, None)
|
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!(
|
2019-06-21 20:43:24 -07:00
|
|
|
None, // would be Some((0, 2 * 1 + 1 * 2, 3)),
|
2019-08-12 20:59:57 -07:00
|
|
|
stake.calculate_rewards(1.0, &vote_state, None)
|
2019-06-21 20:43:24 -07:00
|
|
|
);
|
2019-07-02 14:18:11 -07:00
|
|
|
vote_state.commission = std::u8::MAX - 1;
|
2019-06-21 20:43:24 -07:00
|
|
|
assert_eq!(
|
|
|
|
None, // would be pSome((0, 2 * 1 + 1 * 2, 3)),
|
2019-08-12 20:59:57 -07:00
|
|
|
stake.calculate_rewards(1.0, &vote_state, None)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_stake_redeem_vote_credits() {
|
2019-07-12 16:38:15 -07:00
|
|
|
let clock = sysvar::clock::Clock::default();
|
|
|
|
let mut rewards = sysvar::rewards::Rewards::default();
|
2019-06-21 20:43:24 -07:00
|
|
|
rewards.validator_point_value = 100.0;
|
2019-06-17 19:34:21 -07:00
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
let rewards_pool_pubkey = Pubkey::new_rand();
|
2019-08-15 14:35:48 -07:00
|
|
|
let mut rewards_pool_account = Account::new_data(
|
|
|
|
std::u64::MAX,
|
|
|
|
&StakeState::RewardsPool,
|
|
|
|
&crate::rewards_pools::id(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
2019-06-21 20:43:24 -07:00
|
|
|
let mut rewards_pool_keyed_account =
|
|
|
|
KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account);
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
let stake_pubkey = Pubkey::default();
|
2019-06-21 20:43:24 -07:00
|
|
|
let stake_lamports = 100;
|
2019-09-04 13:34:09 -07:00
|
|
|
let mut stake_account = Account::new_data_with_space(
|
|
|
|
stake_lamports,
|
2019-09-12 19:03:28 -07:00
|
|
|
&StakeState::Lockup(Lockup {
|
|
|
|
slot: 0,
|
|
|
|
authorized_pubkey: stake_pubkey,
|
|
|
|
}),
|
2019-09-04 13:34:09 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
2019-06-21 20:43:24 -07:00
|
|
|
|
|
|
|
let vote_pubkey = Pubkey::new_rand();
|
2019-04-10 17:52:47 -07:00
|
|
|
let mut vote_account =
|
|
|
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
2019-04-01 16:45:53 -07:00
|
|
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
// not delegated yet, deserialization fails
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.redeem_vote_credits(
|
|
|
|
&mut vote_keyed_account,
|
|
|
|
&mut rewards_pool_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&rewards,
|
|
|
|
&StakeHistory::default(),
|
2019-06-21 20:43:24 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
2019-04-01 16:45:53 -07:00
|
|
|
|
|
|
|
// delegate the stake
|
|
|
|
assert!(stake_keyed_account
|
2019-09-12 19:03:28 -07:00
|
|
|
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[])
|
2019-04-01 16:45:53 -07:00
|
|
|
.is_ok());
|
2019-08-12 20:59:57 -07:00
|
|
|
|
|
|
|
let stake_history = create_stake_history_from_stakes(
|
|
|
|
Some(100),
|
|
|
|
0..10,
|
|
|
|
&[StakeState::stake_from(&stake_keyed_account.account).unwrap()],
|
|
|
|
);
|
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
// no credits to claim
|
2019-04-01 16:45:53 -07:00
|
|
|
assert_eq!(
|
2019-06-21 20:43:24 -07:00
|
|
|
stake_keyed_account.redeem_vote_credits(
|
|
|
|
&mut vote_keyed_account,
|
|
|
|
&mut rewards_pool_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&rewards,
|
|
|
|
&stake_history,
|
2019-06-21 20:43:24 -07:00
|
|
|
),
|
2019-09-06 10:55:03 -07:00
|
|
|
Err(StakeError::NoCreditsToRedeem.into())
|
2019-05-21 07:32:38 -07:00
|
|
|
);
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-08-12 20:59:57 -07:00
|
|
|
// in this call, we've swapped rewards and vote, deserialization of rewards_pool fails
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2019-06-21 20:43:24 -07:00
|
|
|
stake_keyed_account.redeem_vote_credits(
|
|
|
|
&mut rewards_pool_keyed_account,
|
|
|
|
&mut vote_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&rewards,
|
|
|
|
&StakeHistory::default(),
|
2019-06-21 20:43:24 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
2019-04-07 21:45:28 -07:00
|
|
|
);
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-04-10 17:52:47 -07:00
|
|
|
let mut vote_account =
|
|
|
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
let mut vote_state = VoteState::from(&vote_account).unwrap();
|
|
|
|
// put in some credits in epoch 0 for which we should have a non-zero stake
|
|
|
|
for _i in 0..100 {
|
2019-08-12 20:59:57 -07:00
|
|
|
vote_state.increment_credits(1);
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
2019-08-12 20:59:57 -07:00
|
|
|
vote_state.increment_credits(2);
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
vote_state.to(&mut vote_account).unwrap();
|
|
|
|
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
// some credits to claim, but rewards pool empty (shouldn't ever happen)
|
|
|
|
rewards_pool_keyed_account.account.lamports = 1;
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.redeem_vote_credits(
|
|
|
|
&mut vote_keyed_account,
|
|
|
|
&mut rewards_pool_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&rewards,
|
|
|
|
&StakeHistory::default(),
|
2019-06-21 20:43:24 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::UnbalancedInstruction)
|
|
|
|
);
|
|
|
|
rewards_pool_keyed_account.account.lamports = std::u64::MAX;
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
// finally! some credits to claim
|
2019-04-01 16:45:53 -07:00
|
|
|
assert_eq!(
|
2019-06-21 20:43:24 -07:00
|
|
|
stake_keyed_account.redeem_vote_credits(
|
|
|
|
&mut vote_keyed_account,
|
|
|
|
&mut rewards_pool_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&rewards,
|
|
|
|
&stake_history,
|
2019-06-21 20:43:24 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
2019-04-01 16:45:53 -07:00
|
|
|
);
|
|
|
|
|
2019-06-21 20:43:24 -07:00
|
|
|
let wrong_vote_pubkey = Pubkey::new_rand();
|
|
|
|
let mut wrong_vote_keyed_account =
|
|
|
|
KeyedAccount::new(&wrong_vote_pubkey, false, &mut vote_account);
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-05-23 23:20:04 -07:00
|
|
|
// wrong voter_pubkey...
|
2019-04-01 16:45:53 -07:00
|
|
|
assert_eq!(
|
2019-06-21 20:43:24 -07:00
|
|
|
stake_keyed_account.redeem_vote_credits(
|
|
|
|
&mut wrong_vote_keyed_account,
|
|
|
|
&mut rewards_pool_keyed_account,
|
2019-08-12 20:59:57 -07:00
|
|
|
&rewards,
|
|
|
|
&stake_history,
|
2019-06-21 20:43:24 -07:00
|
|
|
),
|
2019-04-01 16:45:53 -07:00
|
|
|
Err(InstructionError::InvalidArgument)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
#[test]
|
|
|
|
fn test_authorize_lockup() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let mut stake_account = Account::new_data_with_space(
|
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Lockup(Lockup {
|
|
|
|
slot: 0,
|
|
|
|
authorized_pubkey: stake_pubkey,
|
|
|
|
}),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let to = Pubkey::new_rand();
|
|
|
|
let mut to_account = Account::new(1, 0, &system_program::id());
|
|
|
|
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
|
|
|
|
|
|
|
|
let clock = sysvar::clock::Clock::default();
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
|
|
|
|
let stake_pubkey0 = Pubkey::new_rand();
|
|
|
|
assert_eq!(stake_keyed_account.authorize(&stake_pubkey0, &[]), Ok(()));
|
|
|
|
if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap()
|
|
|
|
{
|
|
|
|
assert_eq!(lockup.authorized_pubkey, stake_pubkey0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// A second authorization signed by the stake_keyed_account should fail
|
|
|
|
let stake_pubkey1 = Pubkey::new_rand();
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize(&stake_pubkey1, &[]),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut staker_account0 = Account::new(1, 0, &system_program::id());
|
|
|
|
let staker_keyed_account0 = KeyedAccount::new(&stake_pubkey0, true, &mut staker_account0);
|
|
|
|
|
|
|
|
// Test a second authorization by the newly authorized pubkey
|
|
|
|
let stake_pubkey2 = Pubkey::new_rand();
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize(&stake_pubkey2, &[staker_keyed_account0]),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap()
|
|
|
|
{
|
|
|
|
assert_eq!(lockup.authorized_pubkey, stake_pubkey2);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut staker_account2 = Account::new(1, 0, &system_program::id());
|
|
|
|
let staker_keyed_account2 = KeyedAccount::new(&stake_pubkey2, true, &mut staker_account2);
|
|
|
|
|
|
|
|
// Test an action by the currently authorized pubkey
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.withdraw(
|
|
|
|
stake_lamports,
|
|
|
|
&mut to_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&[staker_keyed_account2],
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_authorize_delegated_stake() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let mut stake_account = Account::new_data_with_space(
|
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Lockup(Lockup {
|
|
|
|
slot: 0,
|
|
|
|
authorized_pubkey: stake_pubkey,
|
|
|
|
}),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let clock = sysvar::clock::Clock::default();
|
|
|
|
|
|
|
|
let vote_pubkey = Pubkey::new_rand();
|
|
|
|
let mut vote_account =
|
|
|
|
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
|
|
|
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
|
|
|
stake_keyed_account
|
|
|
|
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[])
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let new_staker_pubkey = Pubkey::new_rand();
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize(&new_staker_pubkey, &[]),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
|
|
|
|
assert_eq!(stake.authorized_pubkey, new_staker_pubkey);
|
|
|
|
|
|
|
|
let other_pubkey = Pubkey::new_rand();
|
|
|
|
let mut other_account = Account::new(1, 0, &system_program::id());
|
|
|
|
let other_keyed_account = KeyedAccount::new(&other_pubkey, true, &mut other_account);
|
|
|
|
|
|
|
|
// Use unsigned stake_keyed_account to test other signers
|
|
|
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
|
|
|
|
|
|
|
let new_voter_pubkey = Pubkey::new_rand();
|
|
|
|
let vote_state = VoteState::default();
|
|
|
|
let mut new_vote_account =
|
|
|
|
vote_state::create_account(&new_voter_pubkey, &Pubkey::new_rand(), 0, 100);
|
|
|
|
let mut new_vote_keyed_account =
|
|
|
|
KeyedAccount::new(&new_voter_pubkey, false, &mut new_vote_account);
|
|
|
|
new_vote_keyed_account.set_state(&vote_state).unwrap();
|
|
|
|
|
|
|
|
// Random other account should fail
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.delegate_stake(
|
|
|
|
&new_vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&Config::default(),
|
|
|
|
&[other_keyed_account]
|
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut new_staker_account = Account::new(1, 0, &system_program::id());
|
|
|
|
let new_staker_keyed_account =
|
|
|
|
KeyedAccount::new(&new_staker_pubkey, true, &mut new_staker_account);
|
|
|
|
|
|
|
|
// Authorized staker should succeed
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.delegate_stake(
|
|
|
|
&new_vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&Config::default(),
|
|
|
|
&[new_staker_keyed_account]
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
|
|
|
|
assert_eq!(stake.voter_pubkey(0), &new_voter_pubkey);
|
|
|
|
|
|
|
|
// Test another staking action
|
|
|
|
let new_staker_keyed_account =
|
|
|
|
KeyedAccount::new(&new_staker_pubkey, true, &mut new_staker_account);
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.deactivate_stake(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&[new_staker_keyed_account]
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|