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}