Skip to main content

hopper_runtime/
syscalls.rs

1//! Minimal syscall shims exposed through Hopper Runtime.
2//!
3//! Hopper-owned crates use this module instead of depending on external SDK
4//! syscall paths.
5
6// These runtime-local syscall aliases keep a Rust name distinct from the
7// runtime symbol name (e.g. `syscall_sol_memcpy` -> `sol_memcpy_`) so the public
8// wrappers below can reuse the bare syscall names. Each flows through
9// `hopper_native::define_syscall!`, which is dual-mode:
10//
11//   * default build  -> the historical `extern "C" { #[link_name = ...] }` decl
12//     (byte-identical to before);
13//   * `--features static-syscalls` -> a static murmur32-hashed call taken over
14//     the *symbol* name (the `link_name`, not the Rust alias), matching the
15//     sBPF v3 / SIMD-0178 ABI.
16//
17// The feature is inherited from `hopper-native` via `hopper-runtime`'s own
18// `static-syscalls` feature; the default build is unchanged.
19
20#[cfg(target_os = "solana")]
21hopper_native::define_syscall!(
22    #[link_name = "sol_set_return_data"]
23    fn syscall_sol_set_return_data(data: *const u8, length: u64)
24);
25
26#[cfg(target_os = "solana")]
27hopper_native::define_syscall!(
28    #[link_name = "sol_get_return_data"]
29    fn syscall_sol_get_return_data(data: *mut u8, length: u64, program_id: *mut u8) -> u64
30);
31
32#[cfg(all(target_os = "solana", feature = "remaining-compute-units-syscall"))]
33hopper_native::define_syscall!(
34    #[link_name = "sol_remaining_compute_units"]
35    fn syscall_sol_remaining_compute_units() -> u64
36);
37
38#[cfg(target_os = "solana")]
39hopper_native::define_syscall!(
40    #[link_name = "sol_memcpy_"]
41    fn syscall_sol_memcpy(dst: *mut u8, src: *const u8, n: u64)
42);
43
44#[cfg(target_os = "solana")]
45hopper_native::define_syscall!(
46    #[link_name = "sol_memmove_"]
47    fn syscall_sol_memmove(dst: *mut u8, src: *const u8, n: u64)
48);
49
50#[cfg(target_os = "solana")]
51hopper_native::define_syscall!(
52    #[link_name = "sol_memcmp_"]
53    fn syscall_sol_memcmp(left: *const u8, right: *const u8, n: u64, result: *mut i32)
54);
55
56#[cfg(target_os = "solana")]
57hopper_native::define_syscall!(
58    #[link_name = "sol_memset_"]
59    fn syscall_sol_memset(dst: *mut u8, byte: u8, n: u64)
60);
61
62/// Emit a `sol_log_data` event payload.
63///
64/// # Safety
65///
66/// `segments` must point to a valid array of slice descriptors for the active
67/// Hopper's direct runtime ABI, and `segments_len` must match the number of entries.
68#[inline(always)]
69pub unsafe fn sol_log_data(segments: *const u8, segments_len: u64) {
70    #[cfg(target_os = "solana")]
71    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
72    // unchanged.
73    unsafe {
74        hopper_native::syscalls::sol_log_data(segments, segments_len);
75    }
76
77    #[cfg(not(target_os = "solana"))]
78    {
79        let _ = (segments, segments_len);
80    }
81}
82
83/// Compute SHA-256 over a slice-of-slices payload.
84///
85/// # Safety
86///
87/// `vals` must point to a valid array of slice descriptors and `result` must
88/// point to writable storage for 32 output bytes.
89#[inline(always)]
90pub unsafe fn sol_sha256(vals: *const u8, vals_len: u64, result: *mut u8) {
91    #[cfg(target_os = "solana")]
92    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
93    // unchanged.
94    unsafe {
95        hopper_native::syscalls::sol_sha256(vals, vals_len, result);
96    }
97
98    #[cfg(not(target_os = "solana"))]
99    {
100        let _ = (vals, vals_len, result);
101    }
102}
103
104/// Compute Keccak-256 over a slice-of-slices payload.
105///
106/// # Safety
107///
108/// `vals` must point to a valid array of slice descriptors and `result` must
109/// point to writable storage for 32 output bytes.
110#[inline(always)]
111pub unsafe fn sol_keccak256(vals: *const u8, vals_len: u64, result: *mut u8) {
112    #[cfg(target_os = "solana")]
113    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
114    // unchanged.
115    unsafe {
116        hopper_native::syscalls::sol_keccak256(vals, vals_len, result);
117    }
118
119    #[cfg(not(target_os = "solana"))]
120    {
121        let _ = (vals, vals_len, result);
122    }
123}
124
125/// Compute BLAKE3 over a slice-of-slices payload.
126///
127/// Returns the Solana runtime status code (`0` on success).
128///
129/// # Safety
130///
131/// `vals` must point to a valid array of slice descriptors and `result` must
132/// point to writable storage for 32 output bytes.
133#[inline(always)]
134pub unsafe fn sol_blake3(vals: *const u8, vals_len: u64, result: *mut u8) -> u64 {
135    #[cfg(target_os = "solana")]
136    // SAFETY: Caller supplies the Solana hash syscall descriptors and output buffer.
137    unsafe {
138        return hopper_native::syscalls::sol_blake3(vals, vals_len, result);
139    }
140
141    #[cfg(not(target_os = "solana"))]
142    {
143        let _ = (vals, vals_len, result);
144        0
145    }
146}
147
148/// Recover a secp256k1 public key from a 32-byte message hash and 64-byte
149/// compact signature.
150///
151/// Returns the Solana runtime status code (`0` on success).
152///
153/// # Safety
154///
155/// `hash` must point to 32 bytes, `signature` must point to 64 bytes, and
156/// `result` must point to 64 writable bytes.
157#[inline(always)]
158pub unsafe fn sol_secp256k1_recover(
159    hash: *const u8,
160    recovery_id: u64,
161    signature: *const u8,
162    result: *mut u8,
163) -> u64 {
164    #[cfg(target_os = "solana")]
165    // SAFETY: Caller supplies fixed-width secp256k1 recover syscall buffers.
166    unsafe {
167        return hopper_native::syscalls::sol_secp256k1_recover(
168            hash,
169            recovery_id,
170            signature,
171            result,
172        );
173    }
174
175    #[cfg(not(target_os = "solana"))]
176    {
177        let _ = (hash, recovery_id, signature, result);
178        1
179    }
180}
181
182/// Validate whether a point lies on a runtime-supported curve.
183///
184/// Returns the runtime status code. For Solana curve-validation syscalls,
185/// `0` means the point is valid for the selected curve.
186///
187/// # Safety
188///
189/// `point_addr` must point to a valid 32-byte encoded point. `result_point_addr`
190/// is forwarded to the runtime syscall and may be null for validation-only use.
191#[inline(always)]
192pub unsafe fn sol_curve_validate_point(
193    curve_id: u64,
194    point_addr: *const u8,
195    result_point_addr: *mut u8,
196) -> u64 {
197    #[cfg(target_os = "solana")]
198    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
199    // unchanged.
200    unsafe {
201        return hopper_native::syscalls::sol_curve_validate_point(
202            curve_id,
203            point_addr,
204            result_point_addr,
205        );
206    }
207
208    #[cfg(not(target_os = "solana"))]
209    {
210        let _ = (curve_id, point_addr, result_point_addr);
211        1
212    }
213}
214
215/// Run a group operation on a runtime-supported curve.
216///
217/// Returns the Solana runtime status code (`0` on success).
218///
219/// # Safety
220///
221/// `left_input_addr`, `right_input_addr`, and `result_point_addr` must point to
222/// buffers matching the selected curve and group operation. For multiplication,
223/// Solana expects the scalar as the left input and the point as the right input.
224#[inline(always)]
225pub unsafe fn sol_curve_group_op(
226    curve_id: u64,
227    group_op: u64,
228    left_input_addr: *const u8,
229    right_input_addr: *const u8,
230    result_point_addr: *mut u8,
231) -> u64 {
232    #[cfg(target_os = "solana")]
233    // SAFETY: Caller supplies curve syscall buffers matching the selected operation.
234    unsafe {
235        return hopper_native::syscalls::sol_curve_group_op(
236            curve_id,
237            group_op,
238            left_input_addr,
239            right_input_addr,
240            result_point_addr,
241        );
242    }
243
244    #[cfg(not(target_os = "solana"))]
245    {
246        let _ = (
247            curve_id,
248            group_op,
249            left_input_addr,
250            right_input_addr,
251            result_point_addr,
252        );
253        1
254    }
255}
256
257/// Run variable-length multiscalar multiplication on a runtime-supported curve.
258///
259/// Returns the Solana runtime status code (`0` on success).
260///
261/// # Safety
262///
263/// `scalars_addr` and `points_addr` must point to contiguous arrays of 32-byte
264/// scalar and point encodings with exactly `points_len` entries. `result_point_addr`
265/// must point to 32 writable output bytes.
266#[inline(always)]
267pub unsafe fn sol_curve_multiscalar_mul(
268    curve_id: u64,
269    scalars_addr: *const u8,
270    points_addr: *const u8,
271    points_len: u64,
272    result_point_addr: *mut u8,
273) -> u64 {
274    #[cfg(target_os = "solana")]
275    // SAFETY: Caller supplies curve syscall buffers matching the selected operation.
276    unsafe {
277        return hopper_native::syscalls::sol_curve_multiscalar_mul(
278            curve_id,
279            scalars_addr,
280            points_addr,
281            points_len,
282            result_point_addr,
283        );
284    }
285
286    #[cfg(not(target_os = "solana"))]
287    {
288        let _ = (
289            curve_id,
290            scalars_addr,
291            points_addr,
292            points_len,
293            result_point_addr,
294        );
295        1
296    }
297}
298
299/// Compute a Poseidon hash using Solana's runtime syscall.
300///
301/// Returns the Solana runtime status code (`0` on success).
302///
303/// # Safety
304///
305/// `vals` must point to a valid array of slice descriptors and `hash_result`
306/// must point to 32 writable output bytes.
307#[inline(always)]
308pub unsafe fn sol_poseidon(
309    parameters: u64,
310    endianness: u64,
311    vals: *const u8,
312    val_len: u64,
313    hash_result: *mut u8,
314) -> u64 {
315    #[cfg(target_os = "solana")]
316    // SAFETY: Caller supplies Poseidon slice descriptors and output buffer.
317    unsafe {
318        return hopper_native::syscalls::sol_poseidon(
319            parameters,
320            endianness,
321            vals,
322            val_len,
323            hash_result,
324        );
325    }
326
327    #[cfg(not(target_os = "solana"))]
328    {
329        let _ = (parameters, endianness, vals, val_len, hash_result);
330        1
331    }
332}
333
334/// Run a BN254 / alt_bn128 group operation.
335///
336/// Returns the Solana runtime status code (`0` on success).
337///
338/// # Safety
339///
340/// `input` must point to `input_size` bytes in the operation-specific encoding
341/// and `result` must point to the operation-specific output buffer.
342#[inline(always)]
343pub unsafe fn sol_alt_bn128_group_op(
344    group_op: u64,
345    input: *const u8,
346    input_size: u64,
347    result: *mut u8,
348) -> u64 {
349    #[cfg(target_os = "solana")]
350    // SAFETY: Caller supplies BN254 operation buffers matching `group_op`.
351    unsafe {
352        return hopper_native::syscalls::sol_alt_bn128_group_op(
353            group_op, input, input_size, result,
354        );
355    }
356
357    #[cfg(not(target_os = "solana"))]
358    {
359        let _ = (group_op, input, input_size, result);
360        1
361    }
362}
363
364/// Run a BN254 / alt_bn128 compression operation.
365///
366/// Returns the Solana runtime status code (`0` on success).
367///
368/// # Safety
369///
370/// `input` must point to `input_size` bytes in the operation-specific encoding
371/// and `result` must point to the operation-specific output buffer.
372#[inline(always)]
373pub unsafe fn sol_alt_bn128_compression(
374    op: u64,
375    input: *const u8,
376    input_size: u64,
377    result: *mut u8,
378) -> u64 {
379    #[cfg(target_os = "solana")]
380    // SAFETY: Caller supplies BN254 compression buffers matching `op`.
381    unsafe {
382        return hopper_native::syscalls::sol_alt_bn128_compression(op, input, input_size, result);
383    }
384
385    #[cfg(not(target_os = "solana"))]
386    {
387        let _ = (op, input, input_size, result);
388        1
389    }
390}
391
392/// Run big integer modular exponentiation.
393///
394/// Returns the Solana runtime status code (`0` on success).
395///
396/// # Safety
397///
398/// `params` must point to a `BigModExpParams`-compatible C layout and `result`
399/// must point to writable storage with exactly the modulus length.
400#[inline(always)]
401pub unsafe fn sol_big_mod_exp(params: *const u8, result: *mut u8) -> u64 {
402    #[cfg(target_os = "solana")]
403    // SAFETY: Caller supplies a valid big-mod-exp parameter block and output buffer.
404    unsafe {
405        return hopper_native::syscalls::sol_big_mod_exp(params, result);
406    }
407
408    #[cfg(not(target_os = "solana"))]
409    {
410        let _ = (params, result);
411        1
412    }
413}
414
415/// Return the current Solana instruction stack height.
416#[inline(always)]
417pub fn sol_get_stack_height() -> u64 {
418    #[cfg(target_os = "solana")]
419    // SAFETY: The syscall takes no pointer and has no memory precondition.
420    unsafe {
421        return hopper_native::syscalls::sol_get_stack_height();
422    }
423
424    #[cfg(not(target_os = "solana"))]
425    {
426        1
427    }
428}
429
430/// Read a previously processed sibling instruction from the current transaction.
431///
432/// # Safety
433///
434/// The output pointers must refer to writable buffers large enough for the
435/// runtime to fill according to the processed-instruction syscall contract.
436#[inline(always)]
437pub unsafe fn sol_get_processed_sibling_instruction(
438    index: u64,
439    meta: *mut u8,
440    program_id: *mut u8,
441    data: *mut u8,
442    accounts: *mut u8,
443) -> u64 {
444    #[cfg(target_os = "solana")]
445    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
446    // unchanged.
447    unsafe {
448        return hopper_native::syscalls::sol_get_processed_sibling_instruction(
449            index, meta, program_id, data, accounts,
450        );
451    }
452
453    #[cfg(not(target_os = "solana"))]
454    {
455        let _ = (index, meta, program_id, data, accounts);
456        1
457    }
458}
459
460/// Set CPI return data for the current instruction.
461///
462/// # Safety
463///
464/// `data` must be valid for `length` bytes.
465#[inline(always)]
466pub unsafe fn sol_set_return_data(data: *const u8, length: u64) {
467    #[cfg(target_os = "solana")]
468    // SAFETY: Caller guarantees that `data` is valid for `length` bytes.
469    unsafe {
470        syscall_sol_set_return_data(data, length);
471    }
472
473    #[cfg(not(target_os = "solana"))]
474    {
475        let _ = (data, length);
476    }
477}
478
479/// Read CPI return data set by the most recent CPI.
480///
481/// Returns the runtime-reported data length. If the return data is larger than
482/// the provided buffer, only `length` bytes are copied into `data`.
483///
484/// # Safety
485///
486/// `data` must be valid for `length` writable bytes and `program_id` must point
487/// to 32 writable bytes.
488#[inline(always)]
489pub unsafe fn sol_get_return_data(data: *mut u8, length: u64, program_id: *mut u8) -> u64 {
490    #[cfg(target_os = "solana")]
491    // SAFETY: Caller provides writable buffers for return bytes and program id.
492    unsafe {
493        return syscall_sol_get_return_data(data, length, program_id);
494    }
495
496    #[cfg(not(target_os = "solana"))]
497    {
498        let _ = (data, length, program_id);
499        0
500    }
501}
502
503/// Return the remaining compute units reported by the Solana runtime.
504///
505/// SIMD-0049 is Withdrawn and no public cluster activates its gate; the
506/// on-chain binding exists only under `remaining-compute-units-syscall`.
507#[cfg(any(not(target_os = "solana"), feature = "remaining-compute-units-syscall"))]
508#[inline(always)]
509pub fn sol_remaining_compute_units() -> u64 {
510    #[cfg(target_os = "solana")]
511    // SAFETY: Zero-argument Solana runtime syscall.
512    unsafe {
513        return syscall_sol_remaining_compute_units();
514    }
515
516    #[cfg(not(target_os = "solana"))]
517    {
518        u64::MAX
519    }
520}
521
522/// Copy `n` non-overlapping bytes from `src` to `dst`.
523///
524/// # Safety
525///
526/// `src` and `dst` must be valid for `n` bytes and must not overlap.
527#[inline(always)]
528pub unsafe fn sol_memcpy_(dst: *mut u8, src: *const u8, n: u64) {
529    #[cfg(target_os = "solana")]
530    // SAFETY: Caller upholds the Solana memory syscall contract.
531    unsafe {
532        syscall_sol_memcpy(dst, src, n);
533    }
534
535    #[cfg(not(target_os = "solana"))]
536    // SAFETY: Caller guarantees valid, non-overlapping ranges.
537    unsafe {
538        core::ptr::copy_nonoverlapping(src, dst, n as usize);
539    }
540}
541
542/// Copy `n` bytes from `src` to `dst`, allowing overlap.
543///
544/// # Safety
545///
546/// `src` and `dst` must be valid for `n` bytes.
547#[inline(always)]
548pub unsafe fn sol_memmove_(dst: *mut u8, src: *const u8, n: u64) {
549    #[cfg(target_os = "solana")]
550    // SAFETY: Caller upholds the Solana memory syscall contract.
551    unsafe {
552        syscall_sol_memmove(dst, src, n);
553    }
554
555    #[cfg(not(target_os = "solana"))]
556    // SAFETY: Caller guarantees valid ranges; `copy` allows overlap.
557    unsafe {
558        core::ptr::copy(src, dst, n as usize);
559    }
560}
561
562/// Compare two byte ranges and write a negative, zero, or positive result.
563///
564/// # Safety
565///
566/// `left` and `right` must be valid for `n` bytes. `result` must be writable
567/// for one `i32`.
568#[inline(always)]
569pub unsafe fn sol_memcmp_(left: *const u8, right: *const u8, n: u64, result: *mut i32) {
570    #[cfg(target_os = "solana")]
571    // SAFETY: Caller upholds the Solana memory syscall contract.
572    unsafe {
573        syscall_sol_memcmp(left, right, n, result);
574    }
575
576    #[cfg(not(target_os = "solana"))]
577    // SAFETY: Caller guarantees valid ranges and writable result.
578    unsafe {
579        let left_bytes = core::slice::from_raw_parts(left, n as usize);
580        let right_bytes = core::slice::from_raw_parts(right, n as usize);
581        *result = match left_bytes.cmp(right_bytes) {
582            core::cmp::Ordering::Less => -1,
583            core::cmp::Ordering::Equal => 0,
584            core::cmp::Ordering::Greater => 1,
585        };
586    }
587}
588
589/// Fill `n` bytes at `dst` with `byte`.
590///
591/// # Safety
592///
593/// `dst` must be valid for `n` writable bytes.
594#[inline(always)]
595pub unsafe fn sol_memset_(dst: *mut u8, byte: u8, n: u64) {
596    #[cfg(target_os = "solana")]
597    // SAFETY: Caller upholds the Solana memory syscall contract.
598    unsafe {
599        syscall_sol_memset(dst, byte, n);
600    }
601
602    #[cfg(not(target_os = "solana"))]
603    // SAFETY: Caller guarantees `dst` is valid for `n` bytes.
604    unsafe {
605        core::ptr::write_bytes(dst, byte, n as usize);
606    }
607}