Skip to main content

hopper_native/
mem.rs

1//! Memory operations backed by Solana SVM syscalls.
2//!
3//! On Solana SBF, these dispatch to `sol_memcpy_`, `sol_memmove_`, `sol_memcmp_`,
4//! and `sol_memset_`. Off-chain, they fall back to standard library
5//! implementations.
6
7use crate::error::ProgramError;
8
9/// Copy `n` bytes from `src` to `dst`.
10///
11/// The memory regions **must not overlap**. For overlapping copies, use
12/// `memmove`. This is enforced by the SVM runtime on BPF.
13///
14/// # Safety
15///
16/// Both `src` and `dst` must be valid for `n` bytes. Regions must not overlap.
17#[inline(always)]
18pub unsafe fn memcpy(dst: *mut u8, src: *const u8, n: usize) {
19    #[cfg(target_os = "solana")]
20    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
21    // unchanged.
22    unsafe {
23        crate::syscalls::sol_memcpy_(dst, src, n as u64);
24    }
25    #[cfg(not(target_os = "solana"))]
26    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
27    // unchanged.
28    unsafe {
29        core::ptr::copy_nonoverlapping(src, dst, n);
30    }
31}
32
33/// Copy `n` bytes from `src` to `dst`, handling overlapping regions.
34///
35/// Safe for any src/dst alignment and overlap pattern.
36///
37/// # Safety
38///
39/// Both `src` and `dst` must be valid for `n` bytes.
40#[inline(always)]
41pub unsafe fn memmove(dst: *mut u8, src: *const u8, n: usize) {
42    #[cfg(target_os = "solana")]
43    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
44    // unchanged.
45    unsafe {
46        crate::syscalls::sol_memmove_(dst, src, n as u64);
47    }
48    #[cfg(not(target_os = "solana"))]
49    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
50    // unchanged.
51    unsafe {
52        core::ptr::copy(src, dst, n);
53    }
54}
55
56/// Fill `n` bytes starting at `dst` with `byte`.
57///
58/// Uses the SVM `sol_memset_` syscall on-chain.
59///
60/// # Safety
61///
62/// `dst` must be valid for `n` bytes.
63#[inline(always)]
64pub unsafe fn memset(dst: *mut u8, byte: u8, n: usize) {
65    #[cfg(target_os = "solana")]
66    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
67    // unchanged.
68    unsafe {
69        crate::syscalls::sol_memset_(dst, byte, n as u64);
70    }
71    #[cfg(not(target_os = "solana"))]
72    // SAFETY: This function's `# Safety` contract is the callee's, forwarded
73    // unchanged.
74    unsafe {
75        core::ptr::write_bytes(dst, byte, n);
76    }
77}
78
79/// Compare `n` bytes between two memory regions.
80///
81/// Returns `Ordering::Equal` if the regions are identical, or the
82/// ordering of the first differing byte (lexicographic comparison).
83///
84/// # Safety
85///
86/// Both `a` and `b` must be valid for `n` bytes.
87#[inline(always)]
88pub unsafe fn memcmp(a: *const u8, b: *const u8, n: usize) -> core::cmp::Ordering {
89    #[cfg(target_os = "solana")]
90    {
91        let mut result: i32 = 0;
92        // SAFETY: The caller guarantees both pointers are valid for `n`
93        // bytes; `result` is a live local the syscall writes one `i32` into.
94        unsafe {
95            crate::syscalls::sol_memcmp_(a, b, n as u64, &mut result as *mut i32);
96        }
97        match result {
98            0 => core::cmp::Ordering::Equal,
99            x if x < 0 => core::cmp::Ordering::Less,
100            _ => core::cmp::Ordering::Greater,
101        }
102    }
103    #[cfg(not(target_os = "solana"))]
104    {
105        // SAFETY: The caller guarantees the pointer is valid for reads of `n`
106        // bytes.
107        let a_slice = unsafe { core::slice::from_raw_parts(a, n) };
108        let b_slice = unsafe { core::slice::from_raw_parts(b, n) };
109        a_slice.cmp(b_slice)
110    }
111}
112
113// ---- Safe wrappers ---------------------------------------------------
114
115/// Zero-fill a mutable byte slice using the SVM-optimized memset.
116#[inline(always)]
117pub fn zero_fill(buf: &mut [u8]) {
118    if buf.is_empty() {
119        return;
120    }
121    // SAFETY: The pointer and the length come from one `&mut [u8]`.
122    unsafe {
123        memset(buf.as_mut_ptr(), 0, buf.len());
124    }
125}
126
127/// Copy bytes from one slice to another (no overlap).
128///
129/// Copies `src.len()` bytes into the front of `dst`. Returns
130/// `Err(InvalidArgument)` if `dst` is shorter than `src`; a longer `dst`
131/// keeps its trailing bytes unchanged.
132#[inline]
133pub fn copy_bytes(dst: &mut [u8], src: &[u8]) -> Result<(), ProgramError> {
134    if dst.len() < src.len() {
135        return Err(ProgramError::InvalidArgument);
136    }
137    // SAFETY: `dst.len() >= src.len()` was checked above, and a `&mut` slice
138    // cannot overlap a shared one.
139    unsafe {
140        memcpy(dst.as_mut_ptr(), src.as_ptr(), src.len());
141    }
142    Ok(())
143}
144
145/// Compare two byte slices for equality using SVM-optimized memcmp.
146#[inline]
147pub fn bytes_eq(a: &[u8], b: &[u8]) -> bool {
148    if a.len() != b.len() {
149        return false;
150    }
151    if a.is_empty() {
152        return true;
153    }
154    // SAFETY: The lengths were checked equal above, so both slices are valid
155    // for `a.len()` bytes.
156    unsafe { memcmp(a.as_ptr(), b.as_ptr(), a.len()) == core::cmp::Ordering::Equal }
157}
158
159/// Zero-fill account data using SVM-optimized memset.
160///
161/// Use this when you need to clear account data without closing the account.
162///
163/// Fails with `AccountBorrowFailed` if any data borrow (shared or exclusive)
164/// is outstanding: the memset would mutate memory a live `Ref`/`RefMut`
165/// still points at.
166#[inline]
167pub fn zero_account_data(
168    account: &crate::account_view::AccountView<'_>,
169) -> Result<(), ProgramError> {
170    account.check_borrow_mut()?;
171    let len = account.data_len();
172    if len == 0 {
173        return Ok(());
174    }
175    // SAFETY: no data borrow is outstanding (checked above); the pointer and
176    // length describe this account's SVM-owned data region.
177    unsafe {
178        memset(account.data_ptr_unchecked(), 0, len);
179    }
180    Ok(())
181}