diff --git a/book/src/cluster/stake-delegation-and-rewards.md b/book/src/cluster/stake-delegation-and-rewards.md index a7c1933b3..a36457c75 100644 --- a/book/src/cluster/stake-delegation-and-rewards.md +++ b/book/src/cluster/stake-delegation-and-rewards.md @@ -94,12 +94,13 @@ The Stakes and the RewardsPool are accounts that are owned by the same `Stake` p ### StakeInstruction::DelegateStake -The Stake account is moved from Ininitialized to StakeState::Stake form. This is how stakers choose their initial delegate validator node and activate their stake account lamports. The transaction must be signed by the stake's `authorized_staker`. If the stake account is already StakeState::Stake \(i.e. already activated\), the stake is re-delegated. Stakes may be re-delegated at any time, and updated stakes are reflected immediately, but only one re-delegation is permitted per epoch. +The Stake account is moved from Initialized to StakeState::Stake form, or from a deactivated (i.e. fully cooled-down) StakeState::Stake to activated StakeState::Stake. This is how stakers choose the vote account and validator node to which their stake account lamports are delegated. The transaction must be signed by the stake's `authorized_staker`. * `account[0]` - RW - The StakeState::Stake instance. `StakeState::Stake::credits_observed` is initialized to `VoteState::credits`, `StakeState::Stake::voter_pubkey` is initialized to `account[1]`. If this is the initial delegation of stake, `StakeState::Stake::stake` is initialized to the account's balance in lamports, `StakeState::Stake::activated` is initialized to the current Bank epoch, and `StakeState::Stake::deactivated` is initialized to std::u64::MAX * `account[1]` - R - The VoteState instance. * `account[2]` - R - sysvar::clock account, carries information about current Bank epoch -* `account[3]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration +* `account[3]` - R - sysvar::stakehistory account, carries information about stake history +* `account[4]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration ### StakeInstruction::Authorize\(Pubkey, StakeAuthorize\) diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 2d4648e8f..510583595 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -306,6 +306,7 @@ pub fn delegate_stake( AccountMeta::new(*stake_pubkey, false), AccountMeta::new_readonly(*vote_pubkey, false), AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(sysvar::stake_history::id(), false), AccountMeta::new_readonly(crate::config::id(), false), ] .with_signer(authorized_pubkey); @@ -396,9 +397,10 @@ pub fn process_instruction( StakeInstruction::DelegateStake => { let vote = next_keyed_account(keyed_accounts)?; - me.delegate_stake( + me.delegate( &vote, &Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?, + &StakeHistory::from_keyed_account(next_keyed_account(keyed_accounts)?)?, &config::from_keyed_account(next_keyed_account(keyed_accounts)?)?, &signers, ) @@ -418,7 +420,7 @@ pub fn process_instruction( &signers, ) } - StakeInstruction::Deactivate => me.deactivate_stake( + StakeInstruction::Deactivate => me.deactivate( &Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?, &signers, ), @@ -658,6 +660,13 @@ mod tests { false, &RefCell::new(sysvar::clock::Clock::default().create_account(1)) ), + KeyedAccount::new( + &sysvar::stake_history::id(), + false, + &RefCell::new( + sysvar::stake_history::StakeHistory::default().create_account(1) + ) + ), KeyedAccount::new( &config::id(), false, diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index 31918901d..538eb20cc 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -8,14 +8,11 @@ use serde_derive::{Deserialize, Serialize}; use solana_sdk::{ account::{Account, KeyedAccount}, account_utils::{State, StateMut}, - clock::{Clock, Epoch, Slot, UnixTimestamp}, + clock::{Clock, Epoch, UnixTimestamp}, instruction::InstructionError, pubkey::Pubkey, rent::Rent, - sysvar::{ - self, - stake_history::{StakeHistory, StakeHistoryEntry}, - }, + stake_history::{StakeHistory, StakeHistoryEntry}, }; use solana_vote_program::vote_state::VoteState; use std::collections::HashSet; @@ -154,7 +151,7 @@ impl Meta { pub struct Delegation { /// to whom the stake is delegated pub voter_pubkey: Pubkey, - /// activated stake amount, set at delegate_stake() time + /// activated stake amount, set at delegate() time pub stake: u64, /// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake pub activation_epoch: Epoch, @@ -311,32 +308,11 @@ impl Delegation { } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)] pub struct Stake { pub delegation: Delegation, - /// the epoch when voter_pubkey was most recently set - pub voter_pubkey_epoch: Epoch, /// credits observed is credits from vote account state when delegated or redeemed pub credits_observed: u64, - /// history of prior delegates and the epoch ranges for which - /// they were set, circular buffer - pub prior_delegates: [(Pubkey, Epoch, Epoch, Slot); MAX_PRIOR_DELEGATES], - /// next pointer - pub prior_delegates_idx: usize, -} - -const MAX_PRIOR_DELEGATES: usize = 32; // this is how many epochs a stake is exposed to a slashing condition - -impl Default for Stake { - fn default() -> Self { - Self { - delegation: Delegation::default(), - voter_pubkey_epoch: 0, - credits_observed: 0, - prior_delegates: <[(Pubkey, Epoch, Epoch, Slot); MAX_PRIOR_DELEGATES]>::default(), - prior_delegates_idx: MAX_PRIOR_DELEGATES - 1, - } - } } impl Authorized { @@ -455,25 +431,19 @@ impl Stake { voter_pubkey: &Pubkey, vote_state: &VoteState, clock: &Clock, + stake_history: &StakeHistory, + config: &Config, ) -> Result<(), StakeError> { - // only one re-delegation supported per epoch - if self.voter_pubkey_epoch == clock.epoch { + // 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 { return Err(StakeError::TooSoonToRedelegate); } - - // remember prior delegate and when we switched, to support later slashing - self.prior_delegates_idx += 1; - self.prior_delegates_idx %= MAX_PRIOR_DELEGATES; - - self.prior_delegates[self.prior_delegates_idx] = ( - self.delegation.voter_pubkey, - self.voter_pubkey_epoch, - clock.epoch, - clock.slot, - ); - + self.delegation.activation_epoch = clock.epoch; + self.delegation.deactivation_epoch = std::u64::MAX; self.delegation.voter_pubkey = *voter_pubkey; - self.voter_pubkey_epoch = clock.epoch; + self.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate; self.credits_observed = vote_state.credits(); Ok(()) } @@ -507,9 +477,7 @@ impl Stake { activation_epoch, config.warmup_cooldown_rate, ), - voter_pubkey_epoch: activation_epoch, credits_observed: vote_state.credits(), - ..Stake::default() } } @@ -537,18 +505,15 @@ pub trait StakeAccount { signers: &HashSet, clock: &Clock, ) -> Result<(), InstructionError>; - fn delegate_stake( + fn delegate( &self, vote_account: &KeyedAccount, - clock: &sysvar::clock::Clock, + clock: &Clock, + stake_history: &StakeHistory, config: &Config, signers: &HashSet, ) -> Result<(), InstructionError>; - fn deactivate_stake( - &self, - clock: &sysvar::clock::Clock, - signers: &HashSet, - ) -> Result<(), InstructionError>; + fn deactivate(&self, clock: &Clock, signers: &HashSet) -> Result<(), InstructionError>; fn set_lockup( &self, lockup: &Lockup, @@ -564,8 +529,8 @@ pub trait StakeAccount { &self, lamports: u64, to: &KeyedAccount, - clock: &sysvar::clock::Clock, - stake_history: &sysvar::stake_history::StakeHistory, + clock: &Clock, + stake_history: &StakeHistory, signers: &HashSet, ) -> Result<(), InstructionError>; } @@ -616,10 +581,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> { _ => Err(InstructionError::InvalidAccountData), } } - fn delegate_stake( + fn delegate( &self, vote_account: &KeyedAccount, - clock: &sysvar::clock::Clock, + clock: &Clock, + stake_history: &StakeHistory, config: &Config, signers: &HashSet, ) -> Result<(), InstructionError> { @@ -637,17 +603,19 @@ impl<'a> StakeAccount for KeyedAccount<'a> { } StakeState::Stake(meta, mut stake) => { meta.authorized.check(signers, StakeAuthorize::Staker)?; - stake.redelegate(vote_account.unsigned_key(), &vote_account.state()?, &clock)?; + stake.redelegate( + vote_account.unsigned_key(), + &vote_account.state()?, + clock, + stake_history, + config, + )?; self.set_state(&StakeState::Stake(meta, stake)) } _ => Err(InstructionError::InvalidAccountData), } } - fn deactivate_stake( - &self, - clock: &sysvar::clock::Clock, - signers: &HashSet, - ) -> Result<(), InstructionError> { + fn deactivate(&self, clock: &Clock, signers: &HashSet) -> Result<(), InstructionError> { if let StakeState::Stake(meta, mut stake) = self.state()? { meta.authorized.check(signers, StakeAuthorize::Staker)?; stake.deactivate(clock.epoch)?; @@ -743,8 +711,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> { &self, lamports: u64, to: &KeyedAccount, - clock: &sysvar::clock::Clock, - stake_history: &sysvar::stake_history::StakeHistory, + clock: &Clock, + stake_history: &StakeHistory, signers: &HashSet, ) -> Result<(), InstructionError> { let (lockup, reserve, is_staked) = match self.state()? { @@ -1012,10 +980,10 @@ mod tests { } #[test] - fn test_stake_delegate_stake() { - let mut clock = sysvar::clock::Clock { + fn test_stake_delegate() { + let mut clock = Clock { epoch: 1, - ..sysvar::clock::Clock::default() + ..Clock::default() }; let vote_pubkey = Pubkey::new_rand(); @@ -1052,25 +1020,12 @@ mod tests { // unsigned keyed account let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account); - { - let stake_state: StakeState = stake_keyed_account.state().unwrap(); - assert_eq!( - stake_state, - StakeState::Initialized(Meta { - authorized: Authorized { - staker: stake_pubkey, - withdrawer: stake_pubkey, - }, - ..Meta::default() - }) - ); - } - let mut signers = HashSet::default(); assert_eq!( - stake_keyed_account.delegate_stake( + stake_keyed_account.delegate( &vote_keyed_account, &clock, + &StakeHistory::default(), &Config::default(), &signers, ), @@ -1081,10 +1036,16 @@ mod tests { let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); signers.insert(stake_pubkey); assert!(stake_keyed_account - .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers,) + .delegate( + &vote_keyed_account, + &clock, + &StakeHistory::default(), + &Config::default(), + &signers, + ) .is_ok()); - // verify that delegate_stake() looks right, compare against hand-rolled + // verify that delegate() looks right, compare against hand-rolled let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap(); assert_eq!( stake, @@ -1096,79 +1057,74 @@ mod tests { deactivation_epoch: std::u64::MAX, ..Delegation::default() }, - voter_pubkey_epoch: clock.epoch, credits_observed: vote_state.credits(), ..Stake::default() } ); - // verify that delegate_stake can be called twice, 2nd is redelegate + + clock.epoch += 1; + + // verify that delegate fails if stake is still active assert_eq!( - stake_keyed_account.delegate_stake( + stake_keyed_account.delegate( &vote_keyed_account, &clock, + &StakeHistory::default(), &Config::default(), &signers ), Err(StakeError::TooSoonToRedelegate.into()) ); + // deactivate, so we can re-delegate + stake_keyed_account.deactivate(&clock, &signers).unwrap(); + + // without stake history, cool down is instantaneous clock.epoch += 1; - // verify that delegate_stake can be called twice, 2nd is redelegate + + // verify that delegate can be called twice, 2nd is redelegate assert!(stake_keyed_account - .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers) + .delegate( + &vote_keyed_account, + &clock, + &StakeHistory::default(), + &Config::default(), + &signers + ) .is_ok()); - // verify that non-stakes fail delegate_stake() + // 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() + }, + credits_observed: vote_state.credits(), + ..Stake::default() + } + ); + + // verify that non-stakes fail delegate() 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) + .delegate( + &vote_keyed_account, + &clock, + &StakeHistory::default(), + &Config::default(), + &signers + ) .is_err()); } - #[test] - fn test_stake_redelegate() { - // what a freshly delegated stake looks like - let mut stake = Stake { - delegation: Delegation { - voter_pubkey: Pubkey::new_rand(), - ..Delegation::default() - }, - voter_pubkey_epoch: 0, - ..Stake::default() - }; - // verify that redelegation works when epoch is changing, that - // wraparound works, and that the stake is delegated - // to the most recent vote account - for epoch in 1..=MAX_PRIOR_DELEGATES + 2 { - let voter_pubkey = Pubkey::new_rand(); - assert_eq!( - stake.redelegate( - &voter_pubkey, - &VoteState::default(), - &sysvar::clock::Clock { - epoch: epoch as u64, - ..sysvar::clock::Clock::default() - }, - ), - Ok(()) - ); - assert_eq!( - stake.redelegate( - &voter_pubkey, - &VoteState::default(), - &sysvar::clock::Clock { - epoch: epoch as u64, - ..sysvar::clock::Clock::default() - }, - ), - Err(StakeError::TooSoonToRedelegate) - ); - assert_eq!(stake.delegation.voter_pubkey, voter_pubkey); - } - } - fn create_stake_history_from_delegations( bootstrap: Option, epochs: std::ops::Range, @@ -1546,7 +1502,7 @@ mod tests { } #[test] - fn test_deactivate_stake() { + fn test_deactivate() { let stake_pubkey = Pubkey::new_rand(); let stake_lamports = 42; let stake_account = Account::new_ref_data_with_space( @@ -1557,16 +1513,16 @@ mod tests { ) .expect("stake_account"); - let clock = sysvar::clock::Clock { + let clock = Clock { epoch: 1, - ..sysvar::clock::Clock::default() + ..Clock::default() }; // signed keyed account but not staked yet let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); let signers = vec![stake_pubkey].into_iter().collect(); assert_eq!( - stake_keyed_account.deactivate_stake(&clock, &signers), + stake_keyed_account.deactivate(&clock, &signers), Err(InstructionError::InvalidAccountData) ); @@ -1581,9 +1537,10 @@ mod tests { let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account); vote_keyed_account.set_state(&VoteState::default()).unwrap(); assert_eq!( - stake_keyed_account.delegate_stake( + stake_keyed_account.delegate( &vote_keyed_account, &clock, + &StakeHistory::default(), &Config::default(), &signers ), @@ -1593,20 +1550,17 @@ mod tests { // no signers fails let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account); assert_eq!( - stake_keyed_account.deactivate_stake(&clock, &HashSet::default()), + stake_keyed_account.deactivate(&clock, &HashSet::default()), Err(InstructionError::MissingRequiredSignature) ); // Deactivate after staking let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); - assert_eq!( - stake_keyed_account.deactivate_stake(&clock, &signers), - Ok(()) - ); + assert_eq!(stake_keyed_account.deactivate(&clock, &signers), Ok(())); // verify that deactivate() only works once assert_eq!( - stake_keyed_account.deactivate_stake(&clock, &signers), + stake_keyed_account.deactivate(&clock, &signers), Err(StakeError::AlreadyDeactivated.into()) ); } @@ -1673,9 +1627,10 @@ mod tests { vote_keyed_account.set_state(&VoteState::default()).unwrap(); stake_keyed_account - .delegate_stake( + .delegate( &vote_keyed_account, &Clock::default(), + &StakeHistory::default(), &Config::default(), &vec![stake_pubkey].into_iter().collect(), ) @@ -1717,7 +1672,7 @@ mod tests { ) .expect("stake_account"); - let mut clock = sysvar::clock::Clock::default(); + let mut clock = Clock::default(); let to = Pubkey::new_rand(); let to_account = Account::new_ref(1, 0, &system_program::id()); @@ -1795,9 +1750,10 @@ mod tests { let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account); vote_keyed_account.set_state(&VoteState::default()).unwrap(); assert_eq!( - stake_keyed_account.delegate_stake( + stake_keyed_account.delegate( &vote_keyed_account, &clock, + &StakeHistory::default(), &Config::default(), &signers, ), @@ -1837,10 +1793,7 @@ mod tests { ); // deactivate the stake before withdrawal - assert_eq!( - stake_keyed_account.deactivate_stake(&clock, &signers), - Ok(()) - ); + assert_eq!(stake_keyed_account.deactivate(&clock, &signers), Ok(())); // simulate time passing clock.epoch += 100; @@ -1885,8 +1838,8 @@ mod tests { ) .expect("stake_account"); - let clock = sysvar::clock::Clock::default(); - let mut future = sysvar::clock::Clock::default(); + let clock = Clock::default(); + let mut future = Clock::default(); future.epoch += 16; let to = Pubkey::new_rand(); @@ -1907,9 +1860,10 @@ mod tests { vote_keyed_account.set_state(&VoteState::default()).unwrap(); let signers = vec![stake_pubkey].into_iter().collect(); assert_eq!( - stake_keyed_account.delegate_stake( + stake_keyed_account.delegate( &vote_keyed_account, &future, + &StakeHistory::default(), &Config::default(), &signers, ), @@ -1960,7 +1914,7 @@ mod tests { stake_keyed_account.withdraw( total_lamports, &to_keyed_account, - &sysvar::clock::Clock::default(), + &Clock::default(), &StakeHistory::default(), &signers, ), @@ -1994,7 +1948,7 @@ mod tests { let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); - let mut clock = sysvar::clock::Clock::default(); + let mut clock = Clock::default(); let signers = vec![stake_pubkey].into_iter().collect(); @@ -2202,7 +2156,7 @@ mod tests { let to_account = Account::new_ref(1, 0, &system_program::id()); let to_keyed_account = KeyedAccount::new(&to, false, &to_account); - let clock = sysvar::clock::Clock::default(); + let clock = Clock::default(); let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); let stake_pubkey0 = Pubkey::new_rand(); @@ -2816,7 +2770,7 @@ mod tests { ) .expect("stake_account"); - let mut clock = Clock::default(); + let clock = Clock::default(); let vote_pubkey = Pubkey::new_rand(); let vote_account = RefCell::new(vote_state::create_account( @@ -2830,9 +2784,18 @@ mod tests { let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account); let signers = vec![stake_pubkey].into_iter().collect(); stake_keyed_account - .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers) + .delegate( + &vote_keyed_account, + &clock, + &StakeHistory::default(), + &Config::default(), + &signers, + ) .unwrap(); + // deactivate, so we can re-delegate + stake_keyed_account.deactivate(&clock, &signers).unwrap(); + let new_staker_pubkey = Pubkey::new_rand(); assert_eq!( stake_keyed_account.authorize( @@ -2864,13 +2827,12 @@ mod tests { let new_vote_keyed_account = KeyedAccount::new(&new_voter_pubkey, false, &new_vote_account); new_vote_keyed_account.set_state(&vote_state).unwrap(); - // time passes, so we can re-delegate - clock.epoch += 1; // Random other account should fail assert_eq!( - stake_keyed_account.delegate_stake( + stake_keyed_account.delegate( &new_vote_keyed_account, &clock, + &StakeHistory::default(), &Config::default(), &other_signers, ), @@ -2880,9 +2842,10 @@ mod tests { let new_signers = vec![new_staker_pubkey].into_iter().collect(); // Authorized staker should succeed assert_eq!( - stake_keyed_account.delegate_stake( + stake_keyed_account.delegate( &new_vote_keyed_account, &clock, + &StakeHistory::default(), &Config::default(), &new_signers ), @@ -2893,9 +2856,6 @@ mod tests { assert_eq!(stake.delegation.voter_pubkey, new_voter_pubkey); // Test another staking action - assert_eq!( - stake_keyed_account.deactivate_stake(&clock, &new_signers), - Ok(()) - ); + assert_eq!(stake_keyed_account.deactivate(&clock, &new_signers), Ok(())); } } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 00b6e3c4d..79980b6f9 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -26,6 +26,7 @@ pub mod rpc_port; pub mod short_vec; pub mod slot_hashes; pub mod slot_history; +pub mod stake_history; pub mod system_instruction; pub mod system_program; pub mod sysvar; diff --git a/sdk/src/stake_history.rs b/sdk/src/stake_history.rs new file mode 100644 index 000000000..aaece8810 --- /dev/null +++ b/sdk/src/stake_history.rs @@ -0,0 +1,74 @@ +//! named accounts for synthesized data accounts for bank state, etc. +//! +//! this account carries history about stake activations and de-activations +//! +pub use crate::clock::Epoch; + +use std::ops::Deref; + +pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down + +#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)] +pub struct StakeHistoryEntry { + pub effective: u64, // effective stake at this epoch + pub activating: u64, // sum of portion of stakes not fully warmed up + pub deactivating: u64, // requested to be cooled down, not fully deactivated yet +} + +#[repr(C)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)] +pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>); + +impl StakeHistory { + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn get(&self, epoch: &Epoch) -> Option<&StakeHistoryEntry> { + self.binary_search_by(|probe| epoch.cmp(&probe.0)) + .ok() + .map(|index| &self[index].1) + } + + pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) { + match self.binary_search_by(|probe| epoch.cmp(&probe.0)) { + Ok(index) => (self.0)[index] = (epoch, entry), + Err(index) => (self.0).insert(index, (epoch, entry)), + } + (self.0).truncate(MAX_ENTRIES); + } +} + +impl Deref for StakeHistory { + type Target = Vec<(Epoch, StakeHistoryEntry)>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_stake_history() { + let mut stake_history = StakeHistory::default(); + + for i in 0..MAX_ENTRIES as u64 + 1 { + stake_history.add( + i, + StakeHistoryEntry { + activating: i, + ..StakeHistoryEntry::default() + }, + ); + } + assert_eq!(stake_history.len(), MAX_ENTRIES); + assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1); + assert_eq!(stake_history.get(&0), None); + assert_eq!( + stake_history.get(&1), + Some(&StakeHistoryEntry { + activating: 1, + ..StakeHistoryEntry::default() + }) + ); + } +} diff --git a/sdk/src/sysvar/stake_history.rs b/sdk/src/sysvar/stake_history.rs index 5399fcfe0..3731320b2 100644 --- a/sdk/src/sysvar/stake_history.rs +++ b/sdk/src/sysvar/stake_history.rs @@ -2,26 +2,12 @@ //! //! this account carries history about stake activations and de-activations //! -pub use crate::clock::Epoch; +pub use crate::stake_history::StakeHistory; use crate::{account::Account, sysvar::Sysvar}; -use std::ops::Deref; crate::declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory); -pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down - -#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)] -pub struct StakeHistoryEntry { - pub effective: u64, // effective stake at this epoch - pub activating: u64, // sum of portion of stakes not fully warmed up - pub deactivating: u64, // requested to be cooled down, not fully deactivated yet -} - -#[repr(C)] -#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)] -pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>); - impl Sysvar for StakeHistory { // override fn size_of() -> usize { @@ -30,30 +16,6 @@ impl Sysvar for StakeHistory { } } -impl StakeHistory { - #[allow(clippy::trivially_copy_pass_by_ref)] - pub fn get(&self, epoch: &Epoch) -> Option<&StakeHistoryEntry> { - self.binary_search_by(|probe| epoch.cmp(&probe.0)) - .ok() - .map(|index| &self[index].1) - } - - pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) { - match self.binary_search_by(|probe| epoch.cmp(&probe.0)) { - Ok(index) => (self.0)[index] = (epoch, entry), - Err(index) => (self.0).insert(index, (epoch, entry)), - } - (self.0).truncate(MAX_ENTRIES); - } -} - -impl Deref for StakeHistory { - type Target = Vec<(Epoch, StakeHistoryEntry)>; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - pub fn create_account(lamports: u64, stake_history: &StakeHistory) -> Account { stake_history.create_account(lamports) } @@ -61,15 +23,23 @@ pub fn create_account(lamports: u64, stake_history: &StakeHistory) -> Account { #[cfg(test)] mod tests { use super::*; + use crate::stake_history::*; #[test] fn test_size_of() { + let mut stake_history = StakeHistory::default(); + for i in 0..MAX_ENTRIES as u64 { + stake_history.add( + i, + StakeHistoryEntry { + activating: i, + ..StakeHistoryEntry::default() + }, + ); + } + assert_eq!( - bincode::serialized_size(&StakeHistory(vec![ - (0, StakeHistoryEntry::default()); - MAX_ENTRIES - ])) - .unwrap() as usize, + bincode::serialized_size(&stake_history).unwrap() as usize, StakeHistory::size_of() ); }