solana_program/
stake_history.rs

1//! A type to hold data for the [`StakeHistory` sysvar][sv].
2//!
3//! [sv]: https://docs.solanalabs.com/runtime/sysvars#stakehistory
4//!
5//! The sysvar ID is declared in [`sysvar::stake_history`].
6//!
7//! [`sysvar::stake_history`]: crate::sysvar::stake_history
8
9pub use solana_clock::Epoch;
10use std::ops::Deref;
11
12pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down
13
14#[repr(C)]
15#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
16#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone)]
17pub struct StakeHistoryEntry {
18    pub effective: u64,    // effective stake at this epoch
19    pub activating: u64,   // sum of portion of stakes not fully warmed up
20    pub deactivating: u64, // requested to be cooled down, not fully deactivated yet
21}
22
23impl StakeHistoryEntry {
24    pub fn with_effective(effective: u64) -> Self {
25        Self {
26            effective,
27            ..Self::default()
28        }
29    }
30
31    pub fn with_effective_and_activating(effective: u64, activating: u64) -> Self {
32        Self {
33            effective,
34            activating,
35            ..Self::default()
36        }
37    }
38
39    pub fn with_deactivating(deactivating: u64) -> Self {
40        Self {
41            effective: deactivating,
42            deactivating,
43            ..Self::default()
44        }
45    }
46}
47
48impl std::ops::Add for StakeHistoryEntry {
49    type Output = StakeHistoryEntry;
50    fn add(self, rhs: StakeHistoryEntry) -> Self::Output {
51        Self {
52            effective: self.effective.saturating_add(rhs.effective),
53            activating: self.activating.saturating_add(rhs.activating),
54            deactivating: self.deactivating.saturating_add(rhs.deactivating),
55        }
56    }
57}
58
59#[repr(C)]
60#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
61#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone)]
62pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>);
63
64impl StakeHistory {
65    pub fn get(&self, epoch: Epoch) -> Option<&StakeHistoryEntry> {
66        self.binary_search_by(|probe| epoch.cmp(&probe.0))
67            .ok()
68            .map(|index| &self[index].1)
69    }
70
71    pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) {
72        match self.binary_search_by(|probe| epoch.cmp(&probe.0)) {
73            Ok(index) => (self.0)[index] = (epoch, entry),
74            Err(index) => (self.0).insert(index, (epoch, entry)),
75        }
76        (self.0).truncate(MAX_ENTRIES);
77    }
78}
79
80impl Deref for StakeHistory {
81    type Target = Vec<(Epoch, StakeHistoryEntry)>;
82    fn deref(&self) -> &Self::Target {
83        &self.0
84    }
85}
86
87pub trait StakeHistoryGetEntry {
88    fn get_entry(&self, epoch: Epoch) -> Option<StakeHistoryEntry>;
89}
90
91impl StakeHistoryGetEntry for StakeHistory {
92    fn get_entry(&self, epoch: Epoch) -> Option<StakeHistoryEntry> {
93        self.binary_search_by(|probe| epoch.cmp(&probe.0))
94            .ok()
95            .map(|index| self[index].1.clone())
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_stake_history() {
105        let mut stake_history = StakeHistory::default();
106
107        for i in 0..MAX_ENTRIES as u64 + 1 {
108            stake_history.add(
109                i,
110                StakeHistoryEntry {
111                    activating: i,
112                    ..StakeHistoryEntry::default()
113                },
114            );
115        }
116        assert_eq!(stake_history.len(), MAX_ENTRIES);
117        assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1);
118        assert_eq!(stake_history.get(0), None);
119        assert_eq!(
120            stake_history.get(1),
121            Some(&StakeHistoryEntry {
122                activating: 1,
123                ..StakeHistoryEntry::default()
124            })
125        );
126    }
127}