add stake warmup and cool down (#4711)
This commit is contained in:
parent
9cafd1f85e
commit
0ff9c4cd8e
@ -6,16 +6,11 @@ use serde_derive::{Deserialize, Serialize};
|
|||||||
use solana_sdk::account::KeyedAccount;
|
use solana_sdk::account::KeyedAccount;
|
||||||
use solana_sdk::instruction::{AccountMeta, Instruction, InstructionError};
|
use solana_sdk::instruction::{AccountMeta, Instruction, InstructionError};
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::syscall;
|
||||||
use solana_sdk::system_instruction;
|
use solana_sdk::system_instruction;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum StakeInstruction {
|
pub enum StakeInstruction {
|
||||||
/// Initialize the stake account as a Stake account.
|
|
||||||
///
|
|
||||||
/// Expects 1 Accounts:
|
|
||||||
/// 0 - StakeAccount to be initialized
|
|
||||||
InitializeStake,
|
|
||||||
|
|
||||||
// Initialize the stake account as a MiningPool account
|
// Initialize the stake account as a MiningPool account
|
||||||
///
|
///
|
||||||
/// Expects 1 Accounts:
|
/// Expects 1 Accounts:
|
||||||
@ -25,7 +20,7 @@ pub enum StakeInstruction {
|
|||||||
/// `Delegate` a stake to a particular node
|
/// `Delegate` a stake to a particular node
|
||||||
///
|
///
|
||||||
/// Expects 2 Accounts:
|
/// Expects 2 Accounts:
|
||||||
/// 0 - Delegate StakeAccount to be updated <= must have this signature
|
/// 0 - Uninitialized StakeAccount to be delegated <= must have this signature
|
||||||
/// 1 - VoteAccount to which this Stake will be delegated
|
/// 1 - VoteAccount to which this Stake will be delegated
|
||||||
///
|
///
|
||||||
/// The u64 is the portion of the Stake account balance to be activated,
|
/// The u64 is the portion of the Stake account balance to be activated,
|
||||||
@ -48,20 +43,13 @@ pub fn create_stake_account(
|
|||||||
staker_pubkey: &Pubkey,
|
staker_pubkey: &Pubkey,
|
||||||
lamports: u64,
|
lamports: u64,
|
||||||
) -> Vec<Instruction> {
|
) -> Vec<Instruction> {
|
||||||
vec![
|
vec![system_instruction::create_account(
|
||||||
system_instruction::create_account(
|
from_pubkey,
|
||||||
from_pubkey,
|
staker_pubkey,
|
||||||
staker_pubkey,
|
lamports,
|
||||||
lamports,
|
std::mem::size_of::<StakeState>() as u64,
|
||||||
std::mem::size_of::<StakeState>() as u64,
|
&id(),
|
||||||
&id(),
|
)]
|
||||||
),
|
|
||||||
Instruction::new(
|
|
||||||
id(),
|
|
||||||
&StakeInstruction::InitializeStake,
|
|
||||||
vec![AccountMeta::new(*staker_pubkey, false)],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_stake_account_and_delegate_stake(
|
pub fn create_stake_account_and_delegate_stake(
|
||||||
@ -113,10 +101,16 @@ pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey, stake: u64) -
|
|||||||
let account_metas = vec![
|
let account_metas = vec![
|
||||||
AccountMeta::new(*stake_pubkey, true),
|
AccountMeta::new(*stake_pubkey, true),
|
||||||
AccountMeta::new(*vote_pubkey, false),
|
AccountMeta::new(*vote_pubkey, false),
|
||||||
|
AccountMeta::new(syscall::current::id(), false),
|
||||||
];
|
];
|
||||||
Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas)
|
Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn current(current_account: &KeyedAccount) -> Result<syscall::current::Current, InstructionError> {
|
||||||
|
syscall::current::Current::from(current_account.account)
|
||||||
|
.ok_or(InstructionError::InvalidArgument)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_instruction(
|
pub fn process_instruction(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
keyed_accounts: &mut [KeyedAccount],
|
keyed_accounts: &mut [KeyedAccount],
|
||||||
@ -142,18 +136,13 @@ pub fn process_instruction(
|
|||||||
}
|
}
|
||||||
me.initialize_mining_pool()
|
me.initialize_mining_pool()
|
||||||
}
|
}
|
||||||
StakeInstruction::InitializeStake => {
|
|
||||||
if !rest.is_empty() {
|
|
||||||
Err(InstructionError::InvalidInstructionData)?;
|
|
||||||
}
|
|
||||||
me.initialize_stake()
|
|
||||||
}
|
|
||||||
StakeInstruction::DelegateStake(stake) => {
|
StakeInstruction::DelegateStake(stake) => {
|
||||||
if rest.len() != 1 {
|
if rest.len() != 2 {
|
||||||
Err(InstructionError::InvalidInstructionData)?;
|
Err(InstructionError::InvalidInstructionData)?;
|
||||||
}
|
}
|
||||||
let vote = &rest[0];
|
let vote = &rest[0];
|
||||||
me.delegate_stake(vote, stake)
|
|
||||||
|
me.delegate_stake(vote, stake, ¤t(&rest[1])?)
|
||||||
}
|
}
|
||||||
StakeInstruction::RedeemVoteCredits => {
|
StakeInstruction::RedeemVoteCredits => {
|
||||||
if rest.len() != 2 {
|
if rest.len() != 2 {
|
||||||
@ -175,10 +164,18 @@ mod tests {
|
|||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
|
|
||||||
fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> {
|
fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> {
|
||||||
let mut accounts = vec![];
|
let mut accounts: Vec<_> = instruction
|
||||||
for _ in 0..instruction.accounts.len() {
|
.accounts
|
||||||
accounts.push(Account::default());
|
.iter()
|
||||||
}
|
.map(|meta| {
|
||||||
|
if syscall::current::check_id(&meta.pubkey) {
|
||||||
|
syscall::current::create_account(1, 0, 0, 0)
|
||||||
|
} else {
|
||||||
|
Account::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut keyed_accounts: Vec<_> = instruction
|
let mut keyed_accounts: Vec<_> = instruction
|
||||||
.accounts
|
.accounts
|
||||||
@ -250,13 +247,18 @@ mod tests {
|
|||||||
Err(InstructionError::InvalidInstructionData),
|
Err(InstructionError::InvalidInstructionData),
|
||||||
);
|
);
|
||||||
|
|
||||||
// gets the check in delegate_stake
|
// gets the check non-deserialize-able account in delegate_stake
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
super::process_instruction(
|
super::process_instruction(
|
||||||
&Pubkey::default(),
|
&Pubkey::default(),
|
||||||
&mut [
|
&mut [
|
||||||
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default()),
|
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default()),
|
||||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&syscall::current::id(),
|
||||||
|
false,
|
||||||
|
&mut syscall::current::create_account(1, 0, 0, 0)
|
||||||
|
),
|
||||||
],
|
],
|
||||||
&serialize(&StakeInstruction::DelegateStake(0)).unwrap(),
|
&serialize(&StakeInstruction::DelegateStake(0)).unwrap(),
|
||||||
),
|
),
|
||||||
|
@ -9,16 +9,13 @@ use solana_sdk::account::{Account, KeyedAccount};
|
|||||||
use solana_sdk::account_utils::State;
|
use solana_sdk::account_utils::State;
|
||||||
use solana_sdk::instruction::InstructionError;
|
use solana_sdk::instruction::InstructionError;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::syscall;
|
||||||
use solana_vote_api::vote_state::VoteState;
|
use solana_vote_api::vote_state::VoteState;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub enum StakeState {
|
pub enum StakeState {
|
||||||
Uninitialized,
|
Uninitialized,
|
||||||
Stake {
|
Stake(Stake),
|
||||||
voter_pubkey: Pubkey,
|
|
||||||
credits_observed: u64,
|
|
||||||
stake: u64,
|
|
||||||
},
|
|
||||||
MiningPool {
|
MiningPool {
|
||||||
/// epoch for which this Pool will redeem rewards
|
/// epoch for which this Pool will redeem rewards
|
||||||
epoch: u64,
|
epoch: u64,
|
||||||
@ -62,19 +59,26 @@ impl StakeState {
|
|||||||
Self::from(account).and_then(|state: Self| state.voter_pubkey_and_stake())
|
Self::from(account).and_then(|state: Self| state.voter_pubkey_and_stake())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stake_from(account: &Account) -> Option<(Stake)> {
|
||||||
|
Self::from(account).and_then(|state: Self| state.stake())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn voter_pubkey(&self) -> Option<Pubkey> {
|
pub fn voter_pubkey(&self) -> Option<Pubkey> {
|
||||||
match self {
|
match self {
|
||||||
StakeState::Stake { voter_pubkey, .. } => Some(*voter_pubkey),
|
StakeState::Stake(stake) => Some(stake.voter_pubkey),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn stake(&self) -> Option<Stake> {
|
||||||
|
match self {
|
||||||
|
StakeState::Stake(stake) => Some(stake.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn voter_pubkey_and_stake(&self) -> Option<(Pubkey, u64)> {
|
pub fn voter_pubkey_and_stake(&self) -> Option<(Pubkey, u64)> {
|
||||||
match self {
|
match self {
|
||||||
StakeState::Stake {
|
StakeState::Stake(stake) => Some((stake.voter_pubkey, stake.stake)),
|
||||||
voter_pubkey,
|
|
||||||
stake,
|
|
||||||
..
|
|
||||||
} => Some((*voter_pubkey, *stake)),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,13 +113,76 @@ impl StakeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
|
pub struct Stake {
|
||||||
|
pub voter_pubkey: Pubkey,
|
||||||
|
pub credits_observed: u64,
|
||||||
|
pub stake: u64, // activated stake
|
||||||
|
pub epoch: u64, // epoch the stake was activated
|
||||||
|
pub prev_stake: u64, // for warmup, cooldown
|
||||||
|
}
|
||||||
|
const STAKE_WARMUP_COOLDOWN_EPOCHS: u64 = 3;
|
||||||
|
|
||||||
|
impl Stake {
|
||||||
|
pub fn stake(&mut self, epoch: u64) -> u64 {
|
||||||
|
// prev_stake for stuff in the past
|
||||||
|
if epoch < self.epoch {
|
||||||
|
return self.prev_stake;
|
||||||
|
}
|
||||||
|
if epoch - self.epoch >= STAKE_WARMUP_COOLDOWN_EPOCHS {
|
||||||
|
return self.stake;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.stake != 0 {
|
||||||
|
// warmup
|
||||||
|
// 1/3rd, then 2/3rds...
|
||||||
|
(self.stake / STAKE_WARMUP_COOLDOWN_EPOCHS) * (epoch - self.epoch + 1)
|
||||||
|
} else if self.prev_stake != 0 {
|
||||||
|
// cool down
|
||||||
|
// 3/3rds, then 2/3rds...
|
||||||
|
self.prev_stake
|
||||||
|
- ((self.prev_stake / STAKE_WARMUP_COOLDOWN_EPOCHS) * (epoch - self.epoch))
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delegate(
|
||||||
|
&mut self,
|
||||||
|
stake: u64,
|
||||||
|
voter_pubkey: &Pubkey,
|
||||||
|
vote_state: &VoteState,
|
||||||
|
epoch: u64, // current: &syscall::current::Current
|
||||||
|
) {
|
||||||
|
// resets the current stake's credits
|
||||||
|
self.voter_pubkey = *voter_pubkey;
|
||||||
|
self.credits_observed = vote_state.credits();
|
||||||
|
|
||||||
|
// when this stake was activated
|
||||||
|
self.epoch = epoch;
|
||||||
|
self.stake = stake;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deactivate(&mut self, epoch: u64) {
|
||||||
|
self.voter_pubkey = Pubkey::default();
|
||||||
|
self.credits_observed = std::u64::MAX;
|
||||||
|
self.prev_stake = self.stake(epoch);
|
||||||
|
self.stake = 0;
|
||||||
|
self.epoch = epoch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait StakeAccount {
|
pub trait StakeAccount {
|
||||||
fn initialize_mining_pool(&mut self) -> Result<(), InstructionError>;
|
fn initialize_mining_pool(&mut self) -> Result<(), InstructionError>;
|
||||||
fn initialize_stake(&mut self) -> Result<(), InstructionError>;
|
|
||||||
fn delegate_stake(
|
fn delegate_stake(
|
||||||
&mut self,
|
&mut self,
|
||||||
vote_account: &KeyedAccount,
|
vote_account: &KeyedAccount,
|
||||||
stake: u64,
|
stake: u64,
|
||||||
|
current: &syscall::current::Current,
|
||||||
|
) -> Result<(), InstructionError>;
|
||||||
|
fn deactivate_stake(
|
||||||
|
&mut self,
|
||||||
|
current: &syscall::current::Current,
|
||||||
) -> Result<(), InstructionError>;
|
) -> Result<(), InstructionError>;
|
||||||
fn redeem_vote_credits(
|
fn redeem_vote_credits(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -135,36 +202,49 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||||||
Err(InstructionError::InvalidAccountData)
|
Err(InstructionError::InvalidAccountData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn initialize_stake(&mut self) -> Result<(), InstructionError> {
|
|
||||||
if let StakeState::Uninitialized = self.state()? {
|
fn deactivate_stake(
|
||||||
self.set_state(&StakeState::Stake {
|
|
||||||
voter_pubkey: Pubkey::default(),
|
|
||||||
credits_observed: 0,
|
|
||||||
stake: 0,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(InstructionError::InvalidAccountData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn delegate_stake(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
vote_account: &KeyedAccount,
|
current: &syscall::current::Current,
|
||||||
stake: u64,
|
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
if self.signer_key().is_none() {
|
if self.signer_key().is_none() {
|
||||||
return Err(InstructionError::MissingRequiredSignature);
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
}
|
}
|
||||||
if stake > self.account.lamports {
|
|
||||||
|
if let StakeState::Stake(mut stake) = self.state()? {
|
||||||
|
stake.deactivate(current.epoch);
|
||||||
|
|
||||||
|
self.set_state(&StakeState::Stake(stake))
|
||||||
|
} else {
|
||||||
|
Err(InstructionError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delegate_stake(
|
||||||
|
&mut self,
|
||||||
|
vote_account: &KeyedAccount,
|
||||||
|
new_stake: u64,
|
||||||
|
current: &syscall::current::Current,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
if self.signer_key().is_none() {
|
||||||
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_stake > self.account.lamports {
|
||||||
return Err(InstructionError::InsufficientFunds);
|
return Err(InstructionError::InsufficientFunds);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let StakeState::Stake { .. } = self.state()? {
|
if let StakeState::Uninitialized = self.state()? {
|
||||||
let vote_state: VoteState = vote_account.state()?;
|
let mut stake = Stake::default();
|
||||||
self.set_state(&StakeState::Stake {
|
|
||||||
voter_pubkey: *vote_account.unsigned_key(),
|
stake.delegate(
|
||||||
credits_observed: vote_state.credits(),
|
new_stake,
|
||||||
stake,
|
vote_account.unsigned_key(),
|
||||||
})
|
&vote_account.state()?,
|
||||||
|
current.epoch,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.set_state(&StakeState::Stake(stake))
|
||||||
} else {
|
} else {
|
||||||
Err(InstructionError::InvalidAccountData)
|
Err(InstructionError::InvalidAccountData)
|
||||||
}
|
}
|
||||||
@ -175,27 +255,21 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||||||
stake_account: &mut KeyedAccount,
|
stake_account: &mut KeyedAccount,
|
||||||
vote_account: &mut KeyedAccount,
|
vote_account: &mut KeyedAccount,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
if let (
|
if let (StakeState::MiningPool { .. }, StakeState::Stake(mut stake)) =
|
||||||
StakeState::MiningPool { .. },
|
(self.state()?, stake_account.state()?)
|
||||||
StakeState::Stake {
|
|
||||||
voter_pubkey,
|
|
||||||
credits_observed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) = (self.state()?, stake_account.state()?)
|
|
||||||
{
|
{
|
||||||
let vote_state: VoteState = vote_account.state()?;
|
let vote_state: VoteState = vote_account.state()?;
|
||||||
|
|
||||||
if voter_pubkey != *vote_account.unsigned_key() {
|
if stake.voter_pubkey != *vote_account.unsigned_key() {
|
||||||
return Err(InstructionError::InvalidArgument);
|
return Err(InstructionError::InvalidArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
if credits_observed > vote_state.credits() {
|
if stake.credits_observed > vote_state.credits() {
|
||||||
return Err(InstructionError::InvalidAccountData);
|
return Err(InstructionError::InvalidAccountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((stakers_reward, voters_reward)) = StakeState::calculate_rewards(
|
if let Some((stakers_reward, voters_reward)) = StakeState::calculate_rewards(
|
||||||
credits_observed,
|
stake.credits_observed,
|
||||||
stake_account.account.lamports,
|
stake_account.account.lamports,
|
||||||
&vote_state,
|
&vote_state,
|
||||||
) {
|
) {
|
||||||
@ -206,11 +280,9 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||||||
stake_account.account.lamports += stakers_reward;
|
stake_account.account.lamports += stakers_reward;
|
||||||
vote_account.account.lamports += voters_reward;
|
vote_account.account.lamports += voters_reward;
|
||||||
|
|
||||||
stake_account.set_state(&StakeState::Stake {
|
stake.credits_observed = vote_state.credits();
|
||||||
voter_pubkey,
|
|
||||||
credits_observed: vote_state.credits(),
|
stake_account.set_state(&StakeState::Stake(stake))
|
||||||
stake: 0,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
// not worth collecting
|
// not worth collecting
|
||||||
Err(InstructionError::CustomError(1))
|
Err(InstructionError::CustomError(1))
|
||||||
@ -230,11 +302,13 @@ pub fn create_stake_account(
|
|||||||
let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id());
|
let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id());
|
||||||
|
|
||||||
stake_account
|
stake_account
|
||||||
.set_state(&StakeState::Stake {
|
.set_state(&StakeState::Stake(Stake {
|
||||||
voter_pubkey: *voter_pubkey,
|
voter_pubkey: *voter_pubkey,
|
||||||
credits_observed: vote_state.credits(),
|
credits_observed: vote_state.credits(),
|
||||||
stake: lamports,
|
stake: lamports,
|
||||||
})
|
epoch: 0,
|
||||||
|
prev_stake: lamports,
|
||||||
|
}))
|
||||||
.expect("set_state");
|
.expect("set_state");
|
||||||
|
|
||||||
stake_account
|
stake_account
|
||||||
@ -262,6 +336,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_delegate_stake() {
|
fn test_stake_delegate_stake() {
|
||||||
|
let current = syscall::current::Current::default();
|
||||||
|
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
for i in 0..1000 {
|
||||||
@ -279,6 +355,7 @@ mod tests {
|
|||||||
let mut stake_account =
|
let mut stake_account =
|
||||||
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
|
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
|
||||||
|
|
||||||
|
// unsigned keyed account
|
||||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -286,50 +363,78 @@ mod tests {
|
|||||||
assert_eq!(stake_state, StakeState::default());
|
assert_eq!(stake_state, StakeState::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
stake_keyed_account.initialize_stake().unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.delegate_stake(&vote_keyed_account, 0),
|
stake_keyed_account.delegate_stake(&vote_keyed_account, 0, ¤t),
|
||||||
Err(InstructionError::MissingRequiredSignature)
|
Err(InstructionError::MissingRequiredSignature)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// signed keyed account
|
||||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
||||||
assert!(stake_keyed_account
|
assert!(stake_keyed_account
|
||||||
.delegate_stake(&vote_keyed_account, stake_lamports)
|
.delegate_stake(&vote_keyed_account, stake_lamports, ¤t)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
|
// verify that delegate_stake() looks right, compare against hand-rolled
|
||||||
|
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,
|
||||||
|
epoch: 0,
|
||||||
|
prev_stake: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// verify that delegate_stake can't be called twice StakeState::default()
|
||||||
|
// signed keyed account
|
||||||
|
assert_eq!(
|
||||||
|
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports, ¤t),
|
||||||
|
Err(InstructionError::InvalidAccountData)
|
||||||
|
);
|
||||||
|
|
||||||
// verify can only stake up to account lamports
|
// verify can only stake up to account lamports
|
||||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports + 1),
|
stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports + 1, ¤t),
|
||||||
Err(InstructionError::InsufficientFunds)
|
Err(InstructionError::InsufficientFunds)
|
||||||
);
|
);
|
||||||
|
|
||||||
// verify that create_stake_account() matches the
|
|
||||||
// resulting account from delegate_stake()
|
|
||||||
assert_eq!(
|
|
||||||
create_stake_account(&vote_pubkey, &vote_state, stake_lamports),
|
|
||||||
*stake_keyed_account.account,
|
|
||||||
);
|
|
||||||
|
|
||||||
let stake_state: StakeState = stake_keyed_account.state().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
stake_state,
|
|
||||||
StakeState::Stake {
|
|
||||||
voter_pubkey: vote_keypair.pubkey(),
|
|
||||||
credits_observed: vote_state.credits(),
|
|
||||||
stake: stake_lamports,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let stake_state = StakeState::MiningPool {
|
let stake_state = StakeState::MiningPool {
|
||||||
epoch: 0,
|
epoch: 0,
|
||||||
point_value: 0.0,
|
point_value: 0.0,
|
||||||
};
|
};
|
||||||
stake_keyed_account.set_state(&stake_state).unwrap();
|
stake_keyed_account.set_state(&stake_state).unwrap();
|
||||||
assert!(stake_keyed_account
|
assert!(stake_keyed_account
|
||||||
.delegate_stake(&vote_keyed_account, 0)
|
.delegate_stake(&vote_keyed_account, 0, ¤t)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stake_stake() {
|
||||||
|
let mut stake = Stake::default();
|
||||||
|
assert_eq!(stake.stake(0), 0);
|
||||||
|
let staked = STAKE_WARMUP_COOLDOWN_EPOCHS;
|
||||||
|
stake.delegate(staked, &Pubkey::default(), &VoteState::default(), 1);
|
||||||
|
// test warmup
|
||||||
|
for i in 0..STAKE_WARMUP_COOLDOWN_EPOCHS {
|
||||||
|
assert_eq!(stake.stake(i), i);
|
||||||
|
}
|
||||||
|
assert_eq!(stake.stake(STAKE_WARMUP_COOLDOWN_EPOCHS * 42), staked);
|
||||||
|
|
||||||
|
stake.deactivate(STAKE_WARMUP_COOLDOWN_EPOCHS);
|
||||||
|
|
||||||
|
// test cooldown
|
||||||
|
for i in STAKE_WARMUP_COOLDOWN_EPOCHS..STAKE_WARMUP_COOLDOWN_EPOCHS * 2 {
|
||||||
|
assert_eq!(
|
||||||
|
stake.stake(i),
|
||||||
|
staked
|
||||||
|
- (staked / STAKE_WARMUP_COOLDOWN_EPOCHS) * (i - STAKE_WARMUP_COOLDOWN_EPOCHS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(stake.stake(STAKE_WARMUP_COOLDOWN_EPOCHS * 42), 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_state_calculate_rewards() {
|
fn test_stake_state_calculate_rewards() {
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
@ -380,6 +485,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_redeem_vote_credits() {
|
fn test_stake_redeem_vote_credits() {
|
||||||
|
let current = syscall::current::Current::default();
|
||||||
|
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
for i in 0..1000 {
|
||||||
@ -399,11 +506,10 @@ mod tests {
|
|||||||
&id(),
|
&id(),
|
||||||
);
|
);
|
||||||
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
|
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
|
||||||
stake_keyed_account.initialize_stake().unwrap();
|
|
||||||
|
|
||||||
// delegate the stake
|
// delegate the stake
|
||||||
assert!(stake_keyed_account
|
assert!(stake_keyed_account
|
||||||
.delegate_stake(&vote_keyed_account, STAKE_GETS_PAID_EVERY_VOTE)
|
.delegate_stake(&vote_keyed_account, STAKE_GETS_PAID_EVERY_VOTE, ¤t)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
||||||
@ -457,6 +563,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stake_redeem_vote_credits_vote_errors() {
|
fn test_stake_redeem_vote_credits_vote_errors() {
|
||||||
|
let current = syscall::current::Current::default();
|
||||||
|
|
||||||
let vote_keypair = Keypair::new();
|
let vote_keypair = Keypair::new();
|
||||||
let mut vote_state = VoteState::default();
|
let mut vote_state = VoteState::default();
|
||||||
for i in 0..1000 {
|
for i in 0..1000 {
|
||||||
@ -474,11 +582,10 @@ mod tests {
|
|||||||
let mut stake_account =
|
let mut stake_account =
|
||||||
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
|
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
|
||||||
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
|
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
|
||||||
stake_keyed_account.initialize_stake().unwrap();
|
|
||||||
|
|
||||||
// delegate the stake
|
// delegate the stake
|
||||||
assert!(stake_keyed_account
|
assert!(stake_keyed_account
|
||||||
.delegate_stake(&vote_keyed_account, stake_lamports)
|
.delegate_stake(&vote_keyed_account, stake_lamports, ¤t)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
||||||
|
@ -21,16 +21,17 @@ pub struct Stakes {
|
|||||||
|
|
||||||
impl Stakes {
|
impl Stakes {
|
||||||
// sum the stakes that point to the given voter_pubkey
|
// sum the stakes that point to the given voter_pubkey
|
||||||
fn calculate_stake(&self, voter: &Pubkey) -> u64 {
|
fn calculate_stake(&self, voter_pubkey: &Pubkey) -> u64 {
|
||||||
self.stake_accounts
|
self.stake_accounts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, stake_account)| match StakeState::from(stake_account) {
|
.map(|(_, stake_account)| {
|
||||||
Some(StakeState::Stake {
|
StakeState::stake_from(stake_account).map_or(0, |stake| {
|
||||||
voter_pubkey,
|
if stake.voter_pubkey == *voter_pubkey {
|
||||||
stake,
|
stake.stake
|
||||||
..
|
} else {
|
||||||
}) if *voter == voter_pubkey => stake,
|
0
|
||||||
_ => 0,
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
@ -659,15 +659,13 @@ fn process_show_stake_account(
|
|||||||
use solana_stake_api::stake_state::StakeState;
|
use solana_stake_api::stake_state::StakeState;
|
||||||
let stake_account = rpc_client.get_account(staking_account_pubkey)?;
|
let stake_account = rpc_client.get_account(staking_account_pubkey)?;
|
||||||
match stake_account.state() {
|
match stake_account.state() {
|
||||||
Ok(StakeState::Stake {
|
Ok(StakeState::Stake(stake)) => {
|
||||||
voter_pubkey,
|
|
||||||
credits_observed,
|
|
||||||
stake,
|
|
||||||
}) => {
|
|
||||||
println!("account lamports: {}", stake_account.lamports);
|
println!("account lamports: {}", stake_account.lamports);
|
||||||
println!("voter pubkey: {}", voter_pubkey);
|
println!("voter pubkey: {}", stake.voter_pubkey);
|
||||||
println!("credits observed: {}", credits_observed);
|
println!("credits observed: {}", stake.credits_observed);
|
||||||
println!("activated stake: {}", stake);
|
println!("epoch: {}", stake.epoch);
|
||||||
|
println!("activated stake: {}", stake.stake);
|
||||||
|
println!("previous stake: {}", stake.prev_stake);
|
||||||
Ok("".to_string())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
Ok(StakeState::MiningPool { epoch, point_value }) => {
|
Ok(StakeState::MiningPool { epoch, point_value }) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user