Skip to main content

sbpf_runtime/syscalls/
log.rs

1use {
2    crate::{config::ExecutionCost, runtime::LogCollector},
3    sbpf_vm::{compute::ComputeMeter, errors::SbpfVmResult, memory::Memory},
4};
5
6pub fn sol_log(
7    registers: [u64; 5],
8    memory: &Memory,
9    compute: &ComputeMeter,
10    costs: &ExecutionCost,
11    log_collector: &LogCollector,
12) -> SbpfVmResult<u64> {
13    let msg_ptr = registers[0];
14    let msg_len = registers[1];
15
16    compute.consume(costs.syscall_base_cost.max(msg_len))?;
17
18    let msg_bytes = memory.read_bytes(msg_ptr, msg_len as usize)?;
19    let msg = String::from_utf8_lossy(msg_bytes);
20    log_collector
21        .borrow_mut()
22        .push(format!("Program log: {}", msg));
23    Ok(0)
24}
25
26pub fn sol_log_64(
27    registers: [u64; 5],
28    compute: &ComputeMeter,
29    costs: &ExecutionCost,
30    log_collector: &LogCollector,
31) -> SbpfVmResult<u64> {
32    compute.consume(costs.log_64_units)?;
33    log_collector.borrow_mut().push(format!(
34        "Program log: {:#x}, {:#x}, {:#x}, {:#x}, {:#x}",
35        registers[0], registers[1], registers[2], registers[3], registers[4]
36    ));
37    Ok(0)
38}
39
40pub fn sol_log_pubkey(
41    registers: [u64; 5],
42    memory: &Memory,
43    compute: &ComputeMeter,
44    costs: &ExecutionCost,
45    log_collector: &LogCollector,
46) -> SbpfVmResult<u64> {
47    compute.consume(costs.log_pubkey_units)?;
48
49    let pubkey_bytes = memory.read_bytes(registers[0], 32)?;
50    let pubkey_base58 = bs58::encode(pubkey_bytes).into_string();
51    log_collector
52        .borrow_mut()
53        .push(format!("Program log: {}", pubkey_base58));
54    Ok(0)
55}
56
57pub fn sol_log_compute_units(
58    compute: &ComputeMeter,
59    costs: &ExecutionCost,
60    log_collector: &LogCollector,
61) -> SbpfVmResult<u64> {
62    compute.consume(costs.syscall_base_cost)?;
63    log_collector.borrow_mut().push(format!(
64        "Program consumption: {} units remaining",
65        compute.get_remaining()
66    ));
67    Ok(0)
68}
69
70pub fn sol_remaining_compute_units(
71    compute: &ComputeMeter,
72    costs: &ExecutionCost,
73) -> SbpfVmResult<u64> {
74    compute.consume(costs.syscall_base_cost)?;
75    Ok(compute.get_remaining())
76}
77
78#[cfg(test)]
79mod tests {
80    use {
81        super::*,
82        crate::{
83            runtime::LogCollector,
84            syscalls::tests::test_helpers::{costs, make_memory, meter},
85        },
86        sbpf_vm::{errors::SbpfVmError, memory::Memory},
87        std::{cell::RefCell, rc::Rc},
88    };
89
90    fn new_log() -> LogCollector {
91        Rc::new(RefCell::new(Vec::new()))
92    }
93
94    #[test]
95    fn test_sol_log_basic() {
96        let mut memory = make_memory();
97        let msg = b"hello world";
98        memory.write_bytes(Memory::HEAP_START, msg).unwrap();
99
100        let log = new_log();
101        let registers = [Memory::HEAP_START, msg.len() as u64, 0, 0, 0];
102        sol_log(registers, &memory, &meter(1_000_000), &costs(), &log).unwrap();
103
104        assert_eq!(log.borrow()[0], "Program log: hello world");
105    }
106
107    #[test]
108    fn test_sol_log_empty_message() {
109        let memory = make_memory();
110        let log = new_log();
111        let registers = [Memory::HEAP_START, 0, 0, 0, 0];
112        sol_log(registers, &memory, &meter(1_000_000), &costs(), &log).unwrap();
113
114        assert_eq!(log.borrow()[0], "Program log: ");
115    }
116
117    #[test]
118    fn test_sol_log_invalid_utf8_is_lossy() {
119        let mut memory = make_memory();
120        memory.write_bytes(Memory::HEAP_START, &[0xFF]).unwrap();
121        let log = new_log();
122        let registers = [Memory::HEAP_START, 1, 0, 0, 0];
123        sol_log(registers, &memory, &meter(1_000_000), &costs(), &log).unwrap();
124
125        assert!(log.borrow()[0].starts_with("Program log: "));
126    }
127
128    #[test]
129    fn test_sol_log_compute_exhausted() {
130        let memory = make_memory();
131        let log = new_log();
132        let registers = [Memory::HEAP_START, 0, 0, 0, 0];
133        assert!(matches!(
134            sol_log(registers, &memory, &meter(99), &costs(), &log),
135            Err(SbpfVmError::ComputeBudgetExceeded { .. })
136        ));
137    }
138
139    #[test]
140    fn test_sol_log_64_format() {
141        let log = new_log();
142        let registers = [1, 2, 3, 4, 5];
143        sol_log_64(registers, &meter(1_000_000), &costs(), &log).unwrap();
144
145        assert_eq!(log.borrow()[0], "Program log: 0x1, 0x2, 0x3, 0x4, 0x5");
146    }
147
148    #[test]
149    fn test_sol_log_64_zeros() {
150        let log = new_log();
151        sol_log_64([0, 0, 0, 0, 0], &meter(1_000_000), &costs(), &log).unwrap();
152        assert_eq!(log.borrow()[0], "Program log: 0x0, 0x0, 0x0, 0x0, 0x0");
153    }
154
155    #[test]
156    fn test_sol_log_64_compute_exhausted() {
157        let log = new_log();
158        assert!(matches!(
159            sol_log_64([0; 5], &meter(99), &costs(), &log),
160            Err(SbpfVmError::ComputeBudgetExceeded { .. })
161        ));
162    }
163
164    #[test]
165    fn test_sol_log_pubkey_formats_base58() {
166        let mut memory = make_memory();
167        let pubkey = [0u8; 32];
168        memory.write_bytes(Memory::HEAP_START, &pubkey).unwrap();
169
170        let log = new_log();
171        let registers = [Memory::HEAP_START, 0, 0, 0, 0];
172        sol_log_pubkey(registers, &memory, &meter(1_000_000), &costs(), &log).unwrap();
173
174        let expected = bs58::encode(&pubkey).into_string();
175        assert_eq!(log.borrow()[0], format!("Program log: {}", expected));
176    }
177
178    #[test]
179    fn test_sol_log_pubkey_nonzero() {
180        let mut memory = make_memory();
181        let mut pubkey = [0u8; 32];
182        pubkey[0] = 1;
183        memory.write_bytes(Memory::HEAP_START, &pubkey).unwrap();
184
185        let log = new_log();
186        let registers = [Memory::HEAP_START, 0, 0, 0, 0];
187        sol_log_pubkey(registers, &memory, &meter(1_000_000), &costs(), &log).unwrap();
188
189        let expected = bs58::encode(&pubkey).into_string();
190        assert_eq!(log.borrow()[0], format!("Program log: {}", expected));
191    }
192
193    #[test]
194    fn test_sol_log_compute_units_logs_remaining() {
195        let compute = meter(1_000_000);
196        let log = new_log();
197        sol_log_compute_units(&compute, &costs(), &log).unwrap();
198
199        // After consuming syscall_base_cost=100, remaining = 999_900
200        let msg = &log.borrow()[0];
201        assert!(msg.starts_with("Program consumption:"));
202        assert!(msg.contains("999900 units remaining"));
203    }
204
205    #[test]
206    fn test_sol_remaining_compute_units_returns_remaining() {
207        let compute = meter(1_000);
208        let result = sol_remaining_compute_units(&compute, &costs()).unwrap();
209        assert_eq!(result, 900);
210    }
211
212    #[test]
213    fn test_sol_remaining_compute_units_exhausted() {
214        let compute = meter(50);
215        assert!(matches!(
216            sol_remaining_compute_units(&compute, &costs()),
217            Err(SbpfVmError::ComputeBudgetExceeded { .. })
218        ));
219    }
220}