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