hopper_native/
return_data.rs1use crate::address::Address;
8use crate::error::ProgramError;
9use crate::project::Projectable;
10use core::mem::MaybeUninit;
11
12#[cfg(feature = "cpi")]
13use crate::instruction::{InstructionView, Signer};
14
15pub const MAX_RETURN_DATA: usize = 1024;
17
18pub struct ReturnData {
24 buf: [MaybeUninit<u8>; MAX_RETURN_DATA],
27 len: usize,
29 program_id: Address,
31}
32
33impl ReturnData {
34 #[inline(always)]
36 pub fn data(&self) -> &[u8] {
37 debug_assert!(self.len <= MAX_RETURN_DATA);
40 unsafe { core::slice::from_raw_parts(self.buf.as_ptr() as *const u8, self.len) }
46 }
47
48 #[inline(always)]
50 pub fn program_id(&self) -> &Address {
51 &self.program_id
52 }
53
54 #[inline(always)]
56 pub fn len(&self) -> usize {
57 self.len
58 }
59
60 #[inline(always)]
62 pub fn is_empty(&self) -> bool {
63 self.len == 0
64 }
65
66 #[inline]
71 pub fn as_type<T: Projectable>(&self) -> Result<&T, ProgramError> {
72 let size = core::mem::size_of::<T>();
73 if self.len < size {
74 return Err(ProgramError::AccountDataTooSmall);
75 }
76
77 let data = self.data();
78 let align = core::mem::align_of::<T>();
79 let ptr = data.as_ptr();
80 if !(ptr as usize).is_multiple_of(align) {
81 return Err(ProgramError::InvalidAccountData);
82 }
83
84 Ok(unsafe { &*(ptr as *const T) })
89 }
90
91 #[inline]
95 pub fn as_type_from<T: Projectable>(
96 &self,
97 expected_program: &Address,
98 ) -> Result<&T, ProgramError> {
99 if !crate::address::address_eq(self.program_id(), expected_program) {
100 return Err(ProgramError::IncorrectProgramId);
101 }
102 self.as_type::<T>()
103 }
104
105 #[inline]
107 pub fn as_u64(&self) -> Result<u64, ProgramError> {
108 if self.len < 8 {
109 return Err(ProgramError::AccountDataTooSmall);
110 }
111 let mut bytes = [0u8; 8];
112 bytes.copy_from_slice(&self.data()[..8]);
113 Ok(u64::from_le_bytes(bytes))
114 }
115
116 #[inline]
118 pub fn as_u32(&self) -> Result<u32, ProgramError> {
119 if self.len < 4 {
120 return Err(ProgramError::AccountDataTooSmall);
121 }
122 let mut bytes = [0u8; 4];
123 bytes.copy_from_slice(&self.data()[..4]);
124 Ok(u32::from_le_bytes(bytes))
125 }
126}
127
128#[inline]
137pub fn get_return_data() -> Option<ReturnData> {
138 #[allow(unused_mut)]
139 let mut rd = ReturnData {
140 buf: [const { MaybeUninit::uninit() }; MAX_RETURN_DATA],
141 len: 0,
142 program_id: Address::default(),
143 };
144
145 #[cfg(target_os = "solana")]
146 {
147 let actual_len = unsafe {
152 crate::syscalls::sol_get_return_data(
153 rd.buf.as_mut_ptr() as *mut u8,
154 MAX_RETURN_DATA as u64,
155 rd.program_id.0.as_mut_ptr(),
156 )
157 };
158 rd.len = (actual_len as usize).min(MAX_RETURN_DATA);
159 }
160
161 #[cfg(not(target_os = "solana"))]
162 {
163 }
166
167 if rd.len == 0 {
168 None
169 } else {
170 Some(rd)
171 }
172}
173
174#[cfg(feature = "cpi")]
187#[inline]
188pub fn invoke_and_read<T: Projectable, const ACCOUNTS: usize>(
189 instruction: &InstructionView<'_, '_, '_, '_>,
190 account_views: &[&crate::account_view::AccountView<'_>; ACCOUNTS],
191 signers_seeds: &[Signer<'_, '_>],
192) -> Result<ReturnData, ProgramError> {
193 crate::cpi::invoke_signed::<ACCOUNTS>(instruction, account_views, signers_seeds)?;
194
195 let returned = get_return_data().ok_or(ProgramError::InvalidAccountData)?;
196 returned.as_type_from::<T>(instruction.program_id)?;
197 Ok(returned)
198}
199
200#[cfg(test)]
201impl ReturnData {
202 fn test_snapshot(bytes: &[u8], program_id: Address) -> Self {
205 assert!(bytes.len() <= MAX_RETURN_DATA);
206 let mut buf = [const { MaybeUninit::uninit() }; MAX_RETURN_DATA];
207 for (dst, src) in buf.iter_mut().zip(bytes) {
208 dst.write(*src);
209 }
210 ReturnData {
211 buf,
212 len: bytes.len(),
213 program_id,
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn typed_return_requires_the_expected_producer_and_initialized_type() {
224 let expected = Address::new_from_array([1; 32]);
225 let nested = Address::new_from_array([2; 32]);
226 let correct = ReturnData::test_snapshot(&42u64.to_le_bytes(), expected.clone());
227 assert_eq!(*correct.as_type_from::<u64>(&expected).unwrap(), 42);
228 assert_eq!(
229 correct.as_type_from::<u64>(&nested),
230 Err(ProgramError::IncorrectProgramId)
231 );
232 let short = ReturnData::test_snapshot(&[42], expected.clone());
233 assert_eq!(
234 short.as_type_from::<u64>(&expected),
235 Err(ProgramError::AccountDataTooSmall)
236 );
237 let forwarded = ReturnData::test_snapshot(&42u64.to_le_bytes(), nested);
238 assert_eq!(
239 forwarded.as_type_from::<u64>(&expected),
240 Err(ProgramError::IncorrectProgramId)
241 );
242 }
243
244 #[test]
245 fn offchain_get_return_data_is_none() {
246 assert!(get_return_data().is_none());
247 }
248
249 #[test]
250 fn data_exposes_exactly_the_written_prefix() {
251 let payload = [0xAB, 0xCD, 0xEF];
252 let rd = ReturnData::test_snapshot(&payload, Address::default());
253 assert_eq!(rd.data(), &payload);
254 assert_eq!(rd.len(), payload.len());
255 assert!(!rd.is_empty());
256 }
257
258 #[test]
259 fn as_u64_and_as_u32_never_read_past_the_prefix() {
260 let short = ReturnData::test_snapshot(&[1, 2, 3], Address::default());
261 assert!(short.as_u64().is_err());
262 assert!(short.as_u32().is_err());
263
264 let rd = ReturnData::test_snapshot(&7u64.to_le_bytes(), Address::default());
265 assert_eq!(rd.as_u64().unwrap(), 7);
266 assert_eq!(rd.as_u32().unwrap(), 7);
267 }
268
269 #[test]
270 fn as_type_length_checks_against_the_prefix() {
271 let rd = ReturnData::test_snapshot(&[5u8], Address::default());
272 assert!(rd.as_type::<u64>().is_err());
273 assert_eq!(*rd.as_type::<u8>().unwrap(), 5);
274 }
275}