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}