Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDK: Slot Hashes: Add queries using sol_get_sysvar #1622

Merged
merged 10 commits into from
Jun 12, 2024
34 changes: 34 additions & 0 deletions sdk/program/src/sysvar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,40 @@ macro_rules! impl_sysvar_get {
};
}

/// Handler for retrieving a slice of sysvar data from the `sol_get_sysvar`
/// syscall.
fn get_sysvar(
dst: &mut [u8],
sysvar_id: &Pubkey,
offset: u64,
length: u64,
) -> Result<(), ProgramError> {
// Check that the provided destination buffer is large enough to hold the
// requested data.
if dst.len() < length as usize {
return Err(ProgramError::InvalidArgument);
}

let sysvar_id = sysvar_id as *const _ as *const u8;
let var_addr = dst as *mut _ as *mut u8;

// Check that the provided destination buffer is aligned to 8.
if var_addr.align_offset(8) != 0 {
return Err(ProgramError::InvalidArgument);
}
buffalojoec marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(target_os = "solana")]
let result = unsafe { crate::syscalls::sol_get_sysvar(sysvar_id, var_addr, offset, length) };

#[cfg(not(target_os = "solana"))]
let result = crate::program_stubs::sol_get_sysvar(sysvar_id, var_addr, offset, length);

match result {
crate::entrypoint::SUCCESS => Ok(()),
e => Err(e.into()),
}
}

#[cfg(test)]
mod tests {
use {
Expand Down
155 changes: 153 additions & 2 deletions sdk/program/src/sysvar/slot_hashes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,16 @@
//! ```

pub use crate::slot_hashes::SlotHashes;
use crate::{account_info::AccountInfo, program_error::ProgramError, sysvar::Sysvar};
use {
crate::{
account_info::AccountInfo,
clock::Slot,
hash::Hash,
program_error::ProgramError,
sysvar::{get_sysvar, Sysvar, SysvarId},
},
bytemuck::{Pod, Zeroable},
};

crate::declare_sysvar_id!("SysvarS1otHashes111111111111111111111111111", SlotHashes);

Expand All @@ -62,13 +71,96 @@ impl Sysvar for SlotHashes {
}
}

#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
struct PodSlotHash {
slot: Slot,
hash: Hash,
}

/// Trait for querying the `SlotHashes` sysvar.
pub trait SlotHashesSysvar {
buffalojoec marked this conversation as resolved.
Show resolved Hide resolved
/// Get a value from the sysvar entries by its key.
/// Returns `None` if the key is not found.
fn get(slot: &Slot) -> Result<Option<Hash>, ProgramError> {
let data_len = SlotHashes::size_of();
let mut data = vec![0u8; data_len];
buffalojoec marked this conversation as resolved.
Show resolved Hide resolved
get_sysvar(&mut data, &SlotHashes::id(), 0, data_len as u64)?;
let pod_hashes: &[PodSlotHash] =
bytemuck::try_cast_slice(&data[8..]).map_err(|_| ProgramError::InvalidAccountData)?;

Ok(pod_hashes
.binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
.map(|idx| pod_hashes[idx].hash)
.ok())
}

/// Get the position of an entry in the sysvar by its key.
/// Returns `None` if the key is not found.
fn position(slot: &Slot) -> Result<Option<usize>, ProgramError> {
let data_len = SlotHashes::size_of();
let mut data = vec![0u8; data_len];
get_sysvar(&mut data, &SlotHashes::id(), 0, data_len as u64)?;
let pod_hashes: &[PodSlotHash] =
bytemuck::try_cast_slice(&data[8..]).map_err(|_| ProgramError::InvalidAccountData)?;

Ok(pod_hashes
.binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
.ok())
}
}

impl SlotHashesSysvar for SlotHashes {}

#[cfg(test)]
mod tests {
use {
super::*,
crate::{clock::Slot, hash::Hash, slot_hashes::MAX_ENTRIES},
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 {
// The syscall tests for `sol_get_sysvar` should ensure the following:
//
// - The provided `sysvar_id_addr` can be translated into a valid
// sysvar ID for a sysvar contained in the sysvar cache, of which
// `SlotHashes` is one.
// - Length and memory checks on `offset` and `length`.
//
// Therefore this mockup can simply just unsafely use the provided
// `offset` and `length` to copy the serialized `SlotHashes` into
// the provided `var_addr`.
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]) {
set_syscall_stubs(Box::new(MockSlotHashesSyscall {
slot_hashes: SlotHashes::new(slot_hashes),
}));
}
buffalojoec marked this conversation as resolved.
Show resolved Hide resolved

#[test]
fn test_size_of() {
assert_eq!(
Expand All @@ -81,4 +173,63 @@ mod tests {
.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);

// `get`:
assert_eq!(
<SlotHashes as SlotHashesSysvar>::get(&0).unwrap().as_ref(),
check_slot_hashes.get(&0),
);
assert_eq!(
<SlotHashes as SlotHashesSysvar>::get(&256)
.unwrap()
.as_ref(),
check_slot_hashes.get(&256),
);
assert_eq!(
<SlotHashes as SlotHashesSysvar>::get(&511)
.unwrap()
.as_ref(),
check_slot_hashes.get(&511),
);
// `None`.
assert_eq!(
<SlotHashes as SlotHashesSysvar>::get(&600)
.unwrap()
.as_ref(),
check_slot_hashes.get(&600),
);

// `position`:
assert_eq!(
<SlotHashes as SlotHashesSysvar>::position(&0).unwrap(),
check_slot_hashes.position(&0),
);
assert_eq!(
<SlotHashes as SlotHashesSysvar>::position(&256).unwrap(),
check_slot_hashes.position(&256),
);
assert_eq!(
<SlotHashes as SlotHashesSysvar>::position(&511).unwrap(),
check_slot_hashes.position(&511),
);
// `None`.
assert_eq!(
<SlotHashes as SlotHashesSysvar>::position(&600).unwrap(),
check_slot_hashes.position(&600),
);
}
}