Skip to main content

hopper_native/
log.rs

1//! Zero-allocation logging via Solana syscalls.
2
3/// Log a UTF-8 message to the runtime log.
4#[inline(always)]
5pub fn log(message: &str) {
6    #[cfg(target_os = "solana")]
7    unsafe {
8        crate::syscalls::sol_log_(message.as_ptr(), message.len() as u64);
9    }
10    #[cfg(not(target_os = "solana"))]
11    {
12        // No-op off-chain; test harnesses capture logs separately.
13        let _ = message;
14    }
15}
16
17/// Log five u64 values for quick debugging.
18#[inline(always)]
19pub fn log_64(a: u64, b: u64, c: u64, d: u64, e: u64) {
20    #[cfg(target_os = "solana")]
21    unsafe {
22        crate::syscalls::sol_log_64_(a, b, c, d, e);
23    }
24    #[cfg(not(target_os = "solana"))]
25    {
26        let _ = (a, b, c, d, e);
27    }
28}
29
30/// Log the current compute unit consumption.
31#[inline(always)]
32pub fn log_compute_units() {
33    #[cfg(target_os = "solana")]
34    unsafe {
35        crate::syscalls::sol_log_compute_units_();
36    }
37}
38
39/// Emit structured data segments via `sol_log_data` (for events).
40#[inline(always)]
41pub fn log_data(segments: &[&[u8]]) {
42    #[cfg(target_os = "solana")]
43    unsafe {
44        crate::syscalls::sol_log_data(segments.as_ptr() as *const u8, segments.len() as u64);
45    }
46    #[cfg(not(target_os = "solana"))]
47    {
48        let _ = segments;
49    }
50}
51
52/// Convenience macro for logging. Equivalent to `hopper_native::log::log(msg)`.
53///
54/// Usage: `msg!("Hello, {}", name);`
55///
56/// On BPF, this formats into a stack buffer and calls `sol_log_`.
57/// For simple string literals, prefer `hopper_native::log::log("...")` directly.
58#[macro_export]
59macro_rules! msg {
60    ( $literal:expr ) => {
61        $crate::log::log($literal)
62    };
63    ( $fmt:expr, $($arg:tt)* ) => {{
64        // On BPF we have limited stack, so use a fixed 256-byte buffer.
65        // For string literals, the branch above avoids this entirely.
66        #[cfg(target_os = "solana")]
67        {
68            use core::fmt::Write;
69            let mut buf = [0u8; 256];
70            let mut wrapper = $crate::log::StackWriter::new(&mut buf);
71            let _ = write!(wrapper, $fmt, $($arg)*);
72            let len = wrapper.pos();
73            $crate::log::log(
74                // SAFETY: Write only produces valid UTF-8 from fmt::Display impls.
75                unsafe { core::str::from_utf8_unchecked(&buf[..len]) }
76            );
77        }
78        #[cfg(not(target_os = "solana"))]
79        {
80            let _ = ($fmt, $($arg)*);
81        }
82    }};
83}
84
85/// Stack-allocated write buffer for formatted log messages on BPF.
86pub struct StackWriter<'a> {
87    buf: &'a mut [u8],
88    pos: usize,
89}
90
91impl<'a> StackWriter<'a> {
92    /// Create a new writer over the given buffer.
93    #[inline(always)]
94    pub fn new(buf: &'a mut [u8]) -> Self {
95        Self { buf, pos: 0 }
96    }
97
98    /// Number of bytes written.
99    #[inline(always)]
100    pub fn pos(&self) -> usize {
101        self.pos
102    }
103}
104
105impl core::fmt::Write for StackWriter<'_> {
106    fn write_str(&mut self, s: &str) -> core::fmt::Result {
107        let bytes = s.as_bytes();
108        let remaining = self.buf.len() - self.pos;
109        let to_write = bytes.len().min(remaining);
110        self.buf[self.pos..self.pos + to_write].copy_from_slice(&bytes[..to_write]);
111        self.pos += to_write;
112        Ok(())
113    }
114}