Skip to main content

solana_syscalls/
sysvar.rs

1use {
2    super::*, crate::translate_mut,
3    solana_program_runtime::execution_budget::SVMTransactionExecutionCost, solana_sbpf::ebpf,
4    solana_sysvar_id::SysvarId,
5};
6
7fn get_sysvar<T: SysvarId + Clone>(
8    sysvar: Result<Arc<T>, InstructionError>,
9    var_addr: u64,
10    invoke_context: &mut InvokeContext,
11) -> Result<u64, Error> {
12    let amount = invoke_context
13        .get_execution_cost()
14        .sysvar_base_cost
15        .saturating_add(size_of::<T>() as u64);
16    invoke_context.compute_meter.consume_checked(amount)?;
17
18    // If a test case contains a program that is owned by the deprecated
19    // bpf loader but also contains get_sysvar syscalls, the store into the
20    // host memory will segfault due to unaligned accesses. However, this has
21    // no consensus impact because this loader was deprecated before the get_sysvar
22    // syscall was activated.
23    let check_aligned = invoke_context.get_check_aligned();
24    if !check_aligned {
25        return Err(SyscallError::UnalignedPointer.into());
26    }
27
28    if var_addr >= ebpf::MM_INPUT_START
29        && invoke_context
30            .get_feature_set()
31            .syscall_parameter_address_restrictions
32    {
33        return Err(SyscallError::InvalidPointer.into());
34    }
35
36    let memory_mapping = invoke_context.memory_contexts.memory_mapping_mut()?;
37    translate_mut!(
38        memory_mapping,
39        check_aligned,
40        let var: (&mut std::mem::MaybeUninit<T>) = map(var_addr)?;
41    );
42
43    // this clone looks unnecessary now, but it exists to zero out trailing alignment bytes
44    // it is unclear whether this should ever matter
45    // but there are tests using MemoryMapping that expect to see this
46    // we preserve the previous behavior out of an abundance of caution
47    let sysvar: Arc<T> = sysvar?;
48    var.write(T::clone(&sysvar));
49
50    Ok(SUCCESS)
51}
52
53declare_builtin_function!(
54    /// Get a Clock sysvar
55    SyscallGetClockSysvar,
56    fn rust(
57        invoke_context: &mut InvokeContext<'_, '_>,
58        var_addr: u64,
59        _arg2: u64,
60        _arg3: u64,
61        _arg4: u64,
62        _arg5: u64,
63    ) -> Result<u64, Error> {
64        get_sysvar(
65            invoke_context.environment_config.sysvar_cache().get_clock(),
66            var_addr,
67            invoke_context,
68        )
69    }
70);
71
72declare_builtin_function!(
73    /// Get a EpochSchedule sysvar
74    SyscallGetEpochScheduleSysvar,
75    fn rust(
76        invoke_context: &mut InvokeContext<'_, '_>,
77        var_addr: u64,
78        _arg2: u64,
79        _arg3: u64,
80        _arg4: u64,
81        _arg5: u64,
82    ) -> Result<u64, Error> {
83        get_sysvar(
84            invoke_context.environment_config.sysvar_cache().get_epoch_schedule(),
85            var_addr,
86            invoke_context,
87        )
88    }
89);
90
91declare_builtin_function!(
92    /// Get a EpochRewards sysvar
93    SyscallGetEpochRewardsSysvar,
94    fn rust(
95        invoke_context: &mut InvokeContext<'_, '_>,
96        var_addr: u64,
97        _arg2: u64,
98        _arg3: u64,
99        _arg4: u64,
100        _arg5: u64,
101    ) -> Result<u64, Error> {
102        get_sysvar(
103            invoke_context.environment_config.sysvar_cache().get_epoch_rewards(),
104            var_addr,
105            invoke_context,
106        )
107    }
108);
109
110declare_builtin_function!(
111    /// Get a Fees sysvar
112    SyscallGetFeesSysvar,
113    fn rust(
114        invoke_context: &mut InvokeContext<'_, '_>,
115        var_addr: u64,
116        _arg2: u64,
117        _arg3: u64,
118        _arg4: u64,
119        _arg5: u64,
120    ) -> Result<u64, Error> {
121        #[expect(deprecated)]
122        {
123            get_sysvar(
124                invoke_context.environment_config.sysvar_cache().get_fees(),
125                var_addr,
126                invoke_context,
127            )
128        }
129    }
130);
131
132declare_builtin_function!(
133    /// Get a Rent sysvar
134    SyscallGetRentSysvar,
135    fn rust(
136        invoke_context: &mut InvokeContext<'_, '_>,
137        var_addr: u64,
138        _arg2: u64,
139        _arg3: u64,
140        _arg4: u64,
141        _arg5: u64,
142    ) -> Result<u64, Error> {
143        get_sysvar(
144            invoke_context.environment_config.sysvar_cache().get_rent(),
145            var_addr,
146            invoke_context,
147        )
148    }
149);
150
151declare_builtin_function!(
152    /// Get a Last Restart Slot sysvar
153    SyscallGetLastRestartSlotSysvar,
154    fn rust(
155        invoke_context: &mut InvokeContext<'_, '_>,
156        var_addr: u64,
157        _arg2: u64,
158        _arg3: u64,
159        _arg4: u64,
160        _arg5: u64,
161    ) -> Result<u64, Error> {
162        get_sysvar(
163            invoke_context.environment_config.sysvar_cache().get_last_restart_slot(),
164            var_addr,
165            invoke_context,
166        )
167    }
168);
169
170const SYSVAR_NOT_FOUND: u64 = 2;
171const OFFSET_LENGTH_EXCEEDS_SYSVAR: u64 = 1;
172
173// quoted language from SIMD0127
174// because this syscall can both return error codes and abort, well-ordered error checking is crucial
175declare_builtin_function!(
176    /// Get a slice of a Sysvar in-memory representation
177    SyscallGetSysvar,
178    fn rust(
179        invoke_context: &mut InvokeContext<'_, '_>,
180        sysvar_id_addr: u64,
181        var_addr: u64,
182        offset: u64,
183        length: u64,
184        _arg5: u64,
185    ) -> Result<u64, Error> {
186        let SVMTransactionExecutionCost {
187            sysvar_base_cost,
188            cpi_bytes_per_unit,
189            mem_op_base_cost,
190            ..
191        } = *invoke_context.get_execution_cost();
192
193        // Abort: "Compute budget is exceeded."
194        let sysvar_id_cost = 32_u64.checked_div(cpi_bytes_per_unit).unwrap_or(0);
195        let sysvar_buf_cost = length.checked_div(cpi_bytes_per_unit).unwrap_or(0);
196        let cost = sysvar_base_cost
197            .saturating_add(sysvar_id_cost)
198            .saturating_add(std::cmp::max(sysvar_buf_cost, mem_op_base_cost));
199        invoke_context.compute_meter.consume_checked(cost)?;
200
201        // If a test case contains a program that is owned by the deprecated
202        // bpf loader but also contains get_sysvar syscalls, the store into the
203        // host memory will segfault due to unaligned accesses. However, this has
204        // no consensus impact because this loader was deprecated before the get_sysvar
205        // syscall was activated.
206        let check_aligned = invoke_context.get_check_aligned();
207        if !check_aligned {
208            return Err(SyscallError::UnalignedPointer.into());
209        }
210
211        if var_addr >= ebpf::MM_INPUT_START
212            && invoke_context
213                .get_feature_set()
214                .syscall_parameter_address_restrictions
215        {
216            return Err(SyscallError::InvalidPointer.into());
217        }
218
219        let memory_mapping = invoke_context.memory_contexts.memory_mapping_mut()?;
220        // Abort: "Not all bytes in VM memory range `[var_addr, var_addr + length)` are writable."
221        {
222            // Just a check that this maps correctly for error compatibility with old code.
223            translate_mut!(
224                memory_mapping,
225                check_aligned,
226                let _var: (&mut [MaybeUninit<u8>]) = map(var_addr, length)?;
227            );
228        }
229
230        // Abort: "Not all bytes in VM memory range `[sysvar_id, sysvar_id + 32)` are readable."
231        let sysvar_id = translate_type::<Pubkey>(memory_mapping, sysvar_id_addr, check_aligned)?;
232
233        // Abort: "`offset + length` is not in `[0, 2^64)`."
234        let offset_length = offset
235            .checked_add(length)
236            .ok_or(InstructionError::ArithmeticOverflow)?;
237
238        // Abort: "`var_addr + length` is not in `[0, 2^64)`."
239        let _ = var_addr
240            .checked_add(length)
241            .ok_or(InstructionError::ArithmeticOverflow)?;
242
243
244        // "`2` if the sysvar data is not present in the Sysvar Cache."
245        let cache = invoke_context.environment_config.sysvar_cache();
246        let Some(sysvar_buf) = cache.sysvar_id_to_buffer(sysvar_id) else {
247            return Ok(SYSVAR_NOT_FOUND)
248        };
249
250        // "`1` if `offset + length` is greater than the length of the sysvar data."
251        if let Some(sysvar_slice) = sysvar_buf.get(offset as usize..offset_length as usize) {
252            translate_mut!(
253                memory_mapping,
254                check_aligned,
255                let var: (&mut [MaybeUninit<u8>]) = map(var_addr, length)?;
256            );
257            var.write_copy_of_slice(sysvar_slice);
258        } else {
259            return Ok(OFFSET_LENGTH_EXCEEDS_SYSVAR);
260        }
261
262        Ok(SUCCESS)
263    }
264);