use crate::{config::Config, id, stake_instruction::StakeError};
use serde_derive::{Deserialize, Serialize};
use solana_sdk::{
account::{Account, KeyedAccount},
account_utils::State,
clock::{Epoch, Slot},
instruction::InstructionError,
pubkey::Pubkey,
sysvar::{
self,
stake_history::{StakeHistory, StakeHistoryEntry},
},
};
use solana_vote_api::vote_state::VoteState;
use std::collections::HashSet;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
#[allow(clippy::large_enum_variant)]
pub enum StakeState {
Uninitialized,
Initialized(Authorized, Lockup),
Stake(Authorized, Lockup, Stake),
RewardsPool,
}
impl Default for StakeState {
fn default() -> Self {
StakeState::Uninitialized
}
}
impl StakeState {
pub fn from(account: &Account) -> Option<StakeState> {
account.state().ok()
}
pub fn stake_from(account: &Account) -> Option<Stake> {
Self::from(account).and_then(|state: Self| state.stake())
}
pub fn authorized_from(account: &Account) -> Option<Authorized> {
Self::from(account).and_then(|state: Self| state.authorized())
}
pub fn stake(&self) -> Option<Stake> {
match self {
StakeState::Stake(_authorized, _lockup, stake) => Some(*stake),
_ => None,
}
}
pub fn authorized(&self) -> Option<Authorized> {
match self {
StakeState::Stake(authorized, _lockup, _stake) => Some(*authorized),
_ => None,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub enum StakeAuthorize {
Staker,
Withdrawer,
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Lockup {
pub slot: Slot,
pub custodian: Pubkey,
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Authorized {
pub staker: Pubkey,
pub withdrawer: Pubkey,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Stake {
pub voter_pubkey: Pubkey,
pub voter_pubkey_epoch: Epoch,
pub credits_observed: u64,
pub stake: u64,
pub activation_epoch: Epoch,
pub deactivation_epoch: Epoch,
pub config: Config,
pub prior_delegates: [(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES],
pub prior_delegates_idx: usize,
}
const MAX_PRIOR_DELEGATES: usize = 32;
impl Default for Stake {
fn default() -> Self {
Self {
voter_pubkey: Pubkey::default(),
voter_pubkey_epoch: 0,
credits_observed: 0,
stake: 0,
activation_epoch: 0,
deactivation_epoch: std::u64::MAX,
config: Config::default(),
prior_delegates: <[(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES]>::default(),
prior_delegates_idx: MAX_PRIOR_DELEGATES - 1,
}
}
}
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),
}
}
pub fn authorize(
&mut self,
signers: &HashSet<Pubkey>,
new_authorized: &Pubkey,
stake_authorize: StakeAuthorize,
) -> Result<(), InstructionError> {
self.check(signers, stake_authorize)?;
match stake_authorize {
StakeAuthorize::Staker => self.staker = *new_authorized,
StakeAuthorize::Withdrawer => self.withdrawer = *new_authorized,
}
Ok(())
}
}
impl Stake {
fn is_bootstrap(&self) -> bool {
self.activation_epoch == std::u64::MAX
}
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
self.stake_activating_and_deactivating(epoch, history).0
}
pub fn voter_pubkey(&self, epoch: Epoch) -> &Pubkey {
let prior_delegate_pubkey = &self.prior_delegates[self.prior_delegates_idx].0;
if epoch > self.voter_pubkey_epoch || *prior_delegate_pubkey == Pubkey::default() {
&self.voter_pubkey
} else {
assert!(epoch <= self.prior_delegates[self.prior_delegates_idx].2);
prior_delegate_pubkey
}
}
fn stake_activating_and_deactivating(
&self,
epoch: Epoch,
history: Option<&StakeHistory>,
) -> (u64, u64, u64) {
let (stake, activating) = self.stake_and_activating(epoch, history);
if epoch < self.deactivation_epoch {
(stake, activating, 0) } else if epoch == self.deactivation_epoch {
(stake, 0, stake.min(self.stake)) } else if let Some((history, mut entry)) = history.and_then(|history| {
history
.get(&self.deactivation_epoch)
.map(|entry| (history, entry))
}) {
let mut effective_stake = stake;
let mut next_epoch = self.deactivation_epoch;
loop {
if entry.deactivating == 0 {
break;
}
let weight = effective_stake as f64 / entry.deactivating as f64;
effective_stake = effective_stake.saturating_sub(
((weight * entry.effective as f64 * self.config.cooldown_rate) as u64).max(1),
);
if effective_stake == 0 {
break;
}
next_epoch += 1;
if next_epoch >= epoch {
break;
}
if let Some(next_entry) = history.get(&next_epoch) {
entry = next_entry;
} else {
break;
}
}
(effective_stake, 0, effective_stake)
} else {
(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 {
(0, 0)
} else if let Some((history, mut entry)) = history.and_then(|history| {
history
.get(&self.activation_epoch)
.map(|entry| (history, entry))
}) {
let mut effective_stake = 0;
let mut next_epoch = self.activation_epoch;
loop {
if entry.activating == 0 {
break;
}
let weight = (self.stake - effective_stake) as f64 / entry.activating as f64;
effective_stake +=
((weight * entry.effective as f64 * self.config.warmup_rate) as u64).max(1);
if effective_stake >= self.stake {
effective_stake = self.stake;
break;
}
next_epoch += 1;
if next_epoch >= epoch || next_epoch >= self.deactivation_epoch {
break;
}
if let Some(next_entry) = history.get(&next_epoch) {
entry = next_entry;
} else {
break;
}
}
(effective_stake, self.stake - effective_stake)
} else {
(self.stake, 0)
}
}
fn calculate_rewards(
&self,
point_value: f64,
vote_state: &VoteState,
stake_history: Option<&StakeHistory>,
) -> Option<(u64, u64, u64)> {
if self.credits_observed >= vote_state.credits() {
return None;
}
let mut credits_observed = self.credits_observed;
let mut total_rewards = 0f64;
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
let epoch_credits = if self.credits_observed < *prev_credits {
credits - prev_credits
} else if self.credits_observed < *credits {
credits - credits_observed
} else {
0
};
total_rewards +=
(self.stake(*epoch, stake_history) * epoch_credits) as f64 * point_value;
credits_observed = credits_observed.max(*credits);
}
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 {
return None;
}
Some((
voter_rewards as u64,
staker_rewards as u64,
credits_observed,
))
}
fn new_bootstrap(stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState) -> Self {
Self::new(
stake,
voter_pubkey,
vote_state,
std::u64::MAX,
&Config::default(),
)
}
fn redelegate(
&mut self,
voter_pubkey: &Pubkey,
vote_state: &VoteState,
epoch: Epoch,
) -> Result<(), StakeError> {
if epoch != self.voter_pubkey_epoch {
self.prior_delegates_idx += 1;
self.prior_delegates_idx %= MAX_PRIOR_DELEGATES;
self.prior_delegates[self.prior_delegates_idx] =
(self.voter_pubkey, self.voter_pubkey_epoch, epoch);
}
self.voter_pubkey = *voter_pubkey;
self.voter_pubkey_epoch = epoch;
self.credits_observed = vote_state.credits();
Ok(())
}
fn new(
stake: u64,
voter_pubkey: &Pubkey,
vote_state: &VoteState,
activation_epoch: Epoch,
config: &Config,
) -> Self {
Self {
stake,
activation_epoch,
voter_pubkey: *voter_pubkey,
voter_pubkey_epoch: activation_epoch,
credits_observed: vote_state.credits(),
config: *config,
..Stake::default()
}
}
fn deactivate(&mut self, epoch: u64) -> Result<(), StakeError> {
if self.deactivation_epoch != std::u64::MAX {
Err(StakeError::AlreadyDeactivated)
} else {
self.deactivation_epoch = epoch;
Ok(())
}
}
}
pub trait StakeAccount {
fn initialize(
&mut self,
authorized: &Authorized,
lockup: &Lockup,
) -> Result<(), InstructionError>;
fn authorize(
&mut self,
authority: &Pubkey,
stake_authorize: StakeAuthorize,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
fn delegate_stake(
&mut self,
vote_account: &KeyedAccount,
clock: &sysvar::clock::Clock,
config: &Config,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
fn deactivate_stake(
&mut self,
clock: &sysvar::clock::Clock,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
fn redeem_vote_credits(
&mut self,
vote_account: &mut KeyedAccount,
rewards_account: &mut KeyedAccount,
rewards: &sysvar::rewards::Rewards,
stake_history: &sysvar::stake_history::StakeHistory,
) -> Result<(), InstructionError>;
fn withdraw(
&mut self,
lamports: u64,
to: &mut KeyedAccount,
clock: &sysvar::clock::Clock,
stake_history: &sysvar::stake_history::StakeHistory,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
}
impl<'a> StakeAccount for KeyedAccount<'a> {
fn initialize(
&mut self,
authorized: &Authorized,
lockup: &Lockup,
) -> Result<(), InstructionError> {
if let StakeState::Uninitialized = self.state()? {
self.set_state(&StakeState::Initialized(*authorized, *lockup))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn authorize(
&mut self,
authority: &Pubkey,
stake_authorize: StakeAuthorize,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
let stake_state = self.state()?;
if let StakeState::Stake(mut authorized, lockup, stake) = stake_state {
authorized.authorize(signers, authority, stake_authorize)?;
self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else if let StakeState::Initialized(mut authorized, lockup) = stake_state {
authorized.authorize(signers, authority, stake_authorize)?;
self.set_state(&StakeState::Initialized(authorized, lockup))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn delegate_stake(
&mut self,
vote_account: &KeyedAccount,
clock: &sysvar::clock::Clock,
config: &Config,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
if let StakeState::Initialized(authorized, lockup) = self.state()? {
authorized.check(signers, StakeAuthorize::Staker)?;
let stake = Stake::new(
self.account.lamports,
vote_account.unsigned_key(),
&vote_account.state()?,
clock.epoch,
config,
);
self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? {
authorized.check(signers, StakeAuthorize::Staker)?;
stake.redelegate(
vote_account.unsigned_key(),
&vote_account.state()?,
clock.epoch,
)?;
self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn deactivate_stake(
&mut self,
clock: &sysvar::clock::Clock,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? {
authorized.check(signers, StakeAuthorize::Staker)?;
stake.deactivate(clock.epoch)?;
self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn redeem_vote_credits(
&mut self,
vote_account: &mut KeyedAccount,
rewards_account: &mut KeyedAccount,
rewards: &sysvar::rewards::Rewards,
stake_history: &sysvar::stake_history::StakeHistory,
) -> Result<(), InstructionError> {
if let (StakeState::Stake(authorized, lockup, mut stake), StakeState::RewardsPool) =
(self.state()?, rewards_account.state()?)
{
let vote_state: VoteState = vote_account.state()?;
if stake.voter_pubkey != *vote_account.unsigned_key() {
return Err(InstructionError::InvalidArgument);
}
if let Some((voters_reward, stakers_reward, credits_observed)) = stake
.calculate_rewards(
rewards.validator_point_value,
&vote_state,
Some(stake_history),
)
{
if rewards_account.account.lamports < (stakers_reward + voters_reward) {
return Err(InstructionError::UnbalancedInstruction);
}
rewards_account.account.lamports -= stakers_reward + voters_reward;
self.account.lamports += stakers_reward;
vote_account.account.lamports += voters_reward;
stake.credits_observed = credits_observed;
stake.stake += stakers_reward;
self.set_state(&StakeState::Stake(authorized, lockup, stake))
} else {
Err(StakeError::NoCreditsToRedeem.into())
}
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn withdraw(
&mut self,
lamports: u64,
to: &mut KeyedAccount,
clock: &sysvar::clock::Clock,
stake_history: &sysvar::stake_history::StakeHistory,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
let lockup = match self.state()? {
StakeState::Stake(authorized, lockup, stake) => {
authorized.check(signers, StakeAuthorize::Withdrawer)?;
let staked = if clock.epoch >= stake.deactivation_epoch {
stake.stake(clock.epoch, Some(stake_history))
} else {
stake.stake
};
if lamports > self.account.lamports.saturating_sub(staked) {
return Err(InstructionError::InsufficientFunds);
}
lockup
}
StakeState::Initialized(authorized, lockup) => {
authorized.check(signers, StakeAuthorize::Withdrawer)?;
lockup
}
StakeState::Uninitialized => {
if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
Lockup::default() }
_ => return Err(InstructionError::InvalidAccountData),
};
if lockup.slot > clock.slot && lockup.custodian != *to.unsigned_key() {
return Err(StakeError::LockupInForce.into());
}
if lamports > self.account.lamports {
return Err(InstructionError::InsufficientFunds);
}
self.account.lamports -= lamports;
to.account.lamports += lamports;
Ok(())
}
}
pub fn new_stake_history_entry<'a, I>(
epoch: Epoch,
stakes: I,
history: Option<&StakeHistory>,
) -> StakeHistoryEntry
where
I: Iterator<Item = &'a Stake>,
{
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))
});
StakeHistoryEntry {
effective,
activating,
deactivating,
}
}
pub fn create_account(
authorized: &Pubkey,
voter_pubkey: &Pubkey,
vote_account: &Account,
lamports: u64,
) -> Account {
let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id());
let vote_state = VoteState::from(vote_account).expect("vote_state");
stake_account
.set_state(&StakeState::Stake(
Authorized {
staker: *authorized,
withdrawer: *authorized,
},
Lockup::default(),
Stake::new_bootstrap(lamports, voter_pubkey, &vote_state),
))
.expect("set_state");
stake_account
}
#[cfg(test)]
mod tests {
use super::*;
use crate::id;
use solana_sdk::{account::Account, pubkey::Pubkey, system_program};
use solana_vote_api::vote_state;
#[test]
fn test_stake_state_stake_from_fail() {
let mut stake_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
stake_account
.set_state(&StakeState::default())
.expect("set_state");
assert_eq!(StakeState::stake_from(&stake_account), None);
}
#[test]
fn test_stake_is_bootstrap() {
assert_eq!(
Stake {
activation_epoch: std::u64::MAX,
..Stake::default()
}
.is_bootstrap(),
true
);
assert_eq!(
Stake {
activation_epoch: 0,
..Stake::default()
}
.is_bootstrap(),
false
);
}
#[test]
fn test_stake_delegate_stake() {
let clock = sysvar::clock::Clock {
epoch: 1,
..sysvar::clock::Clock::default()
};
let vote_pubkey = Pubkey::new_rand();
let mut vote_state = VoteState::default();
for i in 0..1000 {
vote_state.process_slot_vote_unchecked(i);
}
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&vote_state).unwrap();
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space(
stake_lamports,
&StakeState::Initialized(
Authorized {
staker: stake_pubkey,
withdrawer: stake_pubkey,
},
Lockup::default(),
),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
{
let stake_state: StakeState = stake_keyed_account.state().unwrap();
assert_eq!(
stake_state,
StakeState::Initialized(
Authorized {
staker: stake_pubkey,
withdrawer: stake_pubkey,
},
Lockup::default(),
)
);
}
let mut signers = HashSet::default();
assert_eq!(
stake_keyed_account.delegate_stake(
&vote_keyed_account,
&clock,
&Config::default(),
&signers,
),
Err(InstructionError::MissingRequiredSignature)
);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
signers.insert(stake_pubkey);
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers,)
.is_ok());
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
assert_eq!(
stake,
Stake {
voter_pubkey: vote_pubkey,
voter_pubkey_epoch: clock.epoch,
credits_observed: vote_state.credits(),
stake: stake_lamports,
activation_epoch: clock.epoch,
deactivation_epoch: std::u64::MAX,
..Stake::default()
}
);
for epoch in 0..=clock.epoch + 1 {
assert_eq!(stake.voter_pubkey(epoch), &vote_pubkey);
}
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
.is_ok());
let stake_state = StakeState::RewardsPool;
stake_keyed_account.set_state(&stake_state).unwrap();
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
.is_err());
}
#[test]
fn test_stake_redelegate() {
let mut stake = Stake {
voter_pubkey: Pubkey::new_rand(),
voter_pubkey_epoch: 0,
..Stake::default()
};
for epoch in 0..=MAX_PRIOR_DELEGATES + 1 {
let voter_pubkey = Pubkey::new_rand();
let _ignored = stake.redelegate(&voter_pubkey, &VoteState::default(), 0);
assert_eq!(stake.voter_pubkey(epoch as u64), &voter_pubkey);
}
let voter_pubkey = Pubkey::new_rand();
let prior_voter_pubkey = stake.voter_pubkey;
let _ignored = stake.redelegate(&voter_pubkey, &VoteState::default(), 1);
assert_eq!(stake.voter_pubkey(0 as u64), &prior_voter_pubkey);
assert_eq!(stake.voter_pubkey(1 as u64), &prior_voter_pubkey);
assert_eq!(stake.voter_pubkey(2 as u64), &voter_pubkey);
for epoch in 0..=MAX_PRIOR_DELEGATES + 1 {
let voter_pubkey = Pubkey::new_rand();
let prior_voter_pubkey = stake.voter_pubkey;
let _ignored = stake.redelegate(&voter_pubkey, &VoteState::default(), epoch as u64);
assert_eq!(stake.voter_pubkey(epoch as u64), &prior_voter_pubkey);
assert_eq!(stake.voter_pubkey((epoch + 1) as u64), &voter_pubkey);
}
}
fn create_stake_history_from_stakes(
bootstrap: Option<u64>,
epochs: std::ops::Range<Epoch>,
stakes: &[Stake],
) -> StakeHistory {
let mut stake_history = StakeHistory::default();
let bootstrap_stake = if let Some(bootstrap) = bootstrap {
vec![Stake {
activation_epoch: std::u64::MAX,
stake: bootstrap,
..Stake::default()
}]
} else {
vec![]
};
for epoch in epochs {
let entry = new_stake_history_entry(
epoch,
stakes.iter().chain(bootstrap_stake.iter()),
Some(&stake_history),
);
stake_history.add(epoch, entry);
}
stake_history
}
#[test]
fn test_stake_activating_and_deactivating() {
let stake = Stake {
stake: 1_000,
activation_epoch: 0, deactivation_epoch: 5,
..Stake::default()
};
let increment = (1_000 as f64 * stake.config.warmup_rate) as u64;
let mut stake_history = StakeHistory::default();
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_eq!(
stake.stake_activating_and_deactivating(stake.deactivation_epoch, Some(&stake_history)),
(stake.stake, 0, stake.stake)
);
assert_eq!(
stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 1,
Some(&stake_history)
),
(0, 0, 0)
);
stake_history.add(
0u64, StakeHistoryEntry {
effective: 1_000,
activating: 0,
..StakeHistoryEntry::default()
},
);
assert_eq!(
stake.stake_activating_and_deactivating(1, Some(&stake_history)),
(0, stake.stake, 0)
);
stake_history.add(
0u64, StakeHistoryEntry {
effective: 1_000,
activating: 1_000,
..StakeHistoryEntry::default()
},
);
assert_eq!(
stake.stake_activating_and_deactivating(2, Some(&stake_history)),
(increment, stake.stake - increment, 0)
);
let mut stake_history = StakeHistory::default();
stake_history.add(
stake.deactivation_epoch, StakeHistoryEntry {
effective: 1_000,
activating: 0,
..StakeHistoryEntry::default()
},
);
assert_eq!(
stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 1,
Some(&stake_history)
),
(stake.stake, 0, stake.stake) );
stake_history.add(
stake.deactivation_epoch, StakeHistoryEntry {
effective: 1_000,
deactivating: 1_000,
..StakeHistoryEntry::default()
},
);
assert_eq!(
stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 2,
Some(&stake_history)
),
(stake.stake - increment, 0, stake.stake - increment) );
}
#[test]
fn test_stop_activating_after_deactivation() {
solana_logger::setup();
let stake = Stake {
stake: 1_000,
activation_epoch: 0,
deactivation_epoch: 3,
..Stake::default()
};
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];
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 {
let increase = (effective as f64 * stake.config.warmup_rate) as u64;
effective += increase.min(activating);
other_activations.push(0);
} else {
let decrease = (effective as f64 * stake.config.cooldown_rate) as u64;
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)
);
}
}
#[test]
fn test_stake_warmup_cooldown_sub_integer_moves() {
let stakes = [Stake {
stake: 2,
activation_epoch: 0, deactivation_epoch: 5,
..Stake::default()
}];
let epochs = 7;
let bootstrap = (stakes[0].config.warmup_rate * 100.0 / 2.0) as u64;
let stake_history = create_stake_history_from_stakes(Some(bootstrap), 0..epochs, &stakes);
let mut max_stake = 0;
let mut min_stake = 2;
for epoch in 0..epochs {
let stake = stakes
.iter()
.map(|stake| stake.stake(epoch, Some(&stake_history)))
.sum::<u64>();
max_stake = max_stake.max(stake);
min_stake = min_stake.min(stake);
}
assert_eq!(max_stake, 2);
assert_eq!(min_stake, 0);
}
#[test]
fn test_stake_warmup_cooldown() {
let stakes = [
Stake {
stake: 1_000,
activation_epoch: std::u64::MAX,
..Stake::default()
},
Stake {
stake: 1_000,
activation_epoch: 0,
deactivation_epoch: 9,
..Stake::default()
},
Stake {
stake: 1_000,
activation_epoch: 1,
deactivation_epoch: 6,
..Stake::default()
},
Stake {
stake: 1_000,
activation_epoch: 2,
deactivation_epoch: 5,
..Stake::default()
},
Stake {
stake: 1_000,
activation_epoch: 2,
deactivation_epoch: 4,
..Stake::default()
},
Stake {
stake: 1_000,
activation_epoch: 4,
deactivation_epoch: 4,
..Stake::default()
},
];
let epochs = 20;
let stake_history = create_stake_history_from_stakes(None, 0..epochs, &stakes);
let mut prev_total_effective_stake = stakes
.iter()
.map(|stake| stake.stake(0, Some(&stake_history)))
.sum::<u64>();
for epoch in 1..epochs {
let total_effective_stake = stakes
.iter()
.map(|stake| stake.stake(epoch, Some(&stake_history)))
.sum::<u64>();
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
};
assert!(
delta
<= ((prev_total_effective_stake as f64 * Config::default().warmup_rate) as u64)
.max(1)
);
prev_total_effective_stake = total_effective_stake;
}
}
#[test]
fn test_stake_lockup() {
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let mut stake_account =
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
let custodian = Pubkey::new_rand();
assert_eq!(
stake_keyed_account.initialize(
&Authorized {
staker: stake_pubkey,
withdrawer: stake_pubkey
},
&Lockup { slot: 1, custodian }
),
Ok(())
);
assert_eq!(
StakeState::from(&stake_keyed_account.account).unwrap(),
StakeState::Initialized(
Authorized {
staker: stake_pubkey,
withdrawer: stake_pubkey
},
Lockup { slot: 1, custodian }
)
);
assert_eq!(
stake_keyed_account.initialize(&Authorized::default(), &Lockup::default()),
Err(InstructionError::InvalidAccountData)
);
}
#[test]
fn test_deactivate_stake() {
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space(
stake_lamports,
&StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let clock = sysvar::clock::Clock {
epoch: 1,
..sysvar::clock::Clock::default()
};
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &signers),
Err(InstructionError::InvalidAccountData)
);
let vote_pubkey = Pubkey::new_rand();
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!(
stake_keyed_account.delegate_stake(
&vote_keyed_account,
&clock,
&Config::default(),
&signers
),
Ok(())
);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &HashSet::default()),
Err(InstructionError::MissingRequiredSignature)
);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &signers),
Ok(())
);
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &signers),
Err(StakeError::AlreadyDeactivated.into())
);
}
#[test]
fn test_withdraw_stake() {
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space(
stake_lamports,
&StakeState::Uninitialized,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let mut clock = sysvar::clock::Clock::default();
let to = Pubkey::new_rand();
let mut to_account = Account::new(1, 0, &system_program::id());
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
assert_eq!(
stake_keyed_account.withdraw(
stake_lamports,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&HashSet::default(),
),
Err(InstructionError::MissingRequiredSignature)
);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
stake_keyed_account.withdraw(
stake_lamports,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers,
),
Ok(())
);
assert_eq!(stake_account.lamports, 0);
stake_account.lamports = stake_lamports;
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let custodian = Pubkey::new_rand();
stake_keyed_account
.initialize(
&Authorized::auto(&stake_pubkey),
&Lockup { slot: 0, custodian },
)
.unwrap();
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
assert_eq!(
stake_keyed_account.withdraw(
stake_lamports + 1,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers,
),
Err(InstructionError::InsufficientFunds)
);
let vote_pubkey = Pubkey::new_rand();
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!(
stake_keyed_account.delegate_stake(
&vote_keyed_account,
&clock,
&Config::default(),
&signers,
),
Ok(())
);
stake_account.lamports += 10;
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
assert_eq!(
stake_keyed_account.withdraw(
10,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers,
),
Ok(())
);
stake_account.lamports += 10;
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
assert_eq!(
stake_keyed_account.withdraw(
10 + 1,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers
),
Err(InstructionError::InsufficientFunds)
);
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &signers),
Ok(())
);
clock.epoch += 100;
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
assert_eq!(
stake_keyed_account.withdraw(
stake_lamports + 10 + 1,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers
),
Err(InstructionError::InsufficientFunds)
);
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
assert_eq!(
stake_keyed_account.withdraw(
stake_lamports + 10,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers
),
Ok(())
);
assert_eq!(stake_account.lamports, 0);
}
#[test]
fn test_withdraw_stake_before_warmup() {
let stake_pubkey = Pubkey::new_rand();
let total_lamports = 100;
let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space(
total_lamports,
&StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let clock = sysvar::clock::Clock::default();
let mut future = sysvar::clock::Clock::default();
future.epoch += 16;
let to = Pubkey::new_rand();
let mut to_account = Account::new(1, 0, &system_program::id());
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let vote_pubkey = Pubkey::new_rand();
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap();
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
stake_keyed_account.delegate_stake(
&vote_keyed_account,
&future,
&Config::default(),
&signers,
),
Ok(())
);
let stake_history = create_stake_history_from_stakes(
None,
0..future.epoch,
&[StakeState::stake_from(&stake_keyed_account.account).unwrap()],
);
assert_eq!(
stake_keyed_account.withdraw(
total_lamports - stake_lamports + 1,
&mut to_keyed_account,
&clock,
&stake_history,
&signers,
),
Err(InstructionError::InsufficientFunds)
);
}
#[test]
fn test_withdraw_stake_invalid_state() {
let stake_pubkey = Pubkey::new_rand();
let total_lamports = 100;
let mut stake_account = Account::new_data_with_space(
total_lamports,
&StakeState::RewardsPool,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let to = Pubkey::new_rand();
let mut to_account = Account::new(1, 0, &system_program::id());
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
stake_keyed_account.withdraw(
total_lamports,
&mut to_keyed_account,
&sysvar::clock::Clock::default(),
&StakeHistory::default(),
&signers,
),
Err(InstructionError::InvalidAccountData)
);
}
#[test]
fn test_withdraw_lockup() {
let stake_pubkey = Pubkey::new_rand();
let custodian = Pubkey::new_rand();
let total_lamports = 100;
let mut stake_account = Account::new_data_with_space(
total_lamports,
&StakeState::Initialized(
Authorized::auto(&stake_pubkey),
Lockup { slot: 1, custodian },
),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let to = Pubkey::new_rand();
let mut to_account = Account::new(1, 0, &system_program::id());
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let mut clock = sysvar::clock::Clock::default();
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
stake_keyed_account.withdraw(
total_lamports,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers,
),
Err(StakeError::LockupInForce.into())
);
let mut custodian_account = Account::new(1, 0, &system_program::id());
let mut custodian_keyed_account =
KeyedAccount::new(&custodian, false, &mut custodian_account);
assert_eq!(
stake_keyed_account.withdraw(
total_lamports,
&mut custodian_keyed_account,
&clock,
&StakeHistory::default(),
&signers,
),
Ok(())
);
stake_keyed_account.account.lamports = total_lamports;
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
clock.slot += 1;
assert_eq!(
stake_keyed_account.withdraw(
total_lamports,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers,
),
Ok(())
);
}
#[test]
fn test_stake_state_calculate_rewards() {
let mut vote_state = VoteState::default();
let mut stake = Stake::new_bootstrap(1, &Pubkey::default(), &vote_state);
assert_eq!(
None,
stake.calculate_rewards(1_000_000_000.0, &vote_state, None)
);
vote_state.increment_credits(0);
vote_state.increment_credits(0);
assert_eq!(
None,
stake.calculate_rewards(1_000_000_000_000.0, &vote_state, None)
);
vote_state.increment_credits(1);
assert_eq!(
Some((0, stake.stake * 2, 2)),
stake.calculate_rewards(1.0, &vote_state, None)
);
stake.credits_observed = 1;
assert_eq!(
Some((0, stake.stake * 1, 2)),
stake.calculate_rewards(1.0, &vote_state, None)
);
stake.credits_observed = 2;
assert_eq!(None, stake.calculate_rewards(1.0, &vote_state, None));
vote_state.increment_credits(2);
assert_eq!(
Some((0, stake.stake * 1, 3)),
stake.calculate_rewards(1.0, &vote_state, None)
);
stake.credits_observed = 0;
assert_eq!(
Some((0, stake.stake * 1 + stake.stake * 2, 3)),
stake.calculate_rewards(1.0, &vote_state, None)
);
vote_state.commission = 1;
assert_eq!(
None, stake.calculate_rewards(1.0, &vote_state, None)
);
vote_state.commission = std::u8::MAX - 1;
assert_eq!(
None, stake.calculate_rewards(1.0, &vote_state, None)
);
}
#[test]
fn test_stake_redeem_vote_credits() {
let clock = sysvar::clock::Clock::default();
let mut rewards = sysvar::rewards::Rewards::default();
rewards.validator_point_value = 100.0;
let rewards_pool_pubkey = Pubkey::new_rand();
let mut rewards_pool_account = Account::new_data(
std::u64::MAX,
&StakeState::RewardsPool,
&crate::rewards_pools::id(),
)
.unwrap();
let mut rewards_pool_keyed_account =
KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account);
let stake_pubkey = Pubkey::default();
let stake_lamports = 100;
let mut stake_account = Account::new_data_with_space(
stake_lamports,
&StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let vote_pubkey = Pubkey::new_rand();
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
assert_eq!(
stake_keyed_account.redeem_vote_credits(
&mut vote_keyed_account,
&mut rewards_pool_keyed_account,
&rewards,
&StakeHistory::default(),
),
Err(InstructionError::InvalidAccountData)
);
let signers = vec![stake_pubkey].into_iter().collect();
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
.is_ok());
let stake_history = create_stake_history_from_stakes(
Some(100),
0..10,
&[StakeState::stake_from(&stake_keyed_account.account).unwrap()],
);
assert_eq!(
stake_keyed_account.redeem_vote_credits(
&mut vote_keyed_account,
&mut rewards_pool_keyed_account,
&rewards,
&stake_history,
),
Err(StakeError::NoCreditsToRedeem.into())
);
assert_eq!(
stake_keyed_account.redeem_vote_credits(
&mut rewards_pool_keyed_account,
&mut vote_keyed_account,
&rewards,
&StakeHistory::default(),
),
Err(InstructionError::InvalidAccountData)
);
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_state = VoteState::from(&vote_account).unwrap();
vote_state.commission = std::u8::MAX / 4;
for _i in 0..100 {
vote_state.increment_credits(1);
}
vote_state.increment_credits(2);
vote_state.to(&mut vote_account).unwrap();
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
rewards_pool_keyed_account.account.lamports = 1;
assert_eq!(
stake_keyed_account.redeem_vote_credits(
&mut vote_keyed_account,
&mut rewards_pool_keyed_account,
&rewards,
&StakeHistory::default(),
),
Err(InstructionError::UnbalancedInstruction)
);
rewards_pool_keyed_account.account.lamports = std::u64::MAX;
let stake_account_balance = stake_keyed_account.account.lamports;
let vote_account_balance = vote_keyed_account.account.lamports;
assert_eq!(
stake_keyed_account.redeem_vote_credits(
&mut vote_keyed_account,
&mut rewards_pool_keyed_account,
&rewards,
&stake_history,
),
Ok(())
);
let staker_rewards = stake_keyed_account.account.lamports - stake_account_balance;
let voter_commission = vote_keyed_account.account.lamports - vote_account_balance;
assert!(voter_commission > 0);
assert!(staker_rewards > 0);
assert!(
staker_rewards / 3 > voter_commission,
"rewards should be split ~3:1"
);
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
assert_eq!(stake.stake, stake_keyed_account.account.lamports);
let wrong_vote_pubkey = Pubkey::new_rand();
let mut wrong_vote_keyed_account =
KeyedAccount::new(&wrong_vote_pubkey, false, &mut vote_account);
assert_eq!(
stake_keyed_account.redeem_vote_credits(
&mut wrong_vote_keyed_account,
&mut rewards_pool_keyed_account,
&rewards,
&stake_history,
),
Err(InstructionError::InvalidArgument)
);
}
#[test]
fn test_authorize_lockup() {
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space(
stake_lamports,
&StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let to = Pubkey::new_rand();
let mut to_account = Account::new(1, 0, &system_program::id());
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
let clock = sysvar::clock::Clock::default();
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let stake_pubkey0 = Pubkey::new_rand();
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
stake_keyed_account.authorize(&stake_pubkey0, StakeAuthorize::Staker, &signers),
Ok(())
);
assert_eq!(
stake_keyed_account.authorize(&stake_pubkey0, StakeAuthorize::Withdrawer, &signers),
Ok(())
);
if let StakeState::Initialized(authorized, _lockup) =
StakeState::from(&stake_keyed_account.account).unwrap()
{
assert_eq!(authorized.staker, stake_pubkey0);
assert_eq!(authorized.withdrawer, stake_pubkey0);
} else {
assert!(false);
}
let stake_pubkey1 = Pubkey::new_rand();
assert_eq!(
stake_keyed_account.authorize(&stake_pubkey1, StakeAuthorize::Staker, &signers),
Err(InstructionError::MissingRequiredSignature)
);
let signers0 = vec![stake_pubkey0].into_iter().collect();
let stake_pubkey2 = Pubkey::new_rand();
assert_eq!(
stake_keyed_account.authorize(&stake_pubkey2, StakeAuthorize::Staker, &signers0),
Ok(())
);
if let StakeState::Initialized(authorized, _lockup) =
StakeState::from(&stake_keyed_account.account).unwrap()
{
assert_eq!(authorized.staker, stake_pubkey2);
}
assert_eq!(
stake_keyed_account.authorize(&stake_pubkey2, StakeAuthorize::Withdrawer, &signers0,),
Ok(())
);
if let StakeState::Initialized(authorized, _lockup) =
StakeState::from(&stake_keyed_account.account).unwrap()
{
assert_eq!(authorized.staker, stake_pubkey2);
}
let signers2 = vec![stake_pubkey2].into_iter().collect();
assert_eq!(
stake_keyed_account.withdraw(
stake_lamports,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers, ),
Err(InstructionError::MissingRequiredSignature)
);
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
assert_eq!(
stake_keyed_account.withdraw(
stake_lamports,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&signers2,
),
Ok(())
);
}
#[test]
fn test_authorize_delegated_stake() {
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space(
stake_lamports,
&StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let clock = sysvar::clock::Clock::default();
let vote_pubkey = Pubkey::new_rand();
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let signers = vec![stake_pubkey].into_iter().collect();
stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
.unwrap();
let new_staker_pubkey = Pubkey::new_rand();
assert_eq!(
stake_keyed_account.authorize(&new_staker_pubkey, StakeAuthorize::Staker, &signers),
Ok(())
);
let authorized = StakeState::authorized_from(&stake_keyed_account.account).unwrap();
assert_eq!(authorized.staker, new_staker_pubkey);
let other_pubkey = Pubkey::new_rand();
let other_signers = vec![other_pubkey].into_iter().collect();
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
let new_voter_pubkey = Pubkey::new_rand();
let vote_state = VoteState::default();
let mut new_vote_account =
vote_state::create_account(&new_voter_pubkey, &Pubkey::new_rand(), 0, 100);
let mut new_vote_keyed_account =
KeyedAccount::new(&new_voter_pubkey, false, &mut new_vote_account);
new_vote_keyed_account.set_state(&vote_state).unwrap();
assert_eq!(
stake_keyed_account.delegate_stake(
&new_vote_keyed_account,
&clock,
&Config::default(),
&other_signers,
),
Err(InstructionError::MissingRequiredSignature)
);
let new_signers = vec![new_staker_pubkey].into_iter().collect();
assert_eq!(
stake_keyed_account.delegate_stake(
&new_vote_keyed_account,
&clock,
&Config::default(),
&new_signers
),
Ok(())
);
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
assert_eq!(stake.voter_pubkey(0), &new_voter_pubkey);
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &new_signers),
Ok(())
);
}
}