hopper_runtime/
return_data.rs1use crate::{Address, Pod, ProgramError, ProgramResult};
7use core::mem::MaybeUninit;
8
9pub const MAX_RETURN_DATA: usize = 1024;
11
12#[derive(Clone)]
18pub struct ReturnData {
19 program_id: Address,
20 data: [MaybeUninit<u8>; MAX_RETURN_DATA],
21 data_len: usize,
22 actual_len: usize,
23}
24
25impl ReturnData {
26 #[inline(always)]
28 pub const fn program_id(&self) -> &Address {
29 &self.program_id
30 }
31
32 #[inline(always)]
34 pub fn data(&self) -> &[u8] {
35 debug_assert!(self.data_len <= MAX_RETURN_DATA);
38 unsafe { core::slice::from_raw_parts(self.data.as_ptr() as *const u8, self.data_len) }
44 }
45
46 #[inline(always)]
48 pub const fn len(&self) -> usize {
49 self.data_len
50 }
51
52 #[inline(always)]
54 pub const fn actual_len(&self) -> usize {
55 self.actual_len
56 }
57
58 #[inline(always)]
60 pub const fn is_empty(&self) -> bool {
61 self.data_len == 0
62 }
63
64 #[inline(always)]
66 pub const fn is_truncated(&self) -> bool {
67 self.actual_len > self.data_len
68 }
69
70 #[inline]
72 pub fn copy_to(&self, dst: &mut [u8]) -> ProgramResult {
73 crate::memory::copy_bytes(dst, self.data())
74 }
75
76 #[inline]
78 pub fn read_pod<T: Pod>(&self) -> Result<T, ProgramError> {
79 let size = core::mem::size_of::<T>();
80 if self.data_len < size {
81 return Err(ProgramError::AccountDataTooSmall);
82 }
83 Ok(unsafe { core::ptr::read_unaligned(self.data().as_ptr() as *const T) })
88 }
89}
90
91impl core::fmt::Debug for ReturnData {
92 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
93 f.debug_struct("ReturnData")
96 .field("program_id", &self.program_id)
97 .field("data", &self.data())
98 .field("data_len", &self.data_len)
99 .field("actual_len", &self.actual_len)
100 .finish()
101 }
102}
103
104#[inline(always)]
106pub fn set_return_data(data: &[u8]) {
107 unsafe {
109 crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64);
110 }
111}
112
113#[inline]
115pub fn try_set_return_data(data: &[u8]) -> ProgramResult {
116 if data.len() > MAX_RETURN_DATA {
117 return Err(ProgramError::InvalidArgument);
118 }
119 set_return_data(data);
120 Ok(())
121}
122
123#[inline]
134pub fn get_return_data() -> Option<ReturnData> {
135 let mut snapshot = ReturnData {
136 program_id: Address::default(),
137 data: [const { MaybeUninit::uninit() }; MAX_RETURN_DATA],
138 data_len: 0,
139 actual_len: 0,
140 };
141
142 let actual_len = unsafe {
146 crate::syscalls::sol_get_return_data(
147 snapshot.data.as_mut_ptr() as *mut u8,
148 MAX_RETURN_DATA as u64,
149 snapshot.program_id.as_mut().as_mut_ptr(),
150 )
151 } as usize;
152
153 if actual_len == 0 {
154 return None;
158 }
159
160 snapshot.actual_len = actual_len;
161 snapshot.data_len = core::cmp::min(actual_len, MAX_RETURN_DATA);
162 Some(snapshot)
163}
164
165#[cfg(test)]
166impl ReturnData {
167 fn test_snapshot(bytes: &[u8], program_id: Address) -> Self {
170 assert!(bytes.len() <= MAX_RETURN_DATA);
171 let mut data = [const { MaybeUninit::uninit() }; MAX_RETURN_DATA];
172 for (dst, src) in data.iter_mut().zip(bytes) {
173 dst.write(*src);
174 }
175 ReturnData {
176 program_id,
177 data,
178 data_len: bytes.len(),
179 actual_len: bytes.len(),
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn offchain_get_return_data_is_none() {
190 assert!(get_return_data().is_none());
191 }
192
193 #[test]
194 fn try_set_return_data_rejects_oversized_payload() {
195 let oversized = [0u8; MAX_RETURN_DATA + 1];
196 assert!(try_set_return_data(&oversized).is_err());
197 }
198
199 #[test]
200 fn return_data_reads_pod_by_value() {
201 let snapshot = ReturnData::test_snapshot(&7u64.to_le_bytes(), Address::default());
202 let raw = snapshot.read_pod::<[u8; 8]>().unwrap();
203 assert_eq!(u64::from_le_bytes(raw), 7);
204 }
205
206 #[test]
207 fn data_exposes_exactly_the_written_prefix() {
208 let payload = [0xAB, 0xCD, 0xEF];
209 let snapshot = ReturnData::test_snapshot(&payload, Address::default());
210 assert_eq!(snapshot.data(), &payload);
211 assert_eq!(snapshot.len(), payload.len());
212 assert!(!snapshot.is_empty());
213 }
214
215 #[test]
216 fn read_pod_never_reads_past_the_prefix() {
217 let snapshot = ReturnData::test_snapshot(&[1, 2, 3], Address::default());
218 assert!(matches!(
219 snapshot.read_pod::<[u8; 4]>(),
220 Err(ProgramError::AccountDataTooSmall)
221 ));
222 assert_eq!(snapshot.read_pod::<[u8; 3]>().unwrap(), [1, 2, 3]);
223 }
224
225 #[test]
226 fn copy_to_copies_only_the_prefix() {
227 let snapshot = ReturnData::test_snapshot(&[9, 8], Address::default());
228 let mut dst = [0xFFu8; 4];
229 snapshot.copy_to(&mut dst).unwrap();
230 assert_eq!(dst, [9, 8, 0xFF, 0xFF]);
231
232 let mut too_small = [0u8; 1];
233 assert!(snapshot.copy_to(&mut too_small).is_err());
234 }
235
236 #[test]
237 fn is_truncated_reflects_runtime_reported_length() {
238 let mut snapshot = ReturnData::test_snapshot(&[0u8; 16], Address::default());
239 assert!(!snapshot.is_truncated());
240 snapshot.actual_len = MAX_RETURN_DATA + 512;
241 assert!(snapshot.is_truncated());
242 assert_eq!(snapshot.actual_len(), MAX_RETURN_DATA + 512);
243 assert_eq!(snapshot.len(), 16);
244 }
245
246 #[test]
247 fn debug_prints_only_the_initialized_prefix() {
248 let snapshot = ReturnData::test_snapshot(&[1, 2], Address::default());
249 let rendered = std::format!("{snapshot:?}");
250 assert!(rendered.contains("data: [1, 2]"));
251 assert!(rendered.contains("data_len: 2"));
252 }
253}