Skip to main content

solana_epoch_schedule/
sysvar.rs

1pub use solana_sdk_ids::sysvar::epoch_schedule::{check_id, id, ID};
2use {
3    crate::EpochSchedule,
4    solana_get_sysvar::{get_sysvar_unchecked, GetSysvar},
5    solana_program_error::ProgramError,
6    solana_sysvar_id::impl_sysvar_id,
7};
8
9impl_sysvar_id!(EpochSchedule);
10
11/// Pod (Plain Old Data) representation of [`EpochSchedule`] with no padding.
12///
13/// This type can be safely loaded via `sol_get_sysvar` without undefined behavior.
14/// Provides performant zero-copy accessors as an alternative to the `EpochSchedule` type.
15#[repr(C)]
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct PodEpochSchedule {
18    slots_per_epoch: [u8; 8],
19    leader_schedule_slot_offset: [u8; 8],
20    warmup: u8,
21    first_normal_epoch: [u8; 8],
22    first_normal_slot: [u8; 8],
23}
24
25const POD_EPOCH_SCHEDULE_SIZE: usize = 33;
26const _: () = assert!(core::mem::size_of::<PodEpochSchedule>() == POD_EPOCH_SCHEDULE_SIZE);
27
28impl PodEpochSchedule {
29    /// Fetch the sysvar data using the `sol_get_sysvar` syscall.
30    /// This provides an alternative to `EpochSchedule` which provides zero-copy accessors.
31    pub fn fetch() -> Result<Self, ProgramError> {
32        let mut pod = core::mem::MaybeUninit::<Self>::uninit();
33        // Safety: `get_sysvar_unchecked` will initialize `pod` with the sysvar data,
34        // and error if unsuccessful.
35        unsafe {
36            get_sysvar_unchecked(
37                pod.as_mut_ptr() as *mut u8,
38                (&id()) as *const _ as *const u8,
39                0,
40                POD_EPOCH_SCHEDULE_SIZE as u64,
41            )?;
42            Ok(pod.assume_init())
43        }
44    }
45
46    pub fn slots_per_epoch(&self) -> u64 {
47        u64::from_le_bytes(self.slots_per_epoch)
48    }
49
50    pub fn leader_schedule_slot_offset(&self) -> u64 {
51        u64::from_le_bytes(self.leader_schedule_slot_offset)
52    }
53
54    pub fn warmup(&self) -> bool {
55        // SAFETY: upstream invariant: the sysvar data is created exclusively
56        // by the Solana runtime and serializes bool as 0x00 or 0x01.
57        self.warmup > 0
58    }
59
60    pub fn first_normal_epoch(&self) -> u64 {
61        u64::from_le_bytes(self.first_normal_epoch)
62    }
63
64    pub fn first_normal_slot(&self) -> u64 {
65        u64::from_le_bytes(self.first_normal_slot)
66    }
67}
68
69impl From<PodEpochSchedule> for EpochSchedule {
70    fn from(pod: PodEpochSchedule) -> Self {
71        Self {
72            slots_per_epoch: pod.slots_per_epoch(),
73            leader_schedule_slot_offset: pod.leader_schedule_slot_offset(),
74            warmup: pod.warmup(),
75            first_normal_epoch: pod.first_normal_epoch(),
76            first_normal_slot: pod.first_normal_slot(),
77        }
78    }
79}
80
81impl GetSysvar for EpochSchedule {
82    fn get() -> Result<Self, ProgramError> {
83        Ok(PodEpochSchedule::fetch()?.into())
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_pod_epoch_schedule_conversion() {
93        let pod = PodEpochSchedule {
94            slots_per_epoch: 432000u64.to_le_bytes(),
95            leader_schedule_slot_offset: 432000u64.to_le_bytes(),
96            warmup: 1,
97            first_normal_epoch: 14u64.to_le_bytes(),
98            first_normal_slot: 524256u64.to_le_bytes(),
99        };
100
101        let epoch_schedule = EpochSchedule::from(pod);
102
103        assert_eq!(epoch_schedule.slots_per_epoch, 432000);
104        assert_eq!(epoch_schedule.leader_schedule_slot_offset, 432000);
105        assert!(epoch_schedule.warmup);
106        assert_eq!(epoch_schedule.first_normal_epoch, 14);
107        assert_eq!(epoch_schedule.first_normal_slot, 524256);
108    }
109}