use {
crate::stake_history::{StakeHistory, StakeHistoryEntry, StakeHistoryGetEntry, MAX_ENTRIES},
solana_clock::Epoch,
solana_sysvar::{get_sysvar, Sysvar},
solana_sysvar_id::declare_sysvar_id,
};
declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory);
impl Sysvar for StakeHistory {}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct StakeHistorySysvar(pub Epoch);
const EPOCH_AND_ENTRY_SERIALIZED_SIZE: u64 = 32;
impl StakeHistoryGetEntry for StakeHistorySysvar {
fn get_entry(&self, target_epoch: Epoch) -> Option<StakeHistoryEntry> {
let current_epoch = self.0;
let newest_historical_epoch = current_epoch.checked_sub(1)?;
let oldest_historical_epoch = current_epoch.saturating_sub(MAX_ENTRIES as u64);
if target_epoch < oldest_historical_epoch {
return None;
}
let epoch_delta = newest_historical_epoch.checked_sub(target_epoch)?;
let offset = epoch_delta
.checked_mul(EPOCH_AND_ENTRY_SERIALIZED_SIZE)?
.checked_add(std::mem::size_of::<u64>() as u64)?;
let mut entry_buf = [0; EPOCH_AND_ENTRY_SERIALIZED_SIZE as usize];
let result = get_sysvar(
&mut entry_buf,
&id(),
offset,
EPOCH_AND_ENTRY_SERIALIZED_SIZE,
);
match result {
Ok(()) => {
let entry_epoch = u64::from_le_bytes(entry_buf[0..8].try_into().unwrap());
let effective = u64::from_le_bytes(entry_buf[8..16].try_into().unwrap());
let activating = u64::from_le_bytes(entry_buf[16..24].try_into().unwrap());
let deactivating = u64::from_le_bytes(entry_buf[24..32].try_into().unwrap());
assert_eq!(entry_epoch, target_epoch);
Some(StakeHistoryEntry {
effective,
activating,
deactivating,
})
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
serial_test::serial,
solana_sysvar::program_stubs::{set_syscall_stubs, SyscallStubs},
};
struct MockGetSysvarSyscall {
data: Vec<u8>,
}
impl SyscallStubs for MockGetSysvarSyscall {
#[allow(clippy::arithmetic_side_effects)]
fn sol_get_sysvar(
&self,
_sysvar_id_addr: *const u8,
var_addr: *mut u8,
offset: u64,
length: u64,
) -> u64 {
let slice = unsafe { std::slice::from_raw_parts_mut(var_addr, length as usize) };
slice.copy_from_slice(&self.data[offset as usize..(offset + length) as usize]);
0 }
}
pub fn mock_get_sysvar_syscall(data: &[u8]) {
set_syscall_stubs(Box::new(MockGetSysvarSyscall {
data: data.to_vec(),
}));
}
#[test]
fn test_size_of() {
let mut stake_history = StakeHistory::default();
for i in 0..MAX_ENTRIES as u64 {
stake_history.add(
i,
StakeHistoryEntry {
activating: i,
..StakeHistoryEntry::default()
},
);
}
assert_eq!(
bincode::serialized_size(&stake_history).unwrap() as usize,
size_of::<u64>() + MAX_ENTRIES * EPOCH_AND_ENTRY_SERIALIZED_SIZE as usize
);
let stake_history_inner: Vec<(Epoch, StakeHistoryEntry)> =
bincode::deserialize(&bincode::serialize(&stake_history).unwrap()).unwrap();
let epoch_entry = stake_history_inner.into_iter().next().unwrap();
assert_eq!(
bincode::serialized_size(&epoch_entry).unwrap(),
EPOCH_AND_ENTRY_SERIALIZED_SIZE
);
}
#[serial]
#[test]
fn test_stake_history_get_entry() {
let unique_entry_for_epoch = |epoch: u64| StakeHistoryEntry {
activating: epoch.saturating_mul(2),
deactivating: epoch.saturating_mul(3),
effective: epoch.saturating_mul(5),
};
let current_epoch = MAX_ENTRIES.saturating_add(2) as u64;
let mut stake_history = StakeHistory::default();
for i in 0..current_epoch {
stake_history.add(i, unique_entry_for_epoch(i));
}
assert_eq!(stake_history.len(), MAX_ENTRIES);
assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 2);
mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
let stake_history_sysvar = StakeHistorySysvar(current_epoch);
assert_eq!(stake_history.get(0), None);
assert_eq!(stake_history.get(1), None);
assert_eq!(stake_history.get(current_epoch), None);
assert_eq!(stake_history.get_entry(0), None);
assert_eq!(stake_history.get_entry(1), None);
assert_eq!(stake_history.get_entry(current_epoch), None);
assert_eq!(stake_history_sysvar.get_entry(0), None);
assert_eq!(stake_history_sysvar.get_entry(1), None);
assert_eq!(stake_history_sysvar.get_entry(current_epoch), None);
for i in 2..current_epoch {
let entry = Some(unique_entry_for_epoch(i));
assert_eq!(stake_history.get(i), entry.as_ref(),);
assert_eq!(stake_history.get_entry(i), entry,);
assert_eq!(stake_history_sysvar.get_entry(i), entry,);
}
}
#[serial]
#[test]
fn test_stake_history_get_entry_zero() {
let mut current_epoch = 0;
let stake_history = StakeHistory::default();
assert_eq!(stake_history.len(), 0);
mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
let stake_history_sysvar = StakeHistorySysvar(current_epoch);
assert_eq!(stake_history.get(0), None);
assert_eq!(stake_history.get_entry(0), None);
assert_eq!(stake_history_sysvar.get_entry(0), None);
let entry_zero = StakeHistoryEntry {
effective: 100,
..StakeHistoryEntry::default()
};
let entry = Some(entry_zero.clone());
let mut stake_history = StakeHistory::default();
stake_history.add(current_epoch, entry_zero);
assert_eq!(stake_history.len(), 1);
current_epoch = current_epoch.saturating_add(1);
mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
let stake_history_sysvar = StakeHistorySysvar(current_epoch);
assert_eq!(stake_history.get(0), entry.as_ref());
assert_eq!(stake_history.get_entry(0), entry);
assert_eq!(stake_history_sysvar.get_entry(0), entry);
stake_history.add(current_epoch, StakeHistoryEntry::default());
assert_eq!(stake_history.len(), 2);
current_epoch = current_epoch.saturating_add(1);
mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
let stake_history_sysvar = StakeHistorySysvar(current_epoch);
assert_eq!(stake_history.get(0), entry.as_ref());
assert_eq!(stake_history.get_entry(0), entry);
assert_eq!(stake_history_sysvar.get_entry(0), entry);
}
}