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