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}