solana_stake_interface/
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
5pub use solana_clock::Epoch;
6use std::ops::Deref;
7
8pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down
9
10#[repr(C)]
11#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
12#[cfg_attr(
13    feature = "serde",
14    derive(serde_derive::Deserialize, serde_derive::Serialize)
15)]
16#[derive(Debug, 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(solana_frozen_abi_macro::AbiExample))]
61#[cfg_attr(
62    feature = "serde",
63    derive(serde_derive::Deserialize, serde_derive::Serialize)
64)]
65#[derive(Debug, PartialEq, Eq, Default, Clone)]
66pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>);
67
68impl StakeHistory {
69    #[inline]
70    fn latest_epoch(&self) -> Option<&Epoch> {
71        self.first().map(|(epoch, _)| epoch)
72    }
73
74    pub fn get(&self, epoch: Epoch) -> Option<&StakeHistoryEntry> {
75        self.latest_epoch()
76            .and_then(|latest| latest.checked_sub(epoch))
77            .and_then(|index| self.0.get(index as usize).map(|(_, entry)| entry))
78    }
79
80    pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) {
81        match self.binary_search_by(|probe| epoch.cmp(&probe.0)) {
82            Ok(index) => (self.0)[index] = (epoch, entry),
83            Err(index) => (self.0).insert(index, (epoch, entry)),
84        }
85        (self.0).truncate(MAX_ENTRIES);
86    }
87}
88
89impl Deref for StakeHistory {
90    type Target = Vec<(Epoch, StakeHistoryEntry)>;
91    fn deref(&self) -> &Self::Target {
92        &self.0
93    }
94}
95
96pub trait StakeHistoryGetEntry {
97    fn get_entry(&self, epoch: Epoch) -> Option<StakeHistoryEntry>;
98}
99
100impl StakeHistoryGetEntry for StakeHistory {
101    fn get_entry(&self, epoch: Epoch) -> Option<StakeHistoryEntry> {
102        self.get(epoch).map(|entry| entry.to_owned())
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use {super::*, solana_sysvar_id::SysvarId};
109
110    #[test]
111    fn test_stake_history() {
112        let mut stake_history = StakeHistory::default();
113
114        for i in 0..MAX_ENTRIES as u64 + 1 {
115            stake_history.add(
116                i,
117                StakeHistoryEntry {
118                    activating: i,
119                    ..StakeHistoryEntry::default()
120                },
121            );
122        }
123        assert_eq!(stake_history.len(), MAX_ENTRIES);
124        assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1);
125        assert_eq!(stake_history.get(0), None);
126        for i in 0..MAX_ENTRIES {
127            let epoch = (i + 1) as u64;
128            assert_eq!(
129                stake_history.get(epoch),
130                Some(&StakeHistoryEntry {
131                    activating: epoch,
132                    ..StakeHistoryEntry::default()
133                })
134            );
135        }
136    }
137
138    #[test]
139    fn test_id() {
140        assert_eq!(
141            StakeHistory::id(),
142            solana_sdk_ids::sysvar::stake_history::id()
143        );
144    }
145}