solana_program/
stake_history.rs1pub use solana_clock::Epoch;
10use std::ops::Deref;
11
12pub const MAX_ENTRIES: usize = 512; #[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, pub activating: u64, pub deactivating: u64, }
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}