Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2d0d5e6
safe sol_get_sysvar from packed reprs
rustopian Nov 25, 2025
08936c4
use decorators instead of macro, more compile-time checks
rustopian Nov 25, 2025
70cae6a
clean up to_bytes
rustopian Nov 25, 2025
732d1e8
tests: rm redundant, clean up bincode gating
rustopian Nov 25, 2025
07e5fb1
rm test util duplication
rustopian Nov 25, 2025
a359358
rm from_raw_parts_mut ub
rustopian Nov 25, 2025
6070ca0
readd intentionally redundant arm
rustopian Nov 25, 2025
201ffb1
rework sysvars to byte arrays and accessors
rustopian Nov 26, 2025
ff3c1f9
rm outdated comment lines
rustopian Nov 26, 2025
7ef14b7
custom rent, epoch sysvar pods
rustopian Dec 2, 2025
c438a3e
doc comments, misc
rustopian Dec 2, 2025
17b02fc
safety comments
rustopian Dec 2, 2025
d3e51c9
rm now unneeded Rent::new()
rustopian Dec 2, 2025
4b95d4d
use common and size constants
rustopian Dec 2, 2025
c52a6bf
rm unnecessary pod for EpochRewards, use pad fill approach
rustopian Dec 2, 2025
0baf904
in-line now unnecessary helper
rustopian Dec 2, 2025
46fa5b1
doc comment
rustopian Dec 2, 2025
a6e7297
impl_sysvar_get takes optional padding
rustopian Dec 2, 2025
2ce5492
safety comments
rustopian Dec 2, 2025
f869967
fold in sysvar errors as previously
rustopian Dec 2, 2025
f5af464
valid bool values only
rustopian Dec 3, 2025
b36f96e
specifier, fmt
rustopian Dec 3, 2025
e8ab6ac
use bool directly, safety comments
rustopian Dec 5, 2025
190e542
allow depr in test
rustopian Dec 5, 2025
372c65b
now unneeded TryFrom -> From
rustopian Dec 5, 2025
4f49270
rm now unused fetch_pod
rustopian Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions define-syscall/src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ define_syscall!(fn sol_get_return_data(data: *mut u8, length: u64, program_id: *
// - `is_writable` (`u8`): `true` if the instruction requires the account to be writable
define_syscall!(fn sol_get_processed_sibling_instruction(index: u64, meta: *mut u8, program_id: *mut u8, data: *mut u8, accounts: *mut u8) -> u64);

// these are to be deprecated once they are superceded by sol_get_sysvar
define_syscall!(fn sol_get_clock_sysvar(addr: *mut u8) -> u64);
define_syscall!(fn sol_get_epoch_schedule_sysvar(addr: *mut u8) -> u64);
define_syscall!(fn sol_get_rent_sysvar(addr: *mut u8) -> u64);
define_syscall!(fn sol_get_last_restart_slot(addr: *mut u8) -> u64);
define_syscall!(fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64);
// these are deprecated - use sol_get_sysvar instead
define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `Clock` sysvar address instead")] fn sol_get_clock_sysvar(addr: *mut u8) -> u64);
define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `EpochSchedule` sysvar address instead")] fn sol_get_epoch_schedule_sysvar(addr: *mut u8) -> u64);
define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `Rent` sysvar address instead")] fn sol_get_rent_sysvar(addr: *mut u8) -> u64);
define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `LastRestartSlot` sysvar address instead")] fn sol_get_last_restart_slot(addr: *mut u8) -> u64);
define_syscall!(#[deprecated(since = "4.1.0", note = "Use `sol_get_sysvar` with `EpochRewards` sysvar address instead")] fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64);

// this cannot go through sol_get_sysvar but can be removed once no longer in use
define_syscall!(fn sol_get_fees_sysvar(addr: *mut u8) -> u64);
14 changes: 8 additions & 6 deletions define-syscall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ pub mod definitions;
))]
#[macro_export]
macro_rules! define_syscall {
(fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enabling deprecated to be passed through (as in the original sol_get_sysvar PR)

$(#[$attr])*
#[inline]
pub unsafe fn $name($($arg: $typ),*) -> $ret {
// this enum is used to force the hash to be computed in a const context
Expand All @@ -23,8 +24,8 @@ macro_rules! define_syscall {
}

};
(fn $name:ident($($arg:ident: $typ:ty),*)) => {
define_syscall!(fn $name($($arg: $typ),*) -> ());
($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*)) => {
define_syscall!($(#[$attr])* fn $name($($arg: $typ),*) -> ());
}
}

Expand All @@ -34,13 +35,14 @@ macro_rules! define_syscall {
)))]
#[macro_export]
macro_rules! define_syscall {
(fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => {
extern "C" {
$(#[$attr])*
pub fn $name($($arg: $typ),*) -> $ret;
}
};
(fn $name:ident($($arg:ident: $typ:ty),*)) => {
define_syscall!(fn $name($($arg: $typ),*) -> ());
($(#[$attr:meta])* fn $name:ident($($arg:ident: $typ:ty),*)) => {
define_syscall!($(#[$attr])* fn $name($($arg: $typ),*) -> ());
}
}

Expand Down
58 changes: 57 additions & 1 deletion sysvar/src/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,64 @@ pub use {
};

impl Sysvar for Clock {
impl_sysvar_get!(sol_get_clock_sysvar);
impl_sysvar_get!(id());
}

#[cfg(feature = "bincode")]
impl SysvarSerialize for Clock {}

#[cfg(test)]
mod tests {
use {super::*, crate::tests::to_bytes, serial_test::serial};

#[test]
#[cfg(feature = "bincode")]
fn test_clock_size_matches_bincode() {
// Prove that Clock's in-memory layout matches its bincode serialization.
let clock = Clock::default();
let in_memory_size = core::mem::size_of::<Clock>();
let bincode_size = bincode::serialized_size(&clock).unwrap() as usize;

assert_eq!(
in_memory_size, bincode_size,
"Clock in-memory size ({in_memory_size}) must match bincode size ({bincode_size})",
);
}

#[test]
#[serial]
fn test_clock_get_uses_sysvar_syscall() {
let expected = Clock {
slot: 1,
epoch_start_timestamp: 2,
epoch: 3,
leader_schedule_epoch: 4,
unix_timestamp: 5,
};

let data = to_bytes(&expected);
crate::tests::mock_get_sysvar_syscall(&data);

let got = Clock::get().unwrap();
assert_eq!(got, expected);
}

#[test]
#[serial]
fn test_clock_get_passes_correct_sysvar_id() {
let expected = Clock {
slot: 11,
epoch_start_timestamp: 22,
epoch: 33,
leader_schedule_epoch: 44,
unix_timestamp: 55,
};
let data = to_bytes(&expected);
let prev = crate::tests::mock_get_sysvar_syscall_with_id(&data, &id());

let got = Clock::get().unwrap();
assert_eq!(got, expected);

let _ = crate::program_stubs::set_syscall_stubs(prev);
}
}
29 changes: 28 additions & 1 deletion sysvar/src/epoch_rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,35 @@ pub use {
};

impl Sysvar for EpochRewards {
impl_sysvar_get!(sol_get_epoch_rewards_sysvar);
impl_sysvar_get!(id(), 15);
}

#[cfg(feature = "bincode")]
impl SysvarSerialize for EpochRewards {}

#[cfg(test)]
mod tests {
use {super::*, crate::Sysvar, serial_test::serial};

#[test]
#[serial]
#[cfg(feature = "bincode")]
fn test_epoch_rewards_get() {
let expected = EpochRewards {
distribution_starting_block_height: 42,
num_partitions: 7,
parent_blockhash: solana_hash::Hash::new_unique(),
total_points: 1234567890,
total_rewards: 100,
distributed_rewards: 10,
active: true,
};

let data = bincode::serialize(&expected).unwrap();
assert_eq!(data.len(), 81);

crate::tests::mock_get_sysvar_syscall(&data);
let got = EpochRewards::get().unwrap();
assert_eq!(got, expected);
}
}
111 changes: 109 additions & 2 deletions sysvar/src/epoch_schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,124 @@
//! #
//! # Ok::<(), anyhow::Error>(())
//! ```
use crate::Sysvar;
#[cfg(feature = "bincode")]
use crate::SysvarSerialize;
use crate::{impl_sysvar_get, Sysvar};
pub use {
solana_epoch_schedule::EpochSchedule,
solana_sdk_ids::sysvar::epoch_schedule::{check_id, id, ID},
};

/// Pod (Plain Old Data) representation of [`EpochSchedule`] with no padding.
///
/// This type can be safely loaded via `sol_get_sysvar` without undefined behavior.
/// Provides performant zero-copy accessors as an alternative to the `EpochSchedule` type.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PodEpochSchedule {
slots_per_epoch: [u8; 8],
leader_schedule_slot_offset: [u8; 8],
warmup: u8,
first_normal_epoch: [u8; 8],
first_normal_slot: [u8; 8],
}

const POD_EPOCH_SCHEDULE_SIZE: usize = 33;
const _: () = assert!(core::mem::size_of::<PodEpochSchedule>() == POD_EPOCH_SCHEDULE_SIZE);

impl PodEpochSchedule {
/// Fetch the sysvar data using the `sol_get_sysvar` syscall.
/// This provides an alternative to `EpochSchedule` which provides zero-copy accessors.
pub fn fetch() -> Result<Self, solana_program_error::ProgramError> {
let mut pod = core::mem::MaybeUninit::<Self>::uninit();
// Safety: `get_sysvar_unchecked` will initialize `pod` with the sysvar data,
// and error if unsuccessful.
unsafe {
crate::get_sysvar_unchecked(
pod.as_mut_ptr() as *mut u8,
(&id()) as *const _ as *const u8,
0,
POD_EPOCH_SCHEDULE_SIZE as u64,
)?;
Ok(pod.assume_init())
}
}

pub fn slots_per_epoch(&self) -> u64 {
u64::from_le_bytes(self.slots_per_epoch)
}

pub fn leader_schedule_slot_offset(&self) -> u64 {
u64::from_le_bytes(self.leader_schedule_slot_offset)
}

pub fn warmup(&self) -> bool {
self.warmup != 0
}

pub fn first_normal_epoch(&self) -> u64 {
u64::from_le_bytes(self.first_normal_epoch)
}

pub fn first_normal_slot(&self) -> u64 {
u64::from_le_bytes(self.first_normal_slot)
}
}

impl From<PodEpochSchedule> for EpochSchedule {
fn from(pod: PodEpochSchedule) -> Self {
Self {
slots_per_epoch: pod.slots_per_epoch(),
leader_schedule_slot_offset: pod.leader_schedule_slot_offset(),
warmup: pod.warmup(),
first_normal_epoch: pod.first_normal_epoch(),
first_normal_slot: pod.first_normal_slot(),
}
}
}

impl Sysvar for EpochSchedule {
impl_sysvar_get!(sol_get_epoch_schedule_sysvar);
fn get() -> Result<Self, solana_program_error::ProgramError> {
Ok(Self::from(PodEpochSchedule::fetch()?))
}
}

#[cfg(feature = "bincode")]
impl SysvarSerialize for EpochSchedule {}

#[cfg(test)]
mod tests {
use {super::*, crate::Sysvar, serial_test::serial};

#[test]
fn test_pod_epoch_schedule_conversion() {
let pod = PodEpochSchedule {
slots_per_epoch: 432000u64.to_le_bytes(),
leader_schedule_slot_offset: 432000u64.to_le_bytes(),
warmup: 1,
first_normal_epoch: 14u64.to_le_bytes(),
first_normal_slot: 524256u64.to_le_bytes(),
};

let epoch_schedule = EpochSchedule::from(pod);

assert_eq!(epoch_schedule.slots_per_epoch, 432000);
assert_eq!(epoch_schedule.leader_schedule_slot_offset, 432000);
assert!(epoch_schedule.warmup);
assert_eq!(epoch_schedule.first_normal_epoch, 14);
assert_eq!(epoch_schedule.first_normal_slot, 524256);
}

#[test]
#[serial]
#[cfg(feature = "bincode")]
fn test_epoch_schedule_get() {
let expected = EpochSchedule::custom(1234, 5678, false);
let data = bincode::serialize(&expected).unwrap();
assert_eq!(data.len(), 33);

crate::tests::mock_get_sysvar_syscall(&data);
let got = EpochSchedule::get().unwrap();
assert_eq!(got, expected);
}
}
48 changes: 47 additions & 1 deletion sysvar/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl SysvarSerialize for Fees {}

#[cfg(test)]
mod tests {
use super::*;
use {super::*, serial_test::serial};

#[test]
fn test_clone() {
Expand All @@ -76,4 +76,50 @@ mod tests {
let cloned_fees = fees.clone();
assert_eq!(cloned_fees, fees);
}

struct MockFeesSyscall;
impl crate::program_stubs::SyscallStubs for MockFeesSyscall {
fn sol_get_fees_sysvar(&self, var_addr: *mut u8) -> u64 {
let fees = Fees {
fee_calculator: FeeCalculator {
lamports_per_signature: 42,
},
};
unsafe {
std::ptr::copy_nonoverlapping(
&fees as *const _ as *const u8,
var_addr,
core::mem::size_of::<Fees>(),
);
}
solana_program_entrypoint::SUCCESS
}
}

#[test]
#[serial]
fn test_fees_get_deprecated_syscall_path() {
let _ = crate::program_stubs::set_syscall_stubs(Box::new(MockFeesSyscall));
let got = Fees::get().unwrap();
assert_eq!(got.fee_calculator.lamports_per_signature, 42);
}

struct FailFeesSyscall;
impl crate::program_stubs::SyscallStubs for FailFeesSyscall {
fn sol_get_fees_sysvar(&self, _var_addr: *mut u8) -> u64 {
9999
}
}

#[test]
#[serial]
fn test_fees_get_deprecated_non_success_maps_to_unsupported() {
let prev = crate::program_stubs::set_syscall_stubs(Box::new(FailFeesSyscall));
let got = Fees::get();
assert_eq!(
got,
Err(solana_program_error::ProgramError::UnsupportedSysvar)
);
let _ = crate::program_stubs::set_syscall_stubs(prev);
}
}
34 changes: 33 additions & 1 deletion sysvar/src/last_restart_slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,40 @@ pub use {
};

impl Sysvar for LastRestartSlot {
impl_sysvar_get!(sol_get_last_restart_slot);
impl_sysvar_get!(id());
}

#[cfg(feature = "bincode")]
impl SysvarSerialize for LastRestartSlot {}

#[cfg(test)]
mod tests {
use {super::*, crate::tests::to_bytes, serial_test::serial};

#[test]
#[cfg(feature = "bincode")]
fn test_last_restart_slot_size_matches_bincode() {
// Prove that LastRestartSlot's in-memory layout matches its bincode serialization.
let slot = LastRestartSlot::default();
let in_memory_size = core::mem::size_of::<LastRestartSlot>();
let bincode_size = bincode::serialized_size(&slot).unwrap() as usize;

assert_eq!(
in_memory_size, bincode_size,
"LastRestartSlot in-memory size ({in_memory_size}) must match bincode size ({bincode_size})",
);
}

#[test]
#[serial]
fn test_last_restart_slot_get_uses_sysvar_syscall() {
let expected = LastRestartSlot {
last_restart_slot: 9999,
};
let data = to_bytes(&expected);
crate::tests::mock_get_sysvar_syscall(&data);

let got = LastRestartSlot::get().unwrap();
assert_eq!(got, expected);
}
}
Loading