solana_program/sysvar/
stake_history.rs1pub use crate::stake_history::StakeHistory;
49use {
50 crate::{
51 stake_history::{StakeHistoryEntry, StakeHistoryGetEntry, MAX_ENTRIES},
52 sysvar::{get_sysvar, Sysvar, SysvarId},
53 },
54 solana_clock::Epoch,
55 solana_sysvar_id::declare_sysvar_id,
56};
57
58declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory);
59
60impl Sysvar for StakeHistory {
61 fn size_of() -> usize {
63 16392 }
66}
67
68#[derive(Debug, PartialEq, Eq, Clone)]
70pub struct StakeHistorySysvar(pub Epoch);
71
72const EPOCH_AND_ENTRY_SERIALIZED_SIZE: u64 = 32;
74
75impl StakeHistoryGetEntry for StakeHistorySysvar {
76 fn get_entry(&self, target_epoch: Epoch) -> Option<StakeHistoryEntry> {
77 let current_epoch = self.0;
78
79 let newest_historical_epoch = current_epoch.checked_sub(1)?;
81 let oldest_historical_epoch = current_epoch.saturating_sub(MAX_ENTRIES as u64);
82
83 if target_epoch < oldest_historical_epoch {
85 return None;
86 }
87
88 let epoch_delta = newest_historical_epoch.checked_sub(target_epoch)?;
91
92 let offset = epoch_delta
94 .checked_mul(EPOCH_AND_ENTRY_SERIALIZED_SIZE)?
95 .checked_add(std::mem::size_of::<u64>() as u64)?;
96
97 let mut entry_buf = [0; EPOCH_AND_ENTRY_SERIALIZED_SIZE as usize];
98 let result = get_sysvar(
99 &mut entry_buf,
100 &StakeHistory::id(),
101 offset,
102 EPOCH_AND_ENTRY_SERIALIZED_SIZE,
103 );
104
105 match result {
106 Ok(()) => {
107 let (entry_epoch, entry) =
108 bincode::deserialize::<(Epoch, StakeHistoryEntry)>(&entry_buf).ok()?;
109
110 assert_eq!(entry_epoch, target_epoch);
112
113 Some(entry)
114 }
115 _ => None,
116 }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use {
123 super::*,
124 crate::{stake_history::*, sysvar::tests::mock_get_sysvar_syscall},
125 serial_test::serial,
126 };
127
128 #[test]
129 fn test_size_of() {
130 let mut stake_history = StakeHistory::default();
131 for i in 0..MAX_ENTRIES as u64 {
132 stake_history.add(
133 i,
134 StakeHistoryEntry {
135 activating: i,
136 ..StakeHistoryEntry::default()
137 },
138 );
139 }
140
141 assert_eq!(
142 bincode::serialized_size(&stake_history).unwrap() as usize,
143 StakeHistory::size_of()
144 );
145
146 let stake_history_inner: Vec<(Epoch, StakeHistoryEntry)> =
147 bincode::deserialize(&bincode::serialize(&stake_history).unwrap()).unwrap();
148 let epoch_entry = stake_history_inner.into_iter().next().unwrap();
149
150 assert_eq!(
151 bincode::serialized_size(&epoch_entry).unwrap(),
152 EPOCH_AND_ENTRY_SERIALIZED_SIZE
153 );
154 }
155
156 #[serial]
157 #[test]
158 fn test_stake_history_get_entry() {
159 let unique_entry_for_epoch = |epoch: u64| StakeHistoryEntry {
160 activating: epoch.saturating_mul(2),
161 deactivating: epoch.saturating_mul(3),
162 effective: epoch.saturating_mul(5),
163 };
164
165 let current_epoch = MAX_ENTRIES.saturating_add(2) as u64;
166
167 let mut stake_history = StakeHistory::default();
169 for i in 0..current_epoch {
170 stake_history.add(i, unique_entry_for_epoch(i));
171 }
172 assert_eq!(stake_history.len(), MAX_ENTRIES);
173 assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 2);
174
175 mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
177
178 let stake_history_sysvar = StakeHistorySysvar(current_epoch);
180
181 assert_eq!(stake_history.get(0), None);
184 assert_eq!(stake_history.get(1), None);
185 assert_eq!(stake_history.get(current_epoch), None);
186
187 assert_eq!(stake_history.get_entry(0), None);
188 assert_eq!(stake_history.get_entry(1), None);
189 assert_eq!(stake_history.get_entry(current_epoch), None);
190
191 assert_eq!(stake_history_sysvar.get_entry(0), None);
192 assert_eq!(stake_history_sysvar.get_entry(1), None);
193 assert_eq!(stake_history_sysvar.get_entry(current_epoch), None);
194
195 for i in 2..current_epoch {
196 let entry = Some(unique_entry_for_epoch(i));
197
198 assert_eq!(stake_history.get(i), entry.as_ref(),);
199
200 assert_eq!(stake_history.get_entry(i), entry,);
201
202 assert_eq!(stake_history_sysvar.get_entry(i), entry,);
203 }
204 }
205
206 #[serial]
207 #[test]
208 fn test_stake_history_get_entry_zero() {
209 let mut current_epoch = 0;
210
211 let stake_history = StakeHistory::default();
213 assert_eq!(stake_history.len(), 0);
214
215 mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
216 let stake_history_sysvar = StakeHistorySysvar(current_epoch);
217
218 assert_eq!(stake_history.get(0), None);
219 assert_eq!(stake_history.get_entry(0), None);
220 assert_eq!(stake_history_sysvar.get_entry(0), None);
221
222 let entry_zero = StakeHistoryEntry {
224 effective: 100,
225 ..StakeHistoryEntry::default()
226 };
227 let entry = Some(entry_zero.clone());
228
229 let mut stake_history = StakeHistory::default();
230 stake_history.add(current_epoch, entry_zero);
231 assert_eq!(stake_history.len(), 1);
232 current_epoch = current_epoch.saturating_add(1);
233
234 mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
235 let stake_history_sysvar = StakeHistorySysvar(current_epoch);
236
237 assert_eq!(stake_history.get(0), entry.as_ref());
238 assert_eq!(stake_history.get_entry(0), entry);
239 assert_eq!(stake_history_sysvar.get_entry(0), entry);
240
241 stake_history.add(current_epoch, StakeHistoryEntry::default());
243 assert_eq!(stake_history.len(), 2);
244 current_epoch = current_epoch.saturating_add(1);
245
246 mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
247 let stake_history_sysvar = StakeHistorySysvar(current_epoch);
248
249 assert_eq!(stake_history.get(0), entry.as_ref());
250 assert_eq!(stake_history.get_entry(0), entry);
251 assert_eq!(stake_history_sysvar.get_entry(0), entry);
252 }
253}