pub use crate::slot_hashes::SlotHashes;
use {
crate::{
account_info::AccountInfo,
clock::Slot,
hash::Hash,
program_error::ProgramError,
slot_hashes::MAX_ENTRIES,
sysvar::{get_sysvar, Sysvar, SysvarId},
},
bytemuck_derive::{Pod, Zeroable},
};
crate::declare_sysvar_id!("SysvarS1otHashes111111111111111111111111111", SlotHashes);
impl Sysvar for SlotHashes {
fn size_of() -> usize {
20_488 }
fn from_account_info(_account_info: &AccountInfo) -> Result<Self, ProgramError> {
Err(ProgramError::UnsupportedSysvar)
}
}
#[derive(Copy, Clone, Default, Pod, Zeroable)]
#[repr(C)]
struct PodSlotHash {
slot: Slot,
hash: Hash,
}
pub struct SlotHashesSysvar;
impl SlotHashesSysvar {
pub fn get(slot: &Slot) -> Result<Option<Hash>, ProgramError> {
get_pod_slot_hashes().map(|pod_hashes| {
pod_hashes
.binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
.map(|idx| pod_hashes[idx].hash)
.ok()
})
}
pub fn position(slot: &Slot) -> Result<Option<usize>, ProgramError> {
get_pod_slot_hashes().map(|pod_hashes| {
pod_hashes
.binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
.ok()
})
}
}
fn get_pod_slot_hashes() -> Result<Vec<PodSlotHash>, ProgramError> {
let mut pod_hashes = vec![PodSlotHash::default(); MAX_ENTRIES];
{
let data = bytemuck::try_cast_slice_mut::<PodSlotHash, u8>(&mut pod_hashes)
.map_err(|_| ProgramError::InvalidAccountData)?;
if data.as_ptr().align_offset(8) != 0 {
return Err(ProgramError::InvalidAccountData);
}
let offset = 8; let length = (SlotHashes::size_of() as u64).saturating_sub(offset);
get_sysvar(data, &SlotHashes::id(), offset, length)?;
}
Ok(pod_hashes)
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
clock::Slot,
entrypoint::SUCCESS,
hash::{hash, Hash},
program_stubs::{set_syscall_stubs, SyscallStubs},
slot_hashes::{SlotHash, MAX_ENTRIES},
},
};
struct MockSlotHashesSyscall {
slot_hashes: SlotHashes,
}
impl SyscallStubs for MockSlotHashesSyscall {
#[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 data = bincode::serialize(&self.slot_hashes).unwrap();
let slice = unsafe { std::slice::from_raw_parts_mut(var_addr, length as usize) };
slice.copy_from_slice(&data[offset as usize..(offset + length) as usize]);
SUCCESS
}
}
fn mock_get_sysvar_syscall(slot_hashes: &[SlotHash]) {
static ONCE: std::sync::Once = std::sync::Once::new();
ONCE.call_once(|| {
set_syscall_stubs(Box::new(MockSlotHashesSyscall {
slot_hashes: SlotHashes::new(slot_hashes),
}));
});
}
#[test]
fn test_size_of() {
assert_eq!(
SlotHashes::size_of(),
bincode::serialized_size(
&(0..MAX_ENTRIES)
.map(|slot| (slot as Slot, Hash::default()))
.collect::<SlotHashes>()
)
.unwrap() as usize
);
}
#[test]
fn test_slot_hashes_sysvar() {
let mut slot_hashes = vec![];
for i in 0..MAX_ENTRIES {
slot_hashes.push((
i as u64,
hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
));
}
mock_get_sysvar_syscall(&slot_hashes);
let check_slot_hashes = SlotHashes::new(&slot_hashes);
assert_eq!(
SlotHashesSysvar::get(&0).unwrap().as_ref(),
check_slot_hashes.get(&0),
);
assert_eq!(
SlotHashesSysvar::get(&256).unwrap().as_ref(),
check_slot_hashes.get(&256),
);
assert_eq!(
SlotHashesSysvar::get(&511).unwrap().as_ref(),
check_slot_hashes.get(&511),
);
assert_eq!(
SlotHashesSysvar::get(&600).unwrap().as_ref(),
check_slot_hashes.get(&600),
);
assert_eq!(
SlotHashesSysvar::position(&0).unwrap(),
check_slot_hashes.position(&0),
);
assert_eq!(
SlotHashesSysvar::position(&256).unwrap(),
check_slot_hashes.position(&256),
);
assert_eq!(
SlotHashesSysvar::position(&511).unwrap(),
check_slot_hashes.position(&511),
);
assert_eq!(
SlotHashesSysvar::position(&600).unwrap(),
check_slot_hashes.position(&600),
);
}
}