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