Skip to main content

solana_get_sysvar/
lib.rs

1//! Access to the `sol_get_sysvar` syscall, used to fetch sysvar data from the runtime.
2
3#![no_std]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6use {solana_address::Address, solana_program_error::ProgramError};
7
8// Stable `$crate` paths for `impl_get_sysvar!`, which expands downstream.
9#[doc(hidden)]
10pub mod __private {
11    pub use {crate::get_sysvar_unchecked, solana_program_error::ProgramError};
12}
13
14/// Syscall success code.
15//
16// Defined in solana-program-entrypoint as [`SUCCESS`](https://github.com/anza-xyz/solana-sdk/blob/program-entrypoint@v2.2.1/program-entrypoint/src/lib.rs#L35).
17const SUCCESS: u64 = 0;
18/// Return value indicating that the  `offset + length` is greater than the length of
19/// the sysvar data.
20//
21// Defined in the Agave syscalls crate as [`OFFSET_LENGTH_EXCEEDS_SYSVAR`](https://github.com/anza-xyz/agave/blob/v4.0.2/syscalls/src/sysvar.rs#L180).
22const OFFSET_LENGTH_EXCEEDS_SYSVAR: u64 = 1;
23
24/// Return value indicating that the sysvar was not found.
25//
26// Defined in the Agave syscalls crate as [`SYSVAR_NOT_FOUND`](https://github.com/anza-xyz/agave/blob/v4.0.2/syscalls/src/sysvar.rs#L179).
27const SYSVAR_NOT_FOUND: u64 = 2;
28
29/// Interface for loading a sysvar directly from the runtime.
30pub trait GetSysvar: Sized {
31    /// Load the sysvar directly from the runtime.
32    ///
33    /// This is the preferred way to load a sysvar. Calling this method does not
34    /// incur any deserialization overhead, and does not require the sysvar
35    /// account to be passed to the program.
36    ///
37    /// Not all sysvars support this method. If not, it returns
38    /// [`ProgramError::UnsupportedSysvar`].
39    fn get() -> Result<Self, ProgramError> {
40        Err(ProgramError::UnsupportedSysvar)
41    }
42}
43
44/// Handler for retrieving a slice of sysvar data from the `sol_get_sysvar`
45/// syscall.
46pub fn get_sysvar(
47    dst: &mut [u8],
48    sysvar_id: &Address,
49    offset: u64,
50    length: u64,
51) -> Result<(), ProgramError> {
52    // Check that the provided destination buffer is large enough to hold the requested data
53    if dst.len() < length as usize {
54        return Err(ProgramError::InvalidArgument);
55    }
56
57    let sysvar_id = sysvar_id as *const _ as *const u8;
58    let var_addr = dst as *mut _ as *mut u8;
59
60    match sol_get_sysvar(sysvar_id, var_addr, offset, length) {
61        SUCCESS => Ok(()),
62        OFFSET_LENGTH_EXCEEDS_SYSVAR => Err(ProgramError::InvalidArgument),
63        _ => Err(ProgramError::UnsupportedSysvar),
64    }
65}
66
67/// Internal helper for retrieving sysvar data directly into a raw buffer.
68///
69/// # Safety
70///
71/// This function bypasses the slice-length check that `get_sysvar` performs.
72/// The caller must ensure that `var_addr` points to a writable buffer of at
73/// least `length` bytes. This is typically used with `MaybeUninit` to load
74/// compact representations of sysvars.
75#[doc(hidden)]
76pub unsafe fn get_sysvar_unchecked(
77    var_addr: *mut u8,
78    sysvar_id: *const u8,
79    offset: u64,
80    length: u64,
81) -> Result<(), ProgramError> {
82    match sol_get_sysvar(sysvar_id, var_addr, offset, length) {
83        SUCCESS => Ok(()),
84        OFFSET_LENGTH_EXCEEDS_SYSVAR => Err(ProgramError::InvalidArgument),
85        SYSVAR_NOT_FOUND => Err(ProgramError::UnsupportedSysvar),
86        // Unexpected errors are folded into `UnsupportedSysvar`.
87        _ => Err(ProgramError::UnsupportedSysvar),
88    }
89}
90
91fn sol_get_sysvar(sysvar_id: *const u8, var_addr: *mut u8, offset: u64, length: u64) -> u64 {
92    // On-chain programs call the runtime syscall directly
93    #[cfg(target_os = "solana")]
94    unsafe {
95        solana_define_syscall::definitions::sol_get_sysvar(sysvar_id, var_addr, offset, length)
96    }
97
98    // Off-chain builds have no solana runtime syscall to call
99    #[cfg(not(target_os = "solana"))]
100    {
101        let _ = (sysvar_id, var_addr, offset, length); // warning suppression
102        solana_program_error::UNSUPPORTED_SYSVAR
103    }
104}
105
106/// Implements [`GetSysvar::get`] for runtime-backed sysvars.
107#[macro_export]
108macro_rules! impl_get_sysvar {
109    // Variant for sysvars with padding at the end. Loads bincode-serialized data
110    // (size - padding bytes) and zeros the padding to avoid undefined behavior.
111    // Only supports sysvars where padding is at the end of the layout. Caller
112    // must supply the correct number of padding bytes.
113    ($sysvar_id:expr, $padding:literal) => {
114        fn get() -> Result<Self, $crate::__private::ProgramError> {
115            let mut var = core::mem::MaybeUninit::<Self>::uninit();
116            let var_addr = var.as_mut_ptr() as *mut u8;
117            let length = core::mem::size_of::<Self>().saturating_sub($padding);
118            let sysvar_id_ptr = (&$sysvar_id) as *const _ as *const u8;
119            // SAFETY: The allocation is valid for `size_of::<Self>()`. We zero
120            // the padding bytes first, then load `(size - padding)` bytes from
121            // the syscall, which matches bincode serialization.
122            let result = unsafe {
123                var_addr.add(length).write_bytes(0, $padding);
124                $crate::__private::get_sysvar_unchecked(var_addr, sysvar_id_ptr, 0, length as u64)
125            };
126            match result {
127                // SAFETY: All bytes initialized: padding was zeroed above,
128                // syscall filled the data bytes.
129                Ok(()) => Ok(unsafe { var.assume_init() }),
130                // Unexpected errors are folded into `UnsupportedSysvar`.
131                Err(_) => Err($crate::__private::ProgramError::UnsupportedSysvar),
132            }
133        }
134    };
135    // Variant for sysvars without padding (struct size matches bincode size).
136    ($sysvar_id:expr) => {
137        $crate::impl_get_sysvar!($sysvar_id, 0);
138    };
139}