Skip to main content

hopper_runtime/
log.rs

1//! Hopper logging helpers.
2//!
3//! Two tiers are exposed:
4//!
5//! - [`log`] for arbitrary UTF-8 text through the active backend's
6//!   `sol_log_` syscall.
7//! - [`log_64`] for integer-heavy logs through the five-u64 `sol_log_64_`
8//!   syscall, which is the cheapest structured-log path on Solana. This
9//!   backs the `hopper_log!` macro's "label + values" form and lets
10//!   hot handlers emit telemetry without the `core::fmt::Write` setup
11//!   cost that `msg!` pays.
12
13/// Log a UTF-8 message through Hopper's direct runtime.
14#[inline(always)]
15pub fn log(message: &str) {
16    #[cfg(target_os = "solana")]
17    // 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.
18    unsafe {
19        hopper_native::syscalls::sol_log_(message.as_ptr(), message.len() as u64);
20    }
21
22    #[cfg(not(target_os = "solana"))]
23    {
24        let _ = message;
25    }
26}
27
28/// Log up to five `u64` values through the `sol_log_64_` syscall.
29///
30/// One syscall, no allocation, no format parsing. Pad unused slots
31/// with zero. The Solana runtime renders the five values as a single
32/// line "Program log: 0x... 0x... ...". Use this as the tight-loop
33/// escape hatch when the output is going to be grep'd, not read.
34///
35/// ```ignore
36/// // Emit "balance, delta, new_balance":
37/// hopper_runtime::log::log_64(balance, delta, new_balance, 0, 0);
38/// ```
39#[inline(always)]
40pub fn log_64(a: u64, b: u64, c: u64, d: u64, e: u64) {
41    #[cfg(target_os = "solana")]
42    // 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.
43    unsafe {
44        hopper_native::syscalls::sol_log_64_(a, b, c, d, e);
45    }
46
47    #[cfg(not(target_os = "solana"))]
48    {
49        let _ = (a, b, c, d, e);
50    }
51}
52
53/// Stack-allocated write buffer for formatted log messages.
54pub struct StackWriter<'a> {
55    buf: &'a mut [u8],
56    pos: usize,
57}
58
59impl<'a> StackWriter<'a> {
60    #[inline(always)]
61    pub fn new(buf: &'a mut [u8]) -> Self {
62        Self { buf, pos: 0 }
63    }
64
65    #[inline(always)]
66    pub fn pos(&self) -> usize {
67        self.pos
68    }
69}
70
71impl core::fmt::Write for StackWriter<'_> {
72    fn write_str(&mut self, s: &str) -> core::fmt::Result {
73        let bytes = s.as_bytes();
74        let remaining = self.buf.len().saturating_sub(self.pos);
75        let to_write = bytes.len().min(remaining);
76        self.buf[self.pos..self.pos + to_write].copy_from_slice(&bytes[..to_write]);
77        self.pos += to_write;
78        Ok(())
79    }
80}