pub mod raw;
#[doc(inline)]
pub use raw::{fetch_into, fetch_into_unchecked, validate_fetch_offset};
#[cfg(test)]
mod test;
#[cfg(test)]
mod test_edge;
#[cfg(test)]
mod test_raw;
#[cfg(test)]
mod test_utils;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
use {
crate::{
account::{AccountView, Ref},
error::ProgramError,
hint::unlikely,
sysvars::clock::Slot,
Address,
},
core::{mem, ops::Deref, slice::from_raw_parts},
};
pub const SLOTHASHES_ID: Address = Address::new_from_array([
6, 167, 213, 23, 25, 47, 10, 175, 198, 242, 101, 227, 251, 119, 204, 122, 218, 130, 197, 41,
208, 190, 59, 19, 110, 45, 0, 85, 32, 0, 0, 0,
]);
pub const HASH_BYTES: usize = 32;
pub const NUM_ENTRIES_SIZE: usize = mem::size_of::<u64>();
pub const SLOT_SIZE: usize = mem::size_of::<Slot>();
pub const ENTRY_SIZE: usize = SLOT_SIZE + HASH_BYTES;
pub const MAX_ENTRIES: usize = 512;
pub const MAX_SIZE: usize = NUM_ENTRIES_SIZE + MAX_ENTRIES * ENTRY_SIZE;
pub type Hash = [u8; HASH_BYTES];
#[cfg_attr(feature = "copy", derive(Copy))]
#[derive(Clone, Eq, Debug, PartialEq)]
#[repr(C)]
pub struct SlotHashEntry {
slot_le: [u8; 8],
pub hash: Hash,
}
const _: [(); 1] = [(); mem::align_of::<SlotHashEntry>()];
#[derive(Debug)]
pub struct SlotHashes<T: Deref<Target = [u8]>> {
data: T,
}
pub fn log(hash: &Hash) {
#[cfg(any(target_os = "solana", target_arch = "bpf"))]
unsafe {
solana_address::syscalls::sol_log_pubkey(hash.as_ptr());
}
#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
core::hint::black_box(hash);
}
#[inline(always)]
pub(crate) unsafe fn get_entry_count(data: &[u8]) -> usize {
debug_assert!(data.len() >= NUM_ENTRIES_SIZE);
u64::from_le_bytes(*(data.as_ptr() as *const [u8; NUM_ENTRIES_SIZE])) as usize
}
impl SlotHashEntry {
#[inline(always)]
pub fn slot(&self) -> Slot {
u64::from_le_bytes(self.slot_le)
}
}
impl<T: Deref<Target = [u8]>> SlotHashes<T> {
#[inline(always)]
pub fn new(data: T) -> Result<Self, ProgramError> {
if data.len() < NUM_ENTRIES_SIZE {
return Err(ProgramError::AccountDataTooSmall);
}
let num_entries = unsafe { get_entry_count(data.as_ref()) };
if num_entries > MAX_ENTRIES {
return Err(ProgramError::InvalidArgument);
}
let required_size = NUM_ENTRIES_SIZE + num_entries * ENTRY_SIZE;
if data.len() < required_size {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(unsafe { Self::new_unchecked(data) })
}
#[inline(always)]
pub unsafe fn new_unchecked(data: T) -> Self {
SlotHashes { data }
}
#[inline(always)]
pub fn len(&self) -> usize {
unsafe { get_entry_count(self.data.as_ref()) }
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline(always)]
pub fn entries(&self) -> &[SlotHashEntry] {
unsafe {
from_raw_parts(
self.data.as_ptr().add(NUM_ENTRIES_SIZE) as *const SlotHashEntry,
self.len(),
)
}
}
#[inline(always)]
pub fn get_entry(&self, index: usize) -> Option<&SlotHashEntry> {
if index >= self.len() {
return None;
}
Some(unsafe { self.get_entry_unchecked(index) })
}
#[inline(always)]
pub fn get_hash(&self, target_slot: Slot) -> Option<&Hash> {
self.position(target_slot)
.map(|index| unsafe { &self.get_entry_unchecked(index).hash })
}
#[inline(always)]
pub fn position(&self, target_slot: Slot) -> Option<usize> {
self.entries()
.binary_search_by(|probe_entry| probe_entry.slot().cmp(&target_slot).reverse())
.ok()
}
#[inline(always)]
pub unsafe fn get_entry_unchecked(&self, index: usize) -> &SlotHashEntry {
debug_assert!(index < self.len());
let entries_ptr = self.data.as_ptr().add(NUM_ENTRIES_SIZE) as *const SlotHashEntry;
&*entries_ptr.add(index)
}
}
impl<'a, T: Deref<Target = [u8]>> IntoIterator for &'a SlotHashes<T> {
type Item = &'a SlotHashEntry;
type IntoIter = core::slice::Iter<'a, SlotHashEntry>;
fn into_iter(self) -> Self::IntoIter {
self.entries().iter()
}
}
impl<'a> SlotHashes<Ref<'a, [u8]>> {
#[inline(always)]
pub fn from_account_view(account_view: &'a AccountView) -> Result<Self, ProgramError> {
if unlikely(account_view.address() != &SLOTHASHES_ID) {
return Err(ProgramError::InvalidArgument);
}
let sysvar_data = account_view.try_borrow()?;
Ok(unsafe { SlotHashes::new_unchecked(sysvar_data) })
}
}
#[cfg(feature = "alloc")]
impl SlotHashes<Box<[u8]>> {
#[inline(always)]
pub fn fetch() -> Result<Self, ProgramError> {
let mut sysvar_data = Box::<[u8]>::new_uninit_slice(MAX_SIZE);
unsafe {
#[cfg(any(target_os = "solana", target_arch = "bpf"))]
crate::sysvars::get_sysvar_unchecked(
sysvar_data.as_mut_ptr() as *mut _,
&SLOTHASHES_ID,
0,
MAX_SIZE,
)?;
#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
core::ptr::write_bytes(sysvar_data.as_mut_ptr(), 0, MAX_SIZE);
}
Ok(unsafe { SlotHashes::new_unchecked(sysvar_data.assume_init()) })
}
}