Skip to main content

sbpf_runtime/syscalls/
return_data.rs

1use {
2    crate::{config::ExecutionCost, cpi::ReturnData},
3    sbpf_vm::{compute::ComputeMeter, errors::SbpfVmResult, memory::Memory},
4    solana_address::Address,
5};
6
7const MAX_RETURN_DATA: usize = 1024;
8
9pub fn sol_set_return_data(
10    registers: [u64; 5],
11    memory: &Memory,
12    compute: &ComputeMeter,
13    costs: &ExecutionCost,
14    program_id: &Address,
15) -> SbpfVmResult<(u64, ReturnData)> {
16    let addr = registers[0];
17    let len = registers[1];
18
19    let cost = len
20        .checked_div(costs.cpi_bytes_per_unit)
21        .unwrap_or(u64::MAX)
22        .saturating_add(costs.syscall_base_cost);
23    compute.consume(cost)?;
24
25    if len > MAX_RETURN_DATA as u64 {
26        return Err(sbpf_vm::errors::SbpfVmError::SyscallError(format!(
27            "Return data too large: {} > {}",
28            len, MAX_RETURN_DATA
29        )));
30    }
31
32    let data = if len == 0 {
33        Vec::new()
34    } else {
35        memory.read_bytes(addr, len as usize)?.to_vec()
36    };
37
38    Ok((0, Some((*program_id, data))))
39}
40
41pub fn sol_get_return_data(
42    registers: [u64; 5],
43    memory: &mut Memory,
44    compute: &ComputeMeter,
45    costs: &ExecutionCost,
46    return_data: &ReturnData,
47) -> SbpfVmResult<u64> {
48    let buf_addr = registers[0];
49    let buf_len = registers[1];
50    let program_id_addr = registers[2];
51
52    compute.consume(costs.syscall_base_cost)?;
53
54    let Some((program_id, data)) = return_data else {
55        return Ok(0);
56    };
57
58    let length = buf_len.min(data.len() as u64);
59
60    if length != 0 {
61        let cost = length
62            .saturating_add(32)
63            .checked_div(costs.cpi_bytes_per_unit)
64            .unwrap_or(u64::MAX);
65        compute.consume(cost)?;
66
67        let from_slice = &data[..length as usize];
68        memory.write_bytes(buf_addr, from_slice)?;
69        memory.write_bytes(program_id_addr, program_id.as_ref())?;
70    }
71
72    Ok(data.len() as u64)
73}
74
75#[cfg(test)]
76mod tests {
77    use {
78        super::*,
79        crate::syscalls::tests::test_helpers::{costs, make_memory, meter},
80        sbpf_vm::{errors::SbpfVmError, memory::Memory},
81        solana_address::Address,
82    };
83
84    fn test_program_id() -> Address {
85        Address::from([7u8; 32])
86    }
87
88    #[test]
89    fn test_set_return_data_basic() {
90        let mut memory = make_memory();
91        let data = b"result";
92        memory.write_bytes(Memory::HEAP_START, data).unwrap();
93
94        let program_id = test_program_id();
95        let registers = [Memory::HEAP_START, data.len() as u64, 0, 0, 0];
96        let (ret, rd) =
97            sol_set_return_data(registers, &memory, &meter(1_000_000), &costs(), &program_id)
98                .unwrap();
99
100        assert_eq!(ret, 0);
101        let (stored_id, stored_data) = rd.unwrap();
102        assert_eq!(stored_id, program_id);
103        assert_eq!(stored_data, data);
104    }
105
106    #[test]
107    fn test_set_return_data_empty() {
108        let memory = make_memory();
109        let program_id = test_program_id();
110        let registers = [Memory::HEAP_START, 0, 0, 0, 0];
111        let (ret, rd) =
112            sol_set_return_data(registers, &memory, &meter(1_000_000), &costs(), &program_id)
113                .unwrap();
114
115        assert_eq!(ret, 0);
116        let (_, stored_data) = rd.unwrap();
117        assert!(stored_data.is_empty());
118    }
119
120    #[test]
121    fn test_set_return_data_too_large() {
122        let memory = make_memory();
123        let program_id = test_program_id();
124        // MAX_RETURN_DATA = 1024; pass 1025
125        let registers = [Memory::HEAP_START, 1025, 0, 0, 0];
126        let result =
127            sol_set_return_data(registers, &memory, &meter(1_000_000), &costs(), &program_id);
128        assert!(matches!(result, Err(SbpfVmError::SyscallError(_))));
129    }
130
131    #[test]
132    fn test_set_return_data_compute_exhausted() {
133        let memory = make_memory();
134        let program_id = test_program_id();
135        // cost = 0/250 + syscall_base_cost=100; budget=99 fails
136        let registers = [Memory::HEAP_START, 0, 0, 0, 0];
137        assert!(matches!(
138            sol_set_return_data(registers, &memory, &meter(99), &costs(), &program_id),
139            Err(SbpfVmError::ComputeBudgetExceeded { .. })
140        ));
141    }
142
143    #[test]
144    fn test_get_return_data_none() {
145        let mut memory = make_memory();
146        let registers = [Memory::HEAP_START, 32, Memory::HEAP_START + 64, 0, 0];
147        let ret = sol_get_return_data(registers, &mut memory, &meter(1_000_000), &costs(), &None)
148            .unwrap();
149        assert_eq!(ret, 0);
150    }
151
152    #[test]
153    fn test_get_return_data_full() {
154        let mut memory = make_memory();
155        let program_id = test_program_id();
156        let data = vec![1u8, 2, 3, 4, 5];
157        let return_data: ReturnData = Some((program_id, data.clone()));
158
159        let buf_addr = Memory::HEAP_START;
160        let pid_addr = Memory::HEAP_START + 64;
161        let registers = [buf_addr, data.len() as u64, pid_addr, 0, 0];
162        let ret = sol_get_return_data(
163            registers,
164            &mut memory,
165            &meter(1_000_000),
166            &costs(),
167            &return_data,
168        )
169        .unwrap();
170
171        assert_eq!(ret, data.len() as u64);
172        assert_eq!(
173            memory.read_bytes(buf_addr, data.len()).unwrap(),
174            data.as_slice()
175        );
176        assert_eq!(
177            memory.read_bytes(pid_addr, 32).unwrap(),
178            program_id.as_ref()
179        );
180    }
181
182    #[test]
183    fn test_get_return_data_truncated_buf() {
184        let mut memory = make_memory();
185        let program_id = test_program_id();
186        let data = vec![10u8, 20, 30, 40, 50];
187        let return_data: ReturnData = Some((program_id, data.clone()));
188
189        let buf_addr = Memory::HEAP_START;
190        let pid_addr = Memory::HEAP_START + 64;
191        // buf_len=3; should write only first 3 bytes but return full 5
192        let registers = [buf_addr, 3, pid_addr, 0, 0];
193        let ret = sol_get_return_data(
194            registers,
195            &mut memory,
196            &meter(1_000_000),
197            &costs(),
198            &return_data,
199        )
200        .unwrap();
201
202        assert_eq!(ret, 5, "returns full data length even when buf is smaller");
203        assert_eq!(memory.read_bytes(buf_addr, 3).unwrap(), &[10, 20, 30]);
204    }
205
206    #[test]
207    fn test_get_return_data_zero_buf_len() {
208        let mut memory = make_memory();
209        let program_id = test_program_id();
210        let data = vec![1u8, 2, 3];
211        let return_data: ReturnData = Some((program_id, data.clone()));
212
213        // buf_len=0 → length=0 → no write, but returns data.len()
214        let registers = [Memory::HEAP_START, 0, Memory::HEAP_START + 64, 0, 0];
215        let ret = sol_get_return_data(
216            registers,
217            &mut memory,
218            &meter(1_000_000),
219            &costs(),
220            &return_data,
221        )
222        .unwrap();
223        assert_eq!(ret, 3);
224    }
225
226    #[test]
227    fn test_get_return_data_compute_exhausted() {
228        let mut memory = make_memory();
229        let registers = [Memory::HEAP_START, 32, Memory::HEAP_START + 64, 0, 0];
230        // syscall_base_cost=100; budget=99 fails
231        assert!(matches!(
232            sol_get_return_data(registers, &mut memory, &meter(99), &costs(), &None),
233            Err(SbpfVmError::ComputeBudgetExceeded { .. })
234        ));
235    }
236}