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