use crate::id;
use serde_derive::{Deserialize, Serialize};
use solana_sdk::account::{Account, KeyedAccount};
use solana_sdk::account_utils::State;
use solana_sdk::instruction::InstructionError;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::sysvar;
use solana_sdk::timing::Epoch;
use solana_vote_api::vote_state::VoteState;
use std::cmp;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub enum StakeState {
Uninitialized,
Stake(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 stake(&self) -> Option<Stake> {
match self {
StakeState::Stake(stake) => Some(stake.clone()),
_ => None,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Stake {
pub voter_pubkey: Pubkey,
pub credits_observed: u64,
pub stake: u64, pub activated: Epoch, pub deactivated: Epoch, }
pub const STAKE_WARMUP_EPOCHS: Epoch = 3;
impl Default for Stake {
fn default() -> Self {
Stake {
voter_pubkey: Pubkey::default(),
credits_observed: 0,
stake: 0,
activated: 0,
deactivated: std::u64::MAX,
}
}
}
impl Stake {
pub fn stake(&self, epoch: Epoch) -> u64 {
if epoch < self.activated || epoch >= self.deactivated {
return 0;
}
if epoch - self.activated < STAKE_WARMUP_EPOCHS {
(self.stake / STAKE_WARMUP_EPOCHS) * (epoch - self.activated + 1)
} else if self.deactivated - epoch < STAKE_WARMUP_EPOCHS {
(self.stake / STAKE_WARMUP_EPOCHS) * (self.deactivated - epoch)
} else {
self.stake
}
}
pub fn calculate_rewards(
&self,
point_value: f64,
vote_state: &VoteState,
) -> 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) * epoch_credits) as f64 * point_value;
credits_observed = std::cmp::max(credits_observed, *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 delegate(&mut self, stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState, epoch: u64) {
assert!(std::u64::MAX - epoch >= (STAKE_WARMUP_EPOCHS * 2));
self.voter_pubkey = *voter_pubkey;
self.credits_observed = vote_state.credits();
self.activated = epoch;
self.stake = stake;
}
fn deactivate(&mut self, epoch: u64) {
self.deactivated = std::cmp::max(
epoch + STAKE_WARMUP_EPOCHS,
self.activated + 2 * STAKE_WARMUP_EPOCHS - 1,
);
}
}
pub trait StakeAccount {
fn delegate_stake(
&mut self,
vote_account: &KeyedAccount,
stake: u64,
clock: &sysvar::clock::Clock,
) -> Result<(), InstructionError>;
fn deactivate_stake(&mut self, clock: &sysvar::clock::Clock) -> Result<(), InstructionError>;
fn redeem_vote_credits(
&mut self,
vote_account: &mut KeyedAccount,
rewards_account: &mut KeyedAccount,
rewards: &sysvar::rewards::Rewards,
) -> Result<(), InstructionError>;
fn withdraw(
&mut self,
lamports: u64,
to: &mut KeyedAccount,
clock: &sysvar::clock::Clock,
) -> Result<(), InstructionError>;
}
impl<'a> StakeAccount for KeyedAccount<'a> {
fn delegate_stake(
&mut self,
vote_account: &KeyedAccount,
new_stake: u64,
clock: &sysvar::clock::Clock,
) -> Result<(), InstructionError> {
if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
if new_stake > self.account.lamports {
return Err(InstructionError::InsufficientFunds);
}
if let StakeState::Uninitialized = self.state()? {
let mut stake = Stake::default();
stake.delegate(
new_stake,
vote_account.unsigned_key(),
&vote_account.state()?,
clock.epoch,
);
self.set_state(&StakeState::Stake(stake))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn deactivate_stake(&mut self, clock: &sysvar::clock::Clock) -> Result<(), InstructionError> {
if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
if let StakeState::Stake(mut stake) = self.state()? {
stake.deactivate(clock.epoch);
self.set_state(&StakeState::Stake(stake))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn redeem_vote_credits(
&mut self,
vote_account: &mut KeyedAccount,
rewards_account: &mut KeyedAccount,
rewards: &sysvar::rewards::Rewards,
) -> Result<(), InstructionError> {
if let (StakeState::Stake(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((stakers_reward, voters_reward, credits_observed)) =
stake.calculate_rewards(rewards.validator_point_value, &vote_state)
{
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;
self.set_state(&StakeState::Stake(stake))
} else {
Err(InstructionError::CustomError(1))
}
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn withdraw(
&mut self,
lamports: u64,
to: &mut KeyedAccount,
clock: &sysvar::clock::Clock,
) -> Result<(), InstructionError> {
if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
match self.state()? {
StakeState::Stake(mut stake) => {
if stake.deactivated == std::u64::MAX {
return Err(InstructionError::InsufficientFunds);
}
let staked = if stake.stake(clock.epoch) == 0 {
0
} else {
stake.stake
};
if lamports > self.account.lamports.saturating_sub(staked) {
return Err(InstructionError::InsufficientFunds);
}
self.account.lamports -= lamports;
to.account.lamports += lamports;
stake.stake = cmp::min(stake.stake, self.account.lamports);
self.set_state(&StakeState::Stake(stake))
}
StakeState::Uninitialized => {
if lamports > self.account.lamports {
return Err(InstructionError::InsufficientFunds);
}
self.account.lamports -= lamports;
to.account.lamports += lamports;
Ok(())
}
_ => Err(InstructionError::InvalidAccountData),
}
}
}
pub fn create_stake_account(
voter_pubkey: &Pubkey,
vote_state: &VoteState,
lamports: u64,
) -> Account {
let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id());
stake_account
.set_state(&StakeState::Stake(Stake {
voter_pubkey: *voter_pubkey,
credits_observed: vote_state.credits(),
stake: lamports,
activated: 0,
deactivated: std::u64::MAX,
}))
.expect("set_state");
stake_account
}
pub fn create_rewards_pool() -> Account {
Account::new_data(std::u64::MAX, &StakeState::RewardsPool, &crate::id()).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::id;
use solana_sdk::account::Account;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_program;
use solana_vote_api::vote_state;
#[test]
fn test_stake_delegate_stake() {
let clock = sysvar::clock::Clock {
epoch: 1,
..sysvar::clock::Clock::default()
};
let vote_keypair = Keypair::new();
let mut vote_state = VoteState::default();
for i in 0..1000 {
vote_state.process_slot_vote_unchecked(i);
}
let vote_pubkey = vote_keypair.pubkey();
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::default();
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 stake_state: StakeState = stake_keyed_account.state().unwrap();
assert_eq!(stake_state, StakeState::default());
}
assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, 0, &clock),
Err(InstructionError::MissingRequiredSignature)
);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, stake_lamports, &clock)
.is_ok());
let stake_state: StakeState = stake_keyed_account.state().unwrap();
assert_eq!(
stake_state,
StakeState::Stake(Stake {
voter_pubkey: vote_keypair.pubkey(),
credits_observed: vote_state.credits(),
stake: stake_lamports,
activated: clock.epoch,
deactivated: std::u64::MAX,
})
);
assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports, &clock),
Err(InstructionError::InvalidAccountData)
);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports + 1, &clock),
Err(InstructionError::InsufficientFunds)
);
let stake_state = StakeState::RewardsPool;
stake_keyed_account.set_state(&stake_state).unwrap();
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, 0, &clock)
.is_err());
}
#[test]
fn test_stake_stake() {
let mut stake = Stake::default();
assert_eq!(stake.stake(0), 0);
let staked = STAKE_WARMUP_EPOCHS;
stake.delegate(staked, &Pubkey::default(), &VoteState::default(), 1);
for i in 0..STAKE_WARMUP_EPOCHS {
assert_eq!(stake.stake(i), i);
}
assert_eq!(stake.stake(STAKE_WARMUP_EPOCHS * 42), staked);
stake.deactivate(STAKE_WARMUP_EPOCHS);
for i in STAKE_WARMUP_EPOCHS..STAKE_WARMUP_EPOCHS * 2 {
assert_eq!(
stake.stake(i),
staked - (staked / STAKE_WARMUP_EPOCHS) * (i - STAKE_WARMUP_EPOCHS)
);
}
assert_eq!(stake.stake(STAKE_WARMUP_EPOCHS * 42), 0);
}
#[test]
fn test_deactivate_stake() {
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 clock = sysvar::clock::Clock {
epoch: 1,
..sysvar::clock::Clock::default()
};
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
assert_eq!(
stake_keyed_account.deactivate_stake(&clock),
Err(InstructionError::MissingRequiredSignature)
);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!(
stake_keyed_account.deactivate_stake(&clock),
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, stake_lamports, &clock),
Ok(())
);
assert_eq!(stake_keyed_account.deactivate_stake(&clock), Ok(()));
}
#[test]
fn test_withdraw_stake() {
let stake_pubkey = Pubkey::new_rand();
let mut total_lamports = 100;
let stake_lamports = 42;
let mut stake_account =
Account::new(total_lamports, std::mem::size_of::<StakeState>(), &id());
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(total_lamports, &mut to_keyed_account, &clock),
Err(InstructionError::MissingRequiredSignature)
);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!(
stake_keyed_account.withdraw(total_lamports + 1, &mut to_keyed_account, &clock),
Err(InstructionError::InsufficientFunds)
);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!(
stake_keyed_account.withdraw(5, &mut to_keyed_account, &clock),
Ok(())
);
total_lamports -= 5;
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, stake_lamports, &clock),
Ok(())
);
assert_eq!(stake_keyed_account.deactivate_stake(&clock), Ok(()));
clock.epoch += STAKE_WARMUP_EPOCHS;
assert_eq!(
stake_keyed_account.withdraw(
total_lamports - stake_lamports + 1,
&mut to_keyed_account,
&clock
),
Err(InstructionError::InsufficientFunds)
);
assert_eq!(
stake_keyed_account.withdraw(
total_lamports - stake_lamports,
&mut to_keyed_account,
&clock
),
Ok(())
);
}
#[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(total_lamports, std::mem::size_of::<StakeState>(), &id());
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();
assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports, &future),
Ok(())
);
assert_eq!(
stake_keyed_account.withdraw(
total_lamports - stake_lamports + 1,
&mut to_keyed_account,
&clock
),
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(total_lamports, std::mem::size_of::<StakeState>(), &id());
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 stake_state = StakeState::RewardsPool;
stake_keyed_account.set_state(&stake_state).unwrap();
assert_eq!(
stake_keyed_account.withdraw(total_lamports, &mut to_keyed_account, &clock),
Err(InstructionError::InvalidAccountData)
);
}
#[test]
fn test_stake_state_calculate_rewards() {
let mut vote_state = VoteState::default();
let mut stake = Stake::default();
stake.stake = 1;
assert_eq!(None, stake.calculate_rewards(1_000_000_000.0, &vote_state));
vote_state.increment_credits(0);
vote_state.increment_credits(0);
assert_eq!(None, stake.calculate_rewards(1_000_000_000.0, &vote_state));
vote_state.increment_credits(1);
assert_eq!(None, stake.calculate_rewards(1.0, &vote_state));
stake.stake = STAKE_WARMUP_EPOCHS;
assert_eq!(
Some((0, 1 * 2, 2)),
stake.calculate_rewards(1.0, &vote_state)
);
stake.stake = STAKE_WARMUP_EPOCHS;
stake.credits_observed = 1;
assert_eq!(
Some((0, 1 * 1, 2)),
stake.calculate_rewards(1.0, &vote_state)
);
stake.stake = STAKE_WARMUP_EPOCHS;
stake.credits_observed = 2;
assert_eq!(None, stake.calculate_rewards(1.0, &vote_state));
vote_state.increment_credits(2);
assert_eq!(
Some((0, 2 * 1, 3)),
stake.calculate_rewards(1.0, &vote_state)
);
stake.credits_observed = 0;
assert_eq!(
Some((0, 2 * 1 + 1 * 2, 3)),
stake.calculate_rewards(1.0, &vote_state)
);
vote_state.commission = 1;
assert_eq!(
None, stake.calculate_rewards(1.0, &vote_state)
);
vote_state.commission = std::u8::MAX - 1;
assert_eq!(
None, stake.calculate_rewards(1.0, &vote_state)
);
}
#[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 = create_rewards_pool();
let mut rewards_pool_keyed_account =
KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account);
let pubkey = Pubkey::default();
let stake_lamports = 100;
let mut stake_account =
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
let mut stake_keyed_account = KeyedAccount::new(&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
),
Err(InstructionError::InvalidAccountData)
);
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, stake_lamports, &clock)
.is_ok());
assert_eq!(
stake_keyed_account.redeem_vote_credits(
&mut vote_keyed_account,
&mut rewards_pool_keyed_account,
&rewards
),
Err(InstructionError::CustomError(1))
);
assert_eq!(
stake_keyed_account.redeem_vote_credits(
&mut rewards_pool_keyed_account,
&mut vote_keyed_account,
&rewards
),
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();
for _i in 0..100 {
vote_state.increment_credits(0);
}
vote_state.increment_credits(1);
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
),
Err(InstructionError::UnbalancedInstruction)
);
rewards_pool_keyed_account.account.lamports = std::u64::MAX;
assert_eq!(
stake_keyed_account.redeem_vote_credits(
&mut vote_keyed_account,
&mut rewards_pool_keyed_account,
&rewards
),
Ok(())
);
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
),
Err(InstructionError::InvalidArgument)
);
}
}