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