solana_stake_interface/sysvar/
stake_history.rs1#[cfg(feature = "bincode")]
47use solana_sysvar::SysvarSerialize;
48use {
49 crate::stake_history::{StakeHistory, StakeHistoryEntry, StakeHistoryGetEntry, MAX_ENTRIES},
50 solana_clock::Epoch,
51 solana_sysvar::{get_sysvar, Sysvar},
52 solana_sysvar_id::declare_sysvar_id,
53};
54
55declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory);
56
57impl Sysvar for StakeHistory {}
58#[cfg(feature = "bincode")]
59impl SysvarSerialize for StakeHistory {
60 fn size_of() -> usize {
62 16392 }
65}
66
67#[derive(Debug, PartialEq, Eq, Clone)]
69pub struct StakeHistorySysvar(pub Epoch);
70
71const EPOCH_AND_ENTRY_SERIALIZED_SIZE: u64 = 32;
73
74impl StakeHistoryGetEntry for StakeHistorySysvar {
75 fn get_entry(&self, target_epoch: Epoch) -> Option<StakeHistoryEntry> {
76 let current_epoch = self.0;
77
78 let newest_historical_epoch = current_epoch.checked_sub(1)?;
80 let oldest_historical_epoch = current_epoch.saturating_sub(MAX_ENTRIES as u64);
81
82 if target_epoch < oldest_historical_epoch {
84 return None;
85 }
86
87 let epoch_delta = newest_historical_epoch.checked_sub(target_epoch)?;
90
91 let offset = epoch_delta
93 .checked_mul(EPOCH_AND_ENTRY_SERIALIZED_SIZE)?
94 .checked_add(std::mem::size_of::<u64>() as u64)?;
95
96 let mut entry_buf = [0; EPOCH_AND_ENTRY_SERIALIZED_SIZE as usize];
97 let result = get_sysvar(
98 &mut entry_buf,
99 &id(),
100 offset,
101 EPOCH_AND_ENTRY_SERIALIZED_SIZE,
102 );
103
104 match result {
105 Ok(()) => {
106 let entry_epoch = u64::from_le_bytes(entry_buf[0..8].try_into().unwrap());
108 let effective = u64::from_le_bytes(entry_buf[8..16].try_into().unwrap());
109 let activating = u64::from_le_bytes(entry_buf[16..24].try_into().unwrap());
110 let deactivating = u64::from_le_bytes(entry_buf[24..32].try_into().unwrap());
111
112 assert_eq!(entry_epoch, target_epoch);
114
115 Some(StakeHistoryEntry {
116 effective,
117 activating,
118 deactivating,
119 })
120 }
121 _ => None,
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use {
129 super::*,
130 serial_test::serial,
131 solana_sysvar::program_stubs::{set_syscall_stubs, SyscallStubs},
132 };
133
134 struct MockGetSysvarSyscall {
136 data: Vec<u8>,
137 }
138 impl SyscallStubs for MockGetSysvarSyscall {
139 #[allow(clippy::arithmetic_side_effects)]
140 fn sol_get_sysvar(
141 &self,
142 _sysvar_id_addr: *const u8,
143 var_addr: *mut u8,
144 offset: u64,
145 length: u64,
146 ) -> u64 {
147 let slice = unsafe { std::slice::from_raw_parts_mut(var_addr, length as usize) };
148 slice.copy_from_slice(&self.data[offset as usize..(offset + length) as usize]);
149 0 }
151 }
152 pub fn mock_get_sysvar_syscall(data: &[u8]) {
153 set_syscall_stubs(Box::new(MockGetSysvarSyscall {
154 data: data.to_vec(),
155 }));
156 }
157
158 #[test]
159 fn test_size_of() {
160 let mut stake_history = StakeHistory::default();
161 for i in 0..MAX_ENTRIES as u64 {
162 stake_history.add(
163 i,
164 StakeHistoryEntry {
165 activating: i,
166 ..StakeHistoryEntry::default()
167 },
168 );
169 }
170
171 assert_eq!(
172 bincode::serialized_size(&stake_history).unwrap() as usize,
173 StakeHistory::size_of()
174 );
175
176 let stake_history_inner: Vec<(Epoch, StakeHistoryEntry)> =
177 bincode::deserialize(&bincode::serialize(&stake_history).unwrap()).unwrap();
178 let epoch_entry = stake_history_inner.into_iter().next().unwrap();
179
180 assert_eq!(
181 bincode::serialized_size(&epoch_entry).unwrap(),
182 EPOCH_AND_ENTRY_SERIALIZED_SIZE
183 );
184 }
185
186 #[serial]
187 #[test]
188 fn test_stake_history_get_entry() {
189 let unique_entry_for_epoch = |epoch: u64| StakeHistoryEntry {
190 activating: epoch.saturating_mul(2),
191 deactivating: epoch.saturating_mul(3),
192 effective: epoch.saturating_mul(5),
193 };
194
195 let current_epoch = MAX_ENTRIES.saturating_add(2) as u64;
196
197 let mut stake_history = StakeHistory::default();
199 for i in 0..current_epoch {
200 stake_history.add(i, unique_entry_for_epoch(i));
201 }
202 assert_eq!(stake_history.len(), MAX_ENTRIES);
203 assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 2);
204
205 mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
207
208 let stake_history_sysvar = StakeHistorySysvar(current_epoch);
210
211 assert_eq!(stake_history.get(0), None);
214 assert_eq!(stake_history.get(1), None);
215 assert_eq!(stake_history.get(current_epoch), None);
216
217 assert_eq!(stake_history.get_entry(0), None);
218 assert_eq!(stake_history.get_entry(1), None);
219 assert_eq!(stake_history.get_entry(current_epoch), None);
220
221 assert_eq!(stake_history_sysvar.get_entry(0), None);
222 assert_eq!(stake_history_sysvar.get_entry(1), None);
223 assert_eq!(stake_history_sysvar.get_entry(current_epoch), None);
224
225 for i in 2..current_epoch {
226 let entry = Some(unique_entry_for_epoch(i));
227
228 assert_eq!(stake_history.get(i), entry.as_ref(),);
229
230 assert_eq!(stake_history.get_entry(i), entry,);
231
232 assert_eq!(stake_history_sysvar.get_entry(i), entry,);
233 }
234 }
235
236 #[serial]
237 #[test]
238 fn test_stake_history_get_entry_zero() {
239 let mut current_epoch = 0;
240
241 let stake_history = StakeHistory::default();
243 assert_eq!(stake_history.len(), 0);
244
245 mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
246 let stake_history_sysvar = StakeHistorySysvar(current_epoch);
247
248 assert_eq!(stake_history.get(0), None);
249 assert_eq!(stake_history.get_entry(0), None);
250 assert_eq!(stake_history_sysvar.get_entry(0), None);
251
252 let entry_zero = StakeHistoryEntry {
254 effective: 100,
255 ..StakeHistoryEntry::default()
256 };
257 let entry = Some(entry_zero.clone());
258
259 let mut stake_history = StakeHistory::default();
260 stake_history.add(current_epoch, entry_zero);
261 assert_eq!(stake_history.len(), 1);
262 current_epoch = current_epoch.saturating_add(1);
263
264 mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
265 let stake_history_sysvar = StakeHistorySysvar(current_epoch);
266
267 assert_eq!(stake_history.get(0), entry.as_ref());
268 assert_eq!(stake_history.get_entry(0), entry);
269 assert_eq!(stake_history_sysvar.get_entry(0), entry);
270
271 stake_history.add(current_epoch, StakeHistoryEntry::default());
273 assert_eq!(stake_history.len(), 2);
274 current_epoch = current_epoch.saturating_add(1);
275
276 mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
277 let stake_history_sysvar = StakeHistorySysvar(current_epoch);
278
279 assert_eq!(stake_history.get(0), entry.as_ref());
280 assert_eq!(stake_history.get_entry(0), entry);
281 assert_eq!(stake_history_sysvar.get_entry(0), entry);
282 }
283}