2019-04-01 16:45:53 -07:00
|
|
|
//! Stake state
|
|
|
|
//! * delegate stakes to vote accounts
|
|
|
|
//! * keep track of rewards
|
|
|
|
//! * own mining pools
|
|
|
|
|
2020-03-02 12:28:43 -08:00
|
|
|
use crate::{
|
|
|
|
config::Config,
|
|
|
|
id,
|
|
|
|
stake_instruction::{LockupArgs, 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},
|
2020-01-22 17:54:06 -08:00
|
|
|
account_utils::{State, StateMut},
|
2020-01-29 17:59:14 -08:00
|
|
|
clock::{Clock, Epoch, UnixTimestamp},
|
2019-08-12 20:59:57 -07:00
|
|
|
instruction::InstructionError,
|
|
|
|
pubkey::Pubkey,
|
2019-10-31 11:07:27 -07:00
|
|
|
rent::Rent,
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_history::{StakeHistory, StakeHistoryEntry},
|
2019-08-12 20:59:57 -07:00
|
|
|
};
|
2020-02-25 17:12:01 -08:00
|
|
|
use solana_vote_program::vote_state::{VoteState, VoteStateVersions};
|
2019-10-14 15:02:24 -07:00
|
|
|
use std::collections::HashSet;
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-09-26 13:29:29 -07:00
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
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-10-31 11:07:27 -07:00
|
|
|
Initialized(Meta),
|
|
|
|
Stake(Meta, 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-12-03 20:44:02 -08:00
|
|
|
pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
|
|
|
|
rent.minimum_balance(std::mem::size_of::<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 {
|
2019-10-31 11:07:27 -07:00
|
|
|
StakeState::Stake(_meta, stake) => Some(*stake),
|
2019-09-26 13:29:29 -07:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
2019-11-25 13:14:32 -08:00
|
|
|
|
|
|
|
pub fn delegation_from(account: &Account) -> Option<Delegation> {
|
|
|
|
Self::from(account).and_then(|state: Self| state.delegation())
|
|
|
|
}
|
|
|
|
pub fn delegation(&self) -> Option<Delegation> {
|
|
|
|
match self {
|
|
|
|
StakeState::Stake(_meta, stake) => Some(stake.delegation),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn authorized_from(account: &Account) -> Option<Authorized> {
|
|
|
|
Self::from(account).and_then(|state: Self| state.authorized())
|
|
|
|
}
|
|
|
|
|
2019-09-26 13:29:29 -07:00
|
|
|
pub fn authorized(&self) -> Option<Authorized> {
|
|
|
|
match self {
|
2019-10-31 11:07:27 -07:00
|
|
|
StakeState::Stake(meta, _stake) => Some(meta.authorized),
|
|
|
|
StakeState::Initialized(meta) => Some(meta.authorized),
|
2019-06-17 19:34:21 -07:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
|
|
|
|
2019-09-26 13:29:29 -07:00
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
|
|
|
pub enum StakeAuthorize {
|
|
|
|
Staker,
|
|
|
|
Withdrawer,
|
|
|
|
}
|
|
|
|
|
2019-09-16 17:47:42 -07:00
|
|
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
2019-09-12 19:03:28 -07:00
|
|
|
pub struct Lockup {
|
2020-01-06 19:52:20 -08:00
|
|
|
/// UnixTimestamp at which this stake will allow withdrawal, or
|
|
|
|
/// changes to authorized staker or withdrawer, unless the
|
|
|
|
/// transaction is signed by the custodian
|
2019-12-19 14:37:47 -08:00
|
|
|
pub unix_timestamp: UnixTimestamp,
|
2020-01-06 19:52:20 -08:00
|
|
|
/// epoch height at which this stake will allow withdrawal, or
|
|
|
|
/// changes to authorized staker or withdrawer, unless the
|
|
|
|
/// transaction is signed by the custodian
|
2019-09-26 13:29:29 -07:00
|
|
|
/// to the custodian
|
2019-11-25 15:11:55 -08:00
|
|
|
pub epoch: Epoch,
|
2020-01-06 19:52:20 -08:00
|
|
|
/// custodian signature on a transaction exempts the operation from
|
|
|
|
/// lockup constraints
|
2019-09-16 17:47:42 -07:00
|
|
|
pub custodian: Pubkey,
|
2019-09-12 19:03:28 -07:00
|
|
|
}
|
|
|
|
|
2019-12-19 14:37:47 -08:00
|
|
|
impl Lockup {
|
|
|
|
pub fn is_in_force(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> bool {
|
|
|
|
(self.unix_timestamp > clock.unix_timestamp || self.epoch > clock.epoch)
|
|
|
|
&& !signers.contains(&self.custodian)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 13:29:29 -07:00
|
|
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
|
|
|
pub struct Authorized {
|
|
|
|
pub staker: Pubkey,
|
|
|
|
pub withdrawer: Pubkey,
|
|
|
|
}
|
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
|
|
|
pub struct Meta {
|
|
|
|
pub rent_exempt_reserve: u64,
|
|
|
|
pub authorized: Authorized,
|
|
|
|
pub lockup: Lockup,
|
|
|
|
}
|
|
|
|
|
2020-01-01 11:03:29 -08:00
|
|
|
impl Meta {
|
2020-01-28 20:59:53 -08:00
|
|
|
pub fn set_lockup(
|
|
|
|
&mut self,
|
2020-03-02 12:28:43 -08:00
|
|
|
lockup: &LockupArgs,
|
2020-01-28 20:59:53 -08:00
|
|
|
signers: &HashSet<Pubkey>,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
if !signers.contains(&self.lockup.custodian) {
|
|
|
|
return Err(InstructionError::MissingRequiredSignature);
|
|
|
|
}
|
2020-03-02 12:28:43 -08:00
|
|
|
if let Some(unix_timestamp) = lockup.unix_timestamp {
|
|
|
|
self.lockup.unix_timestamp = unix_timestamp;
|
|
|
|
}
|
|
|
|
if let Some(epoch) = lockup.epoch {
|
|
|
|
self.lockup.epoch = epoch;
|
|
|
|
}
|
|
|
|
if let Some(custodian) = lockup.custodian {
|
|
|
|
self.lockup.custodian = custodian;
|
|
|
|
}
|
2020-01-28 20:59:53 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-01-01 11:03:29 -08:00
|
|
|
pub fn authorize(
|
|
|
|
&mut self,
|
|
|
|
authority: &Pubkey,
|
|
|
|
stake_authorize: StakeAuthorize,
|
|
|
|
signers: &HashSet<Pubkey>,
|
|
|
|
clock: &Clock,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
// verify that lockup has expired or that the authorization
|
|
|
|
// is *also* signed by the custodian
|
|
|
|
if self.lockup.is_in_force(clock, signers) {
|
|
|
|
return Err(StakeError::LockupInForce.into());
|
|
|
|
}
|
|
|
|
self.authorized
|
|
|
|
.authorize(signers, authority, stake_authorize)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 13:29:29 -07:00
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
2019-11-25 13:14:32 -08:00
|
|
|
pub struct Delegation {
|
|
|
|
/// to whom the stake is delegated
|
2019-06-21 20:43:24 -07:00
|
|
|
pub voter_pubkey: Pubkey,
|
2020-01-29 17:59:14 -08:00
|
|
|
/// activated stake amount, set at delegate() time
|
2019-09-09 18:17:32 -07:00
|
|
|
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,
|
2019-11-25 13:14:32 -08:00
|
|
|
/// how much stake we can activate per-epoch as a fraction of currently effective stake
|
|
|
|
pub warmup_cooldown_rate: f64,
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
2019-08-12 20:59:57 -07:00
|
|
|
|
2019-11-25 13:14:32 -08:00
|
|
|
impl Default for Delegation {
|
2019-06-21 20:43:24 -07:00
|
|
|
fn default() -> Self {
|
2019-08-12 20:59:57 -07:00
|
|
|
Self {
|
2019-06-21 20:43:24 -07:00
|
|
|
voter_pubkey: Pubkey::default(),
|
|
|
|
stake: 0,
|
2019-08-17 18:12:30 -07:00
|
|
|
activation_epoch: 0,
|
|
|
|
deactivation_epoch: std::u64::MAX,
|
2019-11-25 13:14:32 -08:00
|
|
|
warmup_cooldown_rate: Config::default().warmup_cooldown_rate,
|
2019-06-21 20:43:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-25 13:14:32 -08:00
|
|
|
impl Delegation {
|
|
|
|
pub fn new(
|
|
|
|
voter_pubkey: &Pubkey,
|
|
|
|
stake: u64,
|
|
|
|
activation_epoch: Epoch,
|
|
|
|
warmup_cooldown_rate: f64,
|
|
|
|
) -> Self {
|
2019-09-26 13:29:29 -07:00
|
|
|
Self {
|
2019-11-25 13:14:32 -08:00
|
|
|
voter_pubkey: *voter_pubkey,
|
|
|
|
stake,
|
|
|
|
activation_epoch,
|
|
|
|
warmup_cooldown_rate,
|
|
|
|
..Delegation::default()
|
2019-09-26 13:29:29 -07:00
|
|
|
}
|
|
|
|
}
|
2019-11-25 13:14:32 -08:00
|
|
|
pub fn is_bootstrap(&self) -> bool {
|
2019-09-26 13:29:29 -07:00
|
|
|
self.activation_epoch == std::u64::MAX
|
2019-09-16 17:47:42 -07:00
|
|
|
}
|
|
|
|
|
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-12-19 23:27:54 -08:00
|
|
|
#[allow(clippy::comparison_chain)]
|
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(
|
2019-11-25 13:14:32 -08:00
|
|
|
((weight * entry.effective as f64 * self.warmup_cooldown_rate) as u64).max(1),
|
2019-08-17 18:12:30 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
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 +=
|
2019-11-25 13:14:32 -08:00
|
|
|
((weight * entry.effective as f64 * self.warmup_cooldown_rate) as u64).max(1);
|
2019-08-17 18:12:30 -07:00
|
|
|
|
|
|
|
if effective_stake >= self.stake {
|
|
|
|
effective_stake = self.stake;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_epoch += 1;
|
2019-10-14 15:40:24 -07:00
|
|
|
if next_epoch >= epoch || next_epoch >= self.deactivation_epoch {
|
2019-08-17 18:12:30 -07:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2019-11-25 13:14:32 -08:00
|
|
|
}
|
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
2019-11-25 13:14:32 -08:00
|
|
|
pub struct Stake {
|
|
|
|
pub delegation: Delegation,
|
|
|
|
/// credits observed is credits from vote account state when delegated or redeemed
|
|
|
|
pub credits_observed: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Authorized {
|
|
|
|
pub fn auto(authorized: &Pubkey) -> Self {
|
|
|
|
Self {
|
|
|
|
staker: *authorized,
|
|
|
|
withdrawer: *authorized,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub fn check(
|
|
|
|
&self,
|
|
|
|
signers: &HashSet<Pubkey>,
|
|
|
|
stake_authorize: StakeAuthorize,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
match stake_authorize {
|
|
|
|
StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()),
|
|
|
|
StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()),
|
|
|
|
_ => Err(InstructionError::MissingRequiredSignature),
|
|
|
|
}
|
|
|
|
}
|
2019-06-21 20:43:24 -07:00
|
|
|
|
2019-11-25 13:14:32 -08:00
|
|
|
pub fn authorize(
|
|
|
|
&mut self,
|
|
|
|
signers: &HashSet<Pubkey>,
|
|
|
|
new_authorized: &Pubkey,
|
|
|
|
stake_authorize: StakeAuthorize,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
match stake_authorize {
|
2020-02-25 18:03:26 -08:00
|
|
|
StakeAuthorize::Staker => {
|
|
|
|
// Allow either the staker or the withdrawer to change the staker key
|
|
|
|
if !signers.contains(&self.staker) && !signers.contains(&self.withdrawer) {
|
|
|
|
return Err(InstructionError::MissingRequiredSignature);
|
|
|
|
}
|
|
|
|
self.staker = *new_authorized
|
|
|
|
}
|
|
|
|
StakeAuthorize::Withdrawer => {
|
|
|
|
self.check(signers, stake_authorize)?;
|
|
|
|
self.withdrawer = *new_authorized
|
|
|
|
}
|
2019-11-25 13:14:32 -08:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Stake {
|
|
|
|
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
|
|
|
|
self.delegation.stake(epoch, history)
|
|
|
|
}
|
2020-01-22 12:21:31 -08:00
|
|
|
|
|
|
|
pub fn redeem_rewards(
|
|
|
|
&mut self,
|
|
|
|
point_value: f64,
|
|
|
|
vote_state: &VoteState,
|
|
|
|
stake_history: Option<&StakeHistory>,
|
|
|
|
) -> Option<(u64, u64)> {
|
|
|
|
self.calculate_rewards(point_value, vote_state, stake_history)
|
|
|
|
.map(|(voters_reward, stakers_reward, credits_observed)| {
|
|
|
|
self.credits_observed = credits_observed;
|
|
|
|
self.delegation.stake += stakers_reward;
|
|
|
|
(voters_reward, stakers_reward)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
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
|
2020-01-22 12:21:31 -08:00
|
|
|
pub 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 {
|
2020-01-22 16:53:42 -08:00
|
|
|
// the staker has already observed or been redeemed this epoch
|
|
|
|
// or was activated after this epoch
|
2019-06-21 20:43:24 -07:00
|
|
|
0
|
|
|
|
};
|
|
|
|
|
2019-08-12 20:59:57 -07:00
|
|
|
total_rewards +=
|
2019-11-25 13:14:32 -08:00
|
|
|
(self.delegation.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-09-11 09:48:29 -07:00
|
|
|
fn redelegate(
|
|
|
|
&mut self,
|
|
|
|
voter_pubkey: &Pubkey,
|
|
|
|
vote_state: &VoteState,
|
2019-10-31 11:07:27 -07:00
|
|
|
clock: &Clock,
|
2020-01-29 17:59:14 -08:00
|
|
|
stake_history: &StakeHistory,
|
|
|
|
config: &Config,
|
2019-09-11 09:48:29 -07:00
|
|
|
) -> Result<(), StakeError> {
|
2020-01-29 17:59:14 -08:00
|
|
|
// can't redelegate if stake is active. either the stake
|
|
|
|
// is freshly activated or has fully de-activated. redelegation
|
|
|
|
// implies re-activation
|
|
|
|
if self.stake(clock.epoch, Some(stake_history)) != 0 {
|
2019-10-29 14:42:45 -07:00
|
|
|
return Err(StakeError::TooSoonToRedelegate);
|
2019-09-11 09:48:29 -07:00
|
|
|
}
|
2020-01-29 17:59:14 -08:00
|
|
|
self.delegation.activation_epoch = clock.epoch;
|
|
|
|
self.delegation.deactivation_epoch = std::u64::MAX;
|
2019-11-25 13:14:32 -08:00
|
|
|
self.delegation.voter_pubkey = *voter_pubkey;
|
2020-01-29 17:59:14 -08:00
|
|
|
self.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate;
|
2019-09-11 09:48:29 -07:00
|
|
|
self.credits_observed = vote_state.credits();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
fn split(&mut self, lamports: u64) -> Result<Self, StakeError> {
|
2019-11-25 13:14:32 -08:00
|
|
|
if lamports > self.delegation.stake {
|
2019-10-31 11:07:27 -07:00
|
|
|
return Err(StakeError::InsufficientStake);
|
|
|
|
}
|
2019-11-25 13:14:32 -08:00
|
|
|
self.delegation.stake -= lamports;
|
2019-10-31 11:07:27 -07:00
|
|
|
let new = Self {
|
2019-11-25 13:14:32 -08:00
|
|
|
delegation: Delegation {
|
|
|
|
stake: lamports,
|
|
|
|
..self.delegation
|
|
|
|
},
|
2019-10-31 11:07:27 -07:00
|
|
|
..*self
|
|
|
|
};
|
|
|
|
Ok(new)
|
|
|
|
}
|
|
|
|
|
2019-08-15 14:35:48 -07:00
|
|
|
fn new(
|
|
|
|
stake: u64,
|
|
|
|
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,
|
|
|
|
) -> Self {
|
2019-08-12 20:59:57 -07:00
|
|
|
Self {
|
2019-11-25 13:14:32 -08:00
|
|
|
delegation: Delegation::new(
|
|
|
|
voter_pubkey,
|
|
|
|
stake,
|
|
|
|
activation_epoch,
|
|
|
|
config.warmup_cooldown_rate,
|
|
|
|
),
|
2019-08-12 20:59:57 -07:00
|
|
|
credits_observed: vote_state.credits(),
|
|
|
|
}
|
2019-06-17 19:34:21 -07:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:38:30 -07:00
|
|
|
fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
|
2019-11-25 13:14:32 -08:00
|
|
|
if self.delegation.deactivation_epoch != std::u64::MAX {
|
2019-10-15 12:50:31 -07:00
|
|
|
Err(StakeError::AlreadyDeactivated)
|
|
|
|
} else {
|
2019-11-25 13:14:32 -08:00
|
|
|
self.delegation.deactivation_epoch = epoch;
|
2019-10-15 12:50:31 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
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-09-26 13:29:29 -07:00
|
|
|
authority: &Pubkey,
|
|
|
|
stake_authorize: StakeAuthorize,
|
2019-10-14 15:02:24 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
2020-01-01 11:03:29 -08:00
|
|
|
clock: &Clock,
|
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>,
|
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>,
|
|
|
|
) -> 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>;
|
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,
|
2019-10-14 15:02:24 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
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> {
|
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-09-16 17:47:42 -07:00
|
|
|
authority: &Pubkey,
|
2019-09-26 13:29:29 -07:00
|
|
|
stake_authorize: StakeAuthorize,
|
2019-10-14 15:02:24 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
2020-01-01 11:03:29 -08:00
|
|
|
clock: &Clock,
|
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) => {
|
2020-01-01 11:03:29 -08:00
|
|
|
meta.authorize(authority, stake_authorize, signers, clock)?;
|
2019-10-31 11:07:27 -07:00
|
|
|
self.set_state(&StakeState::Stake(meta, stake))
|
|
|
|
}
|
|
|
|
StakeState::Initialized(mut meta) => {
|
2020-01-01 11:03:29 -08:00
|
|
|
meta.authorize(authority, stake_authorize, signers, clock)?;
|
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-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>,
|
2019-06-10 12:17:29 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2019-10-31 11:07:27 -07:00
|
|
|
match self.state()? {
|
|
|
|
StakeState::Initialized(meta) => {
|
|
|
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
|
|
|
let stake = Stake::new(
|
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)?;
|
2020-01-29 17:59:14 -08:00
|
|
|
stake.redelegate(
|
|
|
|
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,
|
|
|
|
)?;
|
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>,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
match self.state()? {
|
|
|
|
StakeState::Initialized(mut meta) => {
|
|
|
|
meta.set_lockup(lockup, signers)?;
|
|
|
|
self.set_state(&StakeState::Initialized(meta))
|
|
|
|
}
|
|
|
|
StakeState::Stake(mut meta, stake) => {
|
|
|
|
meta.set_lockup(lockup, signers)?;
|
|
|
|
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> {
|
|
|
|
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)?;
|
|
|
|
|
|
|
|
// verify enough lamports for rent in new stake with the split
|
2020-01-22 09:11:56 -08:00
|
|
|
if split.lamports()? + lamports < meta.rent_exempt_reserve
|
2019-12-16 15:56:34 -08:00
|
|
|
// verify enough lamports left in previous stake and not full withdrawal
|
2020-01-22 09:11:56 -08:00
|
|
|
|| (lamports + meta.rent_exempt_reserve > self.lamports()? && lamports != self.lamports()?)
|
2019-10-31 11:07:27 -07:00
|
|
|
{
|
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
// split the stake, subtract rent_exempt_balance unless
|
|
|
|
// the destination account already has those lamports
|
|
|
|
// in place.
|
|
|
|
// this could represent a small loss of staked lamports
|
|
|
|
// if the split account starts out with a zero balance
|
|
|
|
let split_stake = stake.split(
|
2020-01-22 09:11:56 -08:00
|
|
|
lamports - meta.rent_exempt_reserve.saturating_sub(split.lamports()?),
|
2019-10-31 11:07:27 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
self.set_state(&StakeState::Stake(meta, stake))?;
|
|
|
|
split.set_state(&StakeState::Stake(meta, split_stake))?;
|
|
|
|
}
|
|
|
|
StakeState::Initialized(meta) => {
|
|
|
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
|
|
|
|
|
|
|
// enough lamports for rent in new stake
|
|
|
|
if lamports < meta.rent_exempt_reserve
|
|
|
|
// verify enough lamports left in previous stake
|
2020-01-22 09:11:56 -08:00
|
|
|
|| (lamports + meta.rent_exempt_reserve > self.lamports()? && lamports != self.lamports()?)
|
2019-10-31 11:07:27 -07:00
|
|
|
{
|
|
|
|
return Err(InstructionError::InsufficientFunds);
|
|
|
|
}
|
|
|
|
|
|
|
|
split.set_state(&StakeState::Initialized(meta))?;
|
|
|
|
}
|
|
|
|
StakeState::Uninitialized => {
|
|
|
|
if !signers.contains(&self.unsigned_key()) {
|
|
|
|
return Err(InstructionError::MissingRequiredSignature);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => return Err(InstructionError::InvalidAccountData),
|
|
|
|
}
|
|
|
|
|
2020-01-22 09:11:56 -08:00
|
|
|
split.try_account_ref_mut()?.lamports += lamports;
|
|
|
|
self.try_account_ref_mut()?.lamports -= lamports;
|
2019-10-31 11:07:27 -07:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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,
|
2019-10-14 15:02:24 -07:00
|
|
|
signers: &HashSet<Pubkey>,
|
2019-06-21 22:28:34 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2019-10-31 11:07:27 -07:00
|
|
|
let (lockup, reserve, is_staked) = match self.state()? {
|
|
|
|
StakeState::Stake(meta, stake) => {
|
|
|
|
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 {
|
|
|
|
stake.delegation.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
|
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
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
(meta.lockup, staked + meta.rent_exempt_reserve, staked != 0)
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
2019-10-31 11:07:27 -07:00
|
|
|
StakeState::Initialized(meta) => {
|
|
|
|
meta.authorized.check(signers, StakeAuthorize::Withdrawer)?;
|
|
|
|
|
|
|
|
(meta.lockup, meta.rent_exempt_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
|
|
|
|
if lockup.is_in_force(&clock, signers) {
|
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
|
|
|
|
|
|
|
// if the stake is active, we mustn't allow the account to go away
|
|
|
|
if is_staked // line coverage for branch coverage
|
2020-01-22 09:11:56 -08:00
|
|
|
&& lamports + 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
|
|
|
|
&& lamports + 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-01-22 09:11:56 -08:00
|
|
|
self.try_account_ref_mut()?.lamports -= lamports;
|
|
|
|
to.try_account_ref_mut()?.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-01-22 12:21:31 -08:00
|
|
|
// utility function, used by runtime
|
|
|
|
pub fn redeem_rewards(
|
|
|
|
stake_account: &mut Account,
|
|
|
|
vote_account: &mut Account,
|
|
|
|
point_value: f64,
|
|
|
|
stake_history: Option<&StakeHistory>,
|
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-01-22 12:21:31 -08:00
|
|
|
|
|
|
|
if let Some((voters_reward, stakers_reward)) =
|
|
|
|
stake.redeem_rewards(point_value, &vote_state, stake_history)
|
|
|
|
{
|
|
|
|
stake_account.lamports += stakers_reward;
|
|
|
|
vote_account.lamports += voters_reward;
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
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| {
|
|
|
|
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-11-25 15:11:55 -08:00
|
|
|
// genesis investor accounts
|
|
|
|
pub fn create_lockup_stake_account(
|
|
|
|
authorized: &Authorized,
|
|
|
|
lockup: &Lockup,
|
|
|
|
rent: &Rent,
|
|
|
|
lamports: u64,
|
|
|
|
) -> Account {
|
|
|
|
let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id());
|
|
|
|
|
|
|
|
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,
|
|
|
|
vote_account: &Account,
|
2019-11-12 12:33:40 -08:00
|
|
|
rent: &Rent,
|
2019-09-26 13:29:29 -07:00
|
|
|
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-11-25 15:11:55 -08:00
|
|
|
|
|
|
|
let rent_exempt_reserve = rent.minimum_balance(stake_account.data.len());
|
|
|
|
|
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
|
|
|
},
|
2019-11-12 12:33:40 -08:00
|
|
|
Stake::new(
|
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,
|
|
|
|
std::u64::MAX,
|
|
|
|
&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::*;
|
|
|
|
use crate::id;
|
2019-09-11 09:48:29 -07:00
|
|
|
use solana_sdk::{account::Account, pubkey::Pubkey, system_program};
|
2019-11-20 10:12:43 -08:00
|
|
|
use solana_vote_program::vote_state;
|
2020-01-22 09:11:56 -08:00
|
|
|
use std::cell::RefCell;
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-11-25 15:11:55 -08:00
|
|
|
impl Meta {
|
|
|
|
pub fn auto(authorized: &Pubkey) -> Self {
|
|
|
|
Self {
|
|
|
|
authorized: Authorized::auto(authorized),
|
|
|
|
..Meta::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-01 11:03:29 -08:00
|
|
|
#[test]
|
|
|
|
fn test_meta_authorize() {
|
|
|
|
let staker = Pubkey::new_rand();
|
|
|
|
let custodian = Pubkey::new_rand();
|
|
|
|
let mut meta = Meta {
|
|
|
|
authorized: Authorized::auto(&staker),
|
|
|
|
lockup: Lockup {
|
|
|
|
epoch: 0,
|
|
|
|
unix_timestamp: 0,
|
|
|
|
custodian,
|
|
|
|
},
|
|
|
|
..Meta::default()
|
|
|
|
};
|
|
|
|
// verify sig check
|
|
|
|
let mut signers = HashSet::new();
|
|
|
|
let mut clock = Clock::default();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
meta.authorize(&staker, StakeAuthorize::Staker, &signers, &clock),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
signers.insert(staker);
|
|
|
|
assert_eq!(
|
|
|
|
meta.authorize(&staker, StakeAuthorize::Staker, &signers, &clock),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
// verify lockup check
|
|
|
|
meta.lockup.epoch = 1;
|
|
|
|
assert_eq!(
|
|
|
|
meta.authorize(&staker, StakeAuthorize::Staker, &signers, &clock),
|
|
|
|
Err(StakeError::LockupInForce.into())
|
|
|
|
);
|
|
|
|
// verify lockup check defeated by custodian
|
|
|
|
signers.insert(custodian);
|
|
|
|
assert_eq!(
|
|
|
|
meta.authorize(&staker, StakeAuthorize::Staker, &signers, &clock),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
// verify lock expiry
|
|
|
|
signers.remove(&custodian);
|
|
|
|
clock.epoch = 1;
|
|
|
|
assert_eq!(
|
|
|
|
meta.authorize(&staker, StakeAuthorize::Staker, &signers, &clock),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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!(
|
2019-11-25 13:14:32 -08:00
|
|
|
Delegation {
|
2019-08-18 15:41:49 -07:00
|
|
|
activation_epoch: std::u64::MAX,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-18 15:41:49 -07:00
|
|
|
}
|
|
|
|
.is_bootstrap(),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-11-25 13:14:32 -08:00
|
|
|
Delegation {
|
2019-08-18 15:41:49 -07:00
|
|
|
activation_epoch: 0,
|
2019-11-25 13:14:32 -08:00
|
|
|
..Delegation::default()
|
2019-08-18 15:41:49 -07:00
|
|
|
}
|
|
|
|
.is_bootstrap(),
|
|
|
|
false
|
|
|
|
);
|
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
|
|
|
|
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
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
&Pubkey::new_rand(),
|
|
|
|
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
|
|
|
let vote_state_credits = vote_state.credits();
|
|
|
|
vote_keyed_account
|
|
|
|
.set_state(&VoteStateVersions::Current(Box::new(vote_state)))
|
|
|
|
.unwrap();
|
2019-04-01 16:45:53 -07:00
|
|
|
|
2019-09-16 17:47:42 -07:00
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
2019-06-10 12:17:29 -07:00
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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,
|
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,
|
|
|
|
)
|
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
|
2020-01-22 09:11:56 -08:00
|
|
|
let stake = StakeState::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-09 18:17:32 -07:00
|
|
|
..Stake::default()
|
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;
|
|
|
|
|
|
|
|
// verify that delegate fails if stake is still active
|
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(),
|
|
|
|
&signers
|
|
|
|
),
|
|
|
|
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
|
|
|
|
|
|
|
// verify that delegate can be called twice, 2nd is redelegate
|
2019-09-11 09:48:29 -07:00
|
|
|
assert!(stake_keyed_account
|
2020-01-29 17:59:14 -08:00
|
|
|
.delegate(
|
|
|
|
&vote_keyed_account,
|
|
|
|
&clock,
|
|
|
|
&StakeHistory::default(),
|
|
|
|
&Config::default(),
|
|
|
|
&signers
|
|
|
|
)
|
2019-09-11 09:48:29 -07:00
|
|
|
.is_ok());
|
|
|
|
|
2020-01-29 17:59:14 -08:00
|
|
|
// verify that delegate() looks right, compare against hand-rolled
|
|
|
|
let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
stake,
|
|
|
|
Stake {
|
|
|
|
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,
|
2020-01-29 17:59:14 -08:00
|
|
|
..Stake::default()
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// 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(),
|
|
|
|
&signers
|
|
|
|
)
|
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),
|
|
|
|
);
|
|
|
|
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
|
2019-11-25 13:14:32 -08:00
|
|
|
let increment = (1_000 as 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!(
|
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if epoch < stake.deactivation_epoch {
|
2019-11-25 13:14:32 -08:00
|
|
|
let increase = (effective as f64 * stake.warmup_cooldown_rate) as u64;
|
2019-10-14 15:40:24 -07:00
|
|
|
effective += increase.min(activating);
|
|
|
|
other_activations.push(0);
|
|
|
|
} else {
|
2019-11-25 13:14:32 -08:00
|
|
|
let decrease = (effective as f64 * stake.warmup_cooldown_rate) as u64;
|
2019-10-14 15:40:24 -07:00
|
|
|
effective -= decrease.min(deactivating);
|
|
|
|
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!(
|
|
|
|
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)),
|
|
|
|
(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()
|
2019-11-25 13:14:32 -08:00
|
|
|
.map(|delegation| delegation.stake(epoch, Some(&stake_history)))
|
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()
|
2019-11-25 13:14:32 -08:00
|
|
|
.map(|delegation| delegation.stake(0, Some(&stake_history)))
|
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()
|
2019-11-25 13:14:32 -08:00
|
|
|
.map(|delegation| delegation.stake(epoch, Some(&stake_history)))
|
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() {
|
2019-06-21 23:45:03 -07:00
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account =
|
2020-01-22 09:11:56 -08:00
|
|
|
Account::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);
|
2019-09-16 17:47:42 -07:00
|
|
|
let custodian = 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!(
|
2020-01-22 09:11:56 -08:00
|
|
|
StakeState::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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-01-29 17:59:14 -08:00
|
|
|
fn test_deactivate() {
|
2019-09-04 13:34:09 -07:00
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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
|
|
|
|
let vote_pubkey = 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,
|
|
|
|
&Pubkey::new_rand(),
|
|
|
|
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
|
|
|
|
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
|
|
|
|
.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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers
|
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() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let stake_account = Account::new_ref_data_with_space(
|
|
|
|
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!(
|
2020-03-02 12:28:43 -08:00
|
|
|
stake_keyed_account.set_lockup(&LockupArgs::default(), &HashSet::default(),),
|
2020-01-28 20:59:53 -08:00
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
|
|
|
|
// initalize the stake
|
|
|
|
let custodian = Pubkey::new_rand();
|
|
|
|
stake_keyed_account
|
|
|
|
.initialize(
|
|
|
|
&Authorized::auto(&stake_pubkey),
|
|
|
|
&Lockup {
|
|
|
|
unix_timestamp: 1,
|
|
|
|
epoch: 1,
|
|
|
|
custodian,
|
|
|
|
},
|
|
|
|
&Rent::free(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
2020-03-02 12:28:43 -08:00
|
|
|
stake_keyed_account.set_lockup(&LockupArgs::default(), &HashSet::default(),),
|
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
|
|
|
},
|
|
|
|
&vec![custodian].into_iter().collect()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// delegate stake
|
|
|
|
let vote_pubkey = Pubkey::new_rand();
|
|
|
|
let vote_account = RefCell::new(vote_state::create_account(
|
|
|
|
&vote_pubkey,
|
|
|
|
&Pubkey::new_rand(),
|
|
|
|
0,
|
|
|
|
100,
|
|
|
|
));
|
|
|
|
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
|
2020-02-25 17:12:01 -08:00
|
|
|
vote_keyed_account
|
|
|
|
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
|
|
|
|
.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(),
|
|
|
|
)
|
|
|
|
.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(),
|
|
|
|
),
|
|
|
|
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),
|
|
|
|
},
|
|
|
|
&vec![custodian].into_iter().collect()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_optional_lockup() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let stake_account = Account::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 = Pubkey::new_rand();
|
|
|
|
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,
|
|
|
|
},
|
|
|
|
&vec![custodian].into_iter().collect()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: Some(2),
|
|
|
|
epoch: None,
|
|
|
|
custodian: None,
|
|
|
|
},
|
|
|
|
&vec![custodian].into_iter().collect()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
if let StakeState::Initialized(Meta { lockup, .. }) =
|
|
|
|
StakeState::from(&stake_keyed_account.account.borrow()).unwrap()
|
|
|
|
{
|
|
|
|
assert_eq!(lockup.unix_timestamp, 2);
|
|
|
|
assert_eq!(lockup.epoch, 1);
|
|
|
|
assert_eq!(lockup.custodian, custodian);
|
|
|
|
} else {
|
|
|
|
assert!(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: None,
|
|
|
|
epoch: Some(3),
|
|
|
|
custodian: None,
|
|
|
|
},
|
|
|
|
&vec![custodian].into_iter().collect()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
if let StakeState::Initialized(Meta { lockup, .. }) =
|
|
|
|
StakeState::from(&stake_keyed_account.account.borrow()).unwrap()
|
|
|
|
{
|
|
|
|
assert_eq!(lockup.unix_timestamp, 2);
|
|
|
|
assert_eq!(lockup.epoch, 3);
|
|
|
|
assert_eq!(lockup.custodian, custodian);
|
|
|
|
} else {
|
|
|
|
assert!(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
let new_custodian = Pubkey::new_rand();
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs {
|
|
|
|
unix_timestamp: None,
|
|
|
|
epoch: None,
|
|
|
|
custodian: Some(new_custodian),
|
|
|
|
},
|
2020-01-28 20:59:53 -08:00
|
|
|
&vec![custodian].into_iter().collect()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
2020-03-02 12:28:43 -08:00
|
|
|
|
|
|
|
if let StakeState::Initialized(Meta { lockup, .. }) =
|
|
|
|
StakeState::from(&stake_keyed_account.account.borrow()).unwrap()
|
|
|
|
{
|
|
|
|
assert_eq!(lockup.unix_timestamp, 2);
|
|
|
|
assert_eq!(lockup.epoch, 3);
|
|
|
|
assert_eq!(lockup.custodian, new_custodian);
|
|
|
|
} else {
|
|
|
|
assert!(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.set_lockup(
|
|
|
|
&LockupArgs::default(),
|
|
|
|
&vec![custodian].into_iter().collect()
|
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
2020-01-28 20:59:53 -08:00
|
|
|
}
|
|
|
|
|
2019-06-21 22:28:34 -07:00
|
|
|
#[test]
|
|
|
|
fn test_withdraw_stake() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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
|
|
|
|
|
|
|
let to = Pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_account = Account::new_ref(1, 0, &system_program::id());
|
|
|
|
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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&HashSet::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
|
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-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!(
|
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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
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
|
|
|
);
|
2020-01-22 09:11:56 -08:00
|
|
|
assert_eq!(stake_account.borrow().lamports, 0);
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2019-08-07 20:29:22 -07:00
|
|
|
// reset balance
|
2020-01-22 09:11:56 -08:00
|
|
|
stake_account.borrow_mut().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);
|
2019-09-16 17:47:42 -07:00
|
|
|
let custodian = 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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
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();
|
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,
|
|
|
|
&Pubkey::new_rand(),
|
|
|
|
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
|
|
|
|
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
|
|
|
|
.unwrap();
|
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,
|
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
|
2020-01-22 09:11:56 -08:00
|
|
|
stake_account.borrow_mut().lamports += 10;
|
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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
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
|
2020-01-22 09:11:56 -08:00
|
|
|
stake_account.borrow_mut().lamports += 10;
|
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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers
|
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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers
|
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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers
|
2019-08-12 20:59:57 -07:00
|
|
|
),
|
2019-06-21 22:28:34 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2020-01-22 09:11:56 -08:00
|
|
|
assert_eq!(stake_account.borrow().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;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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
|
|
|
|
|
|
|
let to = Pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_account = Account::new_ref(1, 0, &system_program::id());
|
|
|
|
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)
|
2019-06-21 22:28:34 -07:00
|
|
|
let vote_pubkey = 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,
|
|
|
|
&Pubkey::new_rand(),
|
|
|
|
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
|
|
|
|
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
|
|
|
|
.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,
|
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,
|
2020-01-22 09:11:56 -08:00
|
|
|
&[
|
|
|
|
StakeState::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,
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
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;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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
|
|
|
|
2019-09-04 13:34:09 -07:00
|
|
|
let to = Pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_account = Account::new_ref(1, 0, &system_program::id());
|
|
|
|
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
|
|
|
|
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-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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
2019-09-04 13:34:09 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-09-26 13:29:29 -07:00
|
|
|
fn test_withdraw_lockup() {
|
2019-09-04 13:34:09 -07:00
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
2019-09-16 17:47:42 -07:00
|
|
|
let custodian = Pubkey::new_rand();
|
2019-09-04 13:34:09 -07:00
|
|
|
let total_lamports = 100;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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
|
|
|
|
|
|
|
let to = Pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_account = Account::new_ref(1, 0, &system_program::id());
|
|
|
|
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
|
|
|
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
|
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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
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
|
|
|
{
|
|
|
|
let mut signers_with_custodian = signers.clone();
|
|
|
|
signers_with_custodian.insert(custodian);
|
|
|
|
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(),
|
|
|
|
&signers_with_custodian,
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
2019-09-16 17:47:42 -07:00
|
|
|
// reset balance
|
2020-01-22 09:11:56 -08:00
|
|
|
stake_keyed_account.account.borrow_mut().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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers,
|
2019-09-04 13:34:09 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
2019-06-21 22:28:34 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
let mut stake = Stake::new(
|
|
|
|
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,
|
|
|
|
stake.redeem_rewards(1_000_000_000.0, &vote_state, None)
|
|
|
|
);
|
|
|
|
|
|
|
|
// 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!(
|
|
|
|
Some((0, stake_lamports * 2)),
|
|
|
|
stake.redeem_rewards(1.0, &vote_state, None)
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
stake.delegation.stake,
|
|
|
|
stake_lamports + (stake_lamports * 2)
|
|
|
|
);
|
|
|
|
assert_eq!(stake.credits_observed, 2);
|
|
|
|
}
|
|
|
|
|
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
|
2019-11-12 12:33:40 -08:00
|
|
|
let mut stake = Stake::new(
|
|
|
|
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,
|
|
|
|
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 should be able to collect exactly 2
|
2019-04-07 21:45:28 -07:00
|
|
|
assert_eq!(
|
2019-11-25 13:14:32 -08:00
|
|
|
Some((0, stake.delegation.stake * 2, 2)),
|
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-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!(
|
2019-11-25 13:14:32 -08:00
|
|
|
Some((0, stake.delegation.stake * 1, 2)),
|
2019-08-12 20:59:57 -07:00
|
|
|
stake.calculate_rewards(1.0, &vote_state, None)
|
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!(
|
|
|
|
Some((0, stake.delegation.stake * 1, 3)),
|
|
|
|
stake.calculate_rewards(1.0, &vote_state, None)
|
|
|
|
);
|
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-01-21 19:08:40 -08:00
|
|
|
Some((0, stake.delegation.stake * 2, 4)),
|
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-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((
|
|
|
|
0,
|
2020-01-21 19:08:40 -08:00
|
|
|
stake.delegation.stake * 2 // epoch 0
|
|
|
|
+ stake.delegation.stake * 1 // epoch 1
|
|
|
|
+ stake.delegation.stake * 1, // epoch 2
|
|
|
|
4
|
2019-11-25 13:14:32 -08:00
|
|
|
)),
|
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-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)),
|
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-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)),
|
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
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
#[test]
|
|
|
|
fn test_authorize_uninit() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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-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
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
assert_eq!(
|
2020-01-01 11:03:29 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&stake_pubkey,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
&signers,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
2019-10-31 11:07:27 -07:00
|
|
|
Err(InstructionError::InvalidAccountData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
#[test]
|
|
|
|
fn test_authorize_lockup() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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");
|
|
|
|
|
|
|
|
let to = Pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let to_account = Account::new_ref(1, 0, &system_program::id());
|
|
|
|
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-01-22 17:54:06 -08:00
|
|
|
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
2019-09-12 19:03:28 -07:00
|
|
|
|
|
|
|
let stake_pubkey0 = Pubkey::new_rand();
|
2019-10-14 15:02:24 -07:00
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
2019-09-26 13:29:29 -07:00
|
|
|
assert_eq!(
|
2020-01-01 11:03:29 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&stake_pubkey0,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
&signers,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
2019-09-26 13:29:29 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2020-01-01 11:03:29 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&stake_pubkey0,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
&signers,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
2019-09-26 13:29:29 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2019-10-31 11:07:27 -07:00
|
|
|
if let StakeState::Initialized(Meta { authorized, .. }) =
|
2020-01-22 09:11:56 -08:00
|
|
|
StakeState::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 {
|
|
|
|
assert!(false);
|
2019-09-12 19:03:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// A second authorization signed by the stake_keyed_account should fail
|
|
|
|
let stake_pubkey1 = Pubkey::new_rand();
|
|
|
|
assert_eq!(
|
2020-01-01 11:03:29 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&stake_pubkey1,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
&signers,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
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
|
|
|
|
let stake_pubkey2 = Pubkey::new_rand();
|
|
|
|
assert_eq!(
|
2020-01-01 11:03:29 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&stake_pubkey2,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
&signers0,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
2019-09-12 19:03:28 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2019-10-31 11:07:27 -07:00
|
|
|
if let StakeState::Initialized(Meta { authorized, .. }) =
|
2020-01-22 09:11:56 -08:00
|
|
|
StakeState::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!(
|
2020-01-01 11:03:29 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&stake_pubkey2,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
&signers0,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
2019-09-26 13:29:29 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2019-10-31 11:07:27 -07:00
|
|
|
if let StakeState::Initialized(Meta { authorized, .. }) =
|
2020-01-22 09:11:56 -08:00
|
|
|
StakeState::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-14 15:02:24 -07:00
|
|
|
let signers2 = vec![stake_pubkey2].into_iter().collect();
|
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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers, // old signer
|
2019-10-10 14:46:38 -07:00
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
// 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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&signers2,
|
2019-09-12 19:03:28 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-25 18:03:26 -08:00
|
|
|
#[test]
|
|
|
|
fn test_authorize_override() {
|
|
|
|
let withdrawer_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let stake_account = Account::new_ref_data_with_space(
|
|
|
|
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.
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let signers = vec![withdrawer_pubkey].into_iter().collect();
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&stake_pubkey,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
&signers,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// Attack! The stake key (a hot key) is stolen and used to authorize a new staker.
|
|
|
|
let mallory_pubkey = Pubkey::new_rand();
|
|
|
|
let signers = vec![stake_pubkey].into_iter().collect();
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&mallory_pubkey,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
&signers,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// Verify the original staker no longer has access.
|
|
|
|
let new_stake_pubkey = Pubkey::new_rand();
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&new_stake_pubkey,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
&signers,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
|
|
|
Err(InstructionError::MissingRequiredSignature)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Verify the withdrawer (pulled from cold storage) can save the day.
|
|
|
|
let signers = vec![withdrawer_pubkey].into_iter().collect();
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&new_stake_pubkey,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
&signers,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
|
|
|
|
// Attack! Verify the staker cannot be used to authorize a withdraw.
|
|
|
|
let signers = vec![new_stake_pubkey].into_iter().collect();
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&mallory_pubkey,
|
|
|
|
StakeAuthorize::Withdrawer,
|
|
|
|
&signers,
|
|
|
|
&Clock::default()
|
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-10-31 11:07:27 -07:00
|
|
|
#[test]
|
|
|
|
fn test_split_source_uninitialized() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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");
|
|
|
|
|
|
|
|
let split_stake_pubkey = Pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let split_stake_account = Account::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!(
|
2020-01-22 09:11:56 -08: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() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_lamports,
|
2019-11-25 13:14:32 -08:00
|
|
|
&StakeState::Stake(Meta::auto(&stake_pubkey), Stake::just_stake(stake_lamports)),
|
2019-10-31 11:07:27 -07:00
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let split_stake_pubkey = Pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let split_stake_account = Account::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)
|
|
|
|
);
|
|
|
|
}
|
2019-11-25 13:14:32 -08:00
|
|
|
impl Stake {
|
|
|
|
fn just_stake(stake: u64) -> Self {
|
|
|
|
Self {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake,
|
|
|
|
..Delegation::default()
|
|
|
|
},
|
|
|
|
..Stake::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-31 11:07:27 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_split_more_than_staked() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::new_ref_data_with_space(
|
2019-10-31 11:07:27 -07:00
|
|
|
stake_lamports,
|
|
|
|
&StakeState::Stake(
|
|
|
|
Meta::auto(&stake_pubkey),
|
2019-11-25 13:14:32 -08:00
|
|
|
Stake::just_stake(stake_lamports / 2 - 1),
|
2019-10-31 11:07:27 -07:00
|
|
|
),
|
|
|
|
std::mem::size_of::<StakeState>(),
|
|
|
|
&id(),
|
|
|
|
)
|
|
|
|
.expect("stake_account");
|
|
|
|
|
|
|
|
let split_stake_pubkey = Pubkey::new_rand();
|
2020-01-22 17:54:06 -08:00
|
|
|
let split_stake_account = Account::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() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let split_stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let rent_exempt_reserve = 10;
|
|
|
|
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),
|
2019-12-16 15:56:34 -08:00
|
|
|
StakeState::Stake(
|
|
|
|
meta,
|
|
|
|
Stake::just_stake(stake_lamports - rent_exempt_reserve),
|
|
|
|
),
|
2019-10-31 11:07:27 -07:00
|
|
|
] {
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let split_stake_account = Account::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
|
|
|
|
|
|
|
// not enough to make a stake account
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(
|
|
|
|
rent_exempt_reserve - 1,
|
2020-01-22 17:54:06 -08:00
|
|
|
&split_stake_keyed_account,
|
2019-10-31 11:07:27 -07:00
|
|
|
&signers
|
|
|
|
),
|
|
|
|
Err(InstructionError::InsufficientFunds)
|
|
|
|
);
|
|
|
|
|
|
|
|
// doesn't leave enough for initial stake
|
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(
|
|
|
|
(stake_lamports - rent_exempt_reserve) + 1,
|
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
|
2020-01-22 09:11:56 -08:00
|
|
|
split_stake_keyed_account.account.borrow_mut().lamports = 1_000;
|
2019-10-31 11:07:27 -07:00
|
|
|
assert_eq!(
|
|
|
|
stake_keyed_account.split(
|
|
|
|
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
|
|
|
|
),
|
|
|
|
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 {
|
|
|
|
stake: stake_lamports - rent_exempt_reserve,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
2019-10-31 11:07:27 -07:00
|
|
|
..*stake
|
|
|
|
}
|
|
|
|
))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2020-01-22 09:11:56 -08:00
|
|
|
stake_keyed_account.account.borrow().lamports,
|
|
|
|
rent_exempt_reserve
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
split_stake_keyed_account.account.borrow().lamports,
|
2019-10-31 11:07:27 -07:00
|
|
|
1_000 + stake_lamports - rent_exempt_reserve
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_split() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
|
|
|
|
let split_stake_pubkey = Pubkey::new_rand();
|
|
|
|
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)),
|
2019-11-25 13:14:32 -08:00
|
|
|
StakeState::Stake(Meta::auto(&stake_pubkey), Stake::just_stake(stake_lamports)),
|
2019-10-31 11:07:27 -07:00
|
|
|
] {
|
2020-01-22 17:54:06 -08:00
|
|
|
let split_stake_account = Account::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-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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!(
|
2020-01-22 09:11:56 -08: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
|
2020-01-22 09:11:56 -08:00
|
|
|
stake_keyed_account.account.borrow_mut().lamports = stake_lamports;
|
2019-10-31 11:07:27 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-16 15:56:34 -08:00
|
|
|
#[test]
|
|
|
|
fn test_split_100_percent_of_source() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
|
|
|
let rent_exempt_reserve = 10;
|
|
|
|
|
|
|
|
let split_stake_pubkey = Pubkey::new_rand();
|
|
|
|
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),
|
|
|
|
StakeState::Stake(
|
|
|
|
meta,
|
|
|
|
Stake::just_stake(stake_lamports - rent_exempt_reserve),
|
|
|
|
),
|
|
|
|
] {
|
2020-01-22 17:54:06 -08:00
|
|
|
let split_stake_account = Account::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
|
|
|
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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!(
|
2020-01-22 09:11:56 -08: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());
|
|
|
|
assert_eq!(Ok(*state), stake_keyed_account.state());
|
|
|
|
}
|
|
|
|
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()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Ok(StakeState::Stake(
|
|
|
|
*meta,
|
|
|
|
Stake {
|
|
|
|
delegation: Delegation {
|
|
|
|
stake: 0,
|
|
|
|
..stake.delegation
|
|
|
|
},
|
|
|
|
..*stake
|
|
|
|
}
|
|
|
|
)),
|
|
|
|
stake_keyed_account.state()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset
|
2020-01-22 09:11:56 -08:00
|
|
|
stake_keyed_account.account.borrow_mut().lamports = stake_lamports;
|
2019-12-16 15:56:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-19 14:37:47 -08:00
|
|
|
#[test]
|
|
|
|
fn test_lockup_is_expired() {
|
|
|
|
let custodian = Pubkey::new_rand();
|
|
|
|
let signers = [custodian].iter().cloned().collect::<HashSet<_>>();
|
|
|
|
let lockup = Lockup {
|
|
|
|
epoch: 1,
|
|
|
|
unix_timestamp: 1,
|
|
|
|
custodian,
|
|
|
|
};
|
|
|
|
// neither time
|
|
|
|
assert_eq!(
|
|
|
|
lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 0,
|
|
|
|
unix_timestamp: 0,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
&HashSet::new()
|
|
|
|
),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
// not timestamp
|
|
|
|
assert_eq!(
|
|
|
|
lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 2,
|
|
|
|
unix_timestamp: 0,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
&HashSet::new()
|
|
|
|
),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
// not epoch
|
|
|
|
assert_eq!(
|
|
|
|
lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 0,
|
|
|
|
unix_timestamp: 2,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
&HashSet::new()
|
|
|
|
),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
// both, no custodian
|
|
|
|
assert_eq!(
|
|
|
|
lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 1,
|
|
|
|
unix_timestamp: 1,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
&HashSet::new()
|
|
|
|
),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
// neither, but custodian
|
|
|
|
assert_eq!(
|
|
|
|
lockup.is_in_force(
|
|
|
|
&Clock {
|
|
|
|
epoch: 0,
|
|
|
|
unix_timestamp: 0,
|
|
|
|
..Clock::default()
|
|
|
|
},
|
|
|
|
&signers,
|
|
|
|
),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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() {
|
|
|
|
let stake_pubkey = Pubkey::new_rand();
|
|
|
|
let stake_lamports = 42;
|
2020-01-22 17:54:06 -08:00
|
|
|
let stake_account = Account::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
|
|
|
|
|
|
|
let vote_pubkey = 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,
|
|
|
|
&Pubkey::new_rand(),
|
|
|
|
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,
|
|
|
|
)
|
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();
|
|
|
|
|
2019-09-12 19:03:28 -07:00
|
|
|
let new_staker_pubkey = Pubkey::new_rand();
|
|
|
|
assert_eq!(
|
2020-01-01 11:03:29 -08:00
|
|
|
stake_keyed_account.authorize(
|
|
|
|
&new_staker_pubkey,
|
|
|
|
StakeAuthorize::Staker,
|
|
|
|
&signers,
|
|
|
|
&clock,
|
|
|
|
),
|
2019-09-12 19:03:28 -07:00
|
|
|
Ok(())
|
|
|
|
);
|
2020-01-22 09:11:56 -08:00
|
|
|
let authorized =
|
|
|
|
StakeState::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
|
|
|
|
|
|
|
let other_pubkey = 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
|
|
|
|
|
|
|
let new_voter_pubkey = Pubkey::new_rand();
|
|
|
|
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,
|
|
|
|
&Pubkey::new_rand(),
|
|
|
|
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,
|
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(),
|
2019-10-14 15:02:24 -07:00
|
|
|
&new_signers
|
2019-09-12 19:03:28 -07:00
|
|
|
),
|
|
|
|
Ok(())
|
|
|
|
);
|
2020-01-22 09:11:56 -08:00
|
|
|
let stake =
|
|
|
|
StakeState::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
|
|
|
}
|
2019-04-01 16:45:53 -07:00
|
|
|
}
|