Skip to main content

hopper_native/
sysvar.rs

1//! Sysvar access via direct syscalls.
2//!
3//! Provides zero-alloc, zero-deserialization access to Solana sysvars
4//! by reading them directly into stack buffers via syscalls. No framework
5//! wraps the epoch schedule sysvar at the native level.
6
7use crate::address::Address;
8use crate::error::ProgramError;
9
10// ── Clock ────────────────────────────────────────────────────────────
11
12/// Clock sysvar data, read directly from the runtime.
13#[repr(C)]
14#[derive(Clone, Copy, Debug, Default)]
15pub struct Clock {
16    pub slot: u64,
17    pub epoch_start_timestamp: i64,
18    pub epoch: u64,
19    pub leader_schedule_epoch: u64,
20    pub unix_timestamp: i64,
21}
22
23/// Read the Clock sysvar.
24#[inline]
25pub fn get_clock() -> Result<Clock, ProgramError> {
26    #[allow(unused_mut)]
27    let mut clock = Clock::default();
28
29    #[cfg(target_os = "solana")]
30    {
31        let rc =
32            unsafe { crate::syscalls::sol_get_clock_sysvar(&mut clock as *mut Clock as *mut u8) };
33        if rc != 0 {
34            return Err(ProgramError::UnsupportedSysvar);
35        }
36    }
37
38    Ok(clock)
39}
40
41// ── Rent ─────────────────────────────────────────────────────────────
42
43/// Rent sysvar data.
44#[repr(C)]
45#[derive(Clone, Copy, Debug, Default)]
46pub struct Rent {
47    pub lamports_per_byte_year: u64,
48    pub exemption_threshold: f64,
49    pub burn_percent: u8,
50}
51
52/// Read the Rent sysvar.
53#[inline]
54pub fn get_rent() -> Result<Rent, ProgramError> {
55    #[allow(unused_mut)]
56    let mut rent = Rent::default();
57
58    #[cfg(target_os = "solana")]
59    {
60        let rc = unsafe { crate::syscalls::sol_get_rent_sysvar(&mut rent as *mut Rent as *mut u8) };
61        if rc != 0 {
62            return Err(ProgramError::UnsupportedSysvar);
63        }
64    }
65
66    Ok(rent)
67}
68
69impl Rent {
70    /// Calculate the minimum lamports for rent exemption at the given data size.
71    #[inline]
72    pub fn minimum_balance(&self, data_len: usize) -> u64 {
73        // Total account size = data + 128 bytes of account metadata overhead.
74        let total_size = (data_len as u64).saturating_add(128);
75        let lamports =
76            (total_size as f64) * self.lamports_per_byte_year as f64 * self.exemption_threshold;
77        lamports as u64
78    }
79}
80
81// ── Epoch Schedule ───────────────────────────────────────────────────
82
83/// Epoch schedule sysvar data.
84///
85/// Nobody wraps this at the native level. Useful for programs that
86/// need to reason about epoch boundaries (staking, vesting, time locks).
87#[repr(C)]
88#[derive(Clone, Copy, Debug, Default)]
89pub struct EpochSchedule {
90    pub slots_per_epoch: u64,
91    pub leader_schedule_slot_offset: u64,
92    pub warmup: bool,
93    pub first_normal_epoch: u64,
94    pub first_normal_slot: u64,
95}
96
97/// Read the EpochSchedule sysvar.
98#[inline]
99pub fn get_epoch_schedule() -> Result<EpochSchedule, ProgramError> {
100    #[allow(unused_mut)]
101    let mut schedule = EpochSchedule::default();
102
103    #[cfg(target_os = "solana")]
104    {
105        let rc = unsafe {
106            crate::syscalls::sol_get_epoch_schedule_sysvar(
107                &mut schedule as *mut EpochSchedule as *mut u8,
108            )
109        };
110        if rc != 0 {
111            return Err(ProgramError::UnsupportedSysvar);
112        }
113    }
114
115    Ok(schedule)
116}
117
118impl EpochSchedule {
119    /// Get the epoch for a given slot.
120    #[inline]
121    pub fn get_epoch(&self, slot: u64) -> u64 {
122        if slot < self.first_normal_slot {
123            // During warmup, epoch length doubles each epoch.
124            // Initial epoch has 32 slots (MINIMUM_SLOTS_PER_EPOCH).
125            if slot == 0 {
126                return 0;
127            }
128            // log2(slot / 32) + 1, clamped.
129            let mut epoch_len: u64 = 32; // MINIMUM_SLOTS_PER_EPOCH
130            let mut epoch: u64 = 0;
131            let mut slot_remaining = slot;
132            while slot_remaining >= epoch_len {
133                slot_remaining -= epoch_len;
134                epoch += 1;
135                epoch_len = epoch_len.saturating_mul(2);
136            }
137            epoch
138        } else {
139            let normal_slot_index = slot - self.first_normal_slot;
140            self.first_normal_epoch + normal_slot_index / self.slots_per_epoch
141        }
142    }
143
144    /// Get the first slot in the given epoch.
145    #[inline]
146    pub fn get_first_slot_in_epoch(&self, epoch: u64) -> u64 {
147        if epoch <= self.first_normal_epoch {
148            // Warmup: each epoch doubles in length starting from 32.
149            if epoch == 0 {
150                return 0;
151            }
152            // First slot = sum of all previous epoch lengths.
153            // = 32 * (2^epoch - 1)
154            let shift = epoch.min(63);
155            32_u64.saturating_mul((1_u64 << shift).saturating_sub(1))
156        } else {
157            let normal_epoch_index = epoch - self.first_normal_epoch;
158            self.first_normal_slot + normal_epoch_index * self.slots_per_epoch
159        }
160    }
161}
162
163// ── Well-known sysvar addresses ──────────────────────────────────────
164
165/// Clock sysvar address.
166pub const CLOCK_ID: Address = crate::address!("SysvarC1ock11111111111111111111111111111111");
167
168/// Rent sysvar address.
169pub const RENT_ID: Address = crate::address!("SysvarRent111111111111111111111111111111111");
170
171/// Epoch schedule sysvar address.
172pub const EPOCH_SCHEDULE_ID: Address =
173    crate::address!("SysvarEpochSchedu1e111111111111111111111111");