1use crate::address::Address;
20use crate::error::ProgramError;
21
22#[inline(always)]
29pub fn get_stack_height() -> u64 {
30 #[cfg(target_os = "solana")]
31 {
32 unsafe { crate::syscalls::sol_get_stack_height() }
34 }
35 #[cfg(not(target_os = "solana"))]
36 {
37 1 }
39}
40
41#[inline(always)]
44pub fn is_top_level() -> bool {
45 get_stack_height() <= 1
46}
47
48#[inline(always)]
50pub fn is_cpi() -> bool {
51 get_stack_height() > 1
52}
53
54#[inline(always)]
60pub fn require_top_level() -> Result<(), ProgramError> {
61 if is_top_level() {
62 Ok(())
63 } else {
64 Err(ProgramError::InvalidArgument)
65 }
66}
67
68#[inline(always)]
73pub fn require_cpi() -> Result<(), ProgramError> {
74 if is_cpi() {
75 Ok(())
76 } else {
77 Err(ProgramError::InvalidArgument)
78 }
79}
80
81#[derive(Clone, Debug)]
85pub struct ProcessedInstruction {
86 pub program_id: Address,
88 pub data: [u8; 1232],
90 pub data_len: usize,
92 pub accounts_len: usize,
94}
95
96#[repr(C)]
100#[derive(Clone, Debug, Default, PartialEq, Eq)]
101pub struct ProcessedInstructionAccount {
102 pub address: Address,
103 pub is_signer: bool,
104 pub is_writable: bool,
105}
106
107const _: () = {
108 assert!(core::mem::size_of::<ProcessedInstructionAccount>() == 34);
109 assert!(core::mem::align_of::<ProcessedInstructionAccount>() == 1);
110 assert!(core::mem::offset_of!(ProcessedInstructionAccount, address) == 0);
111 assert!(core::mem::offset_of!(ProcessedInstructionAccount, is_signer) == 32);
112 assert!(core::mem::offset_of!(ProcessedInstructionAccount, is_writable) == 33);
113};
114
115#[derive(Debug)]
121pub struct ProcessedInstructionView<'a> {
122 pub program_id: Address,
123 pub data: &'a [u8],
124 pub accounts: &'a [ProcessedInstructionAccount],
125}
126
127#[inline]
140pub fn get_processed_instruction_into<'a>(
141 index: u64,
142 data: &'a mut [u8],
143 accounts: &'a mut [ProcessedInstructionAccount],
144) -> Result<Option<ProcessedInstructionView<'a>>, ProgramError> {
145 read_processed_with(index, data, accounts, sibling_syscall)
146}
147
148fn sibling_syscall(
149 index: u64,
150 meta: &mut ProcessedInstructionMeta,
151 program: &mut Address,
152 data: &mut [u8],
153 accounts: &mut [ProcessedInstructionAccount],
154) -> u64 {
155 #[cfg(target_os = "solana")]
156 {
157 unsafe {
161 crate::syscalls::sol_get_processed_sibling_instruction(
162 index,
163 meta as *mut _ as *mut u8,
164 program.0.as_mut_ptr(),
165 data.as_mut_ptr(),
166 accounts.as_mut_ptr().cast(),
167 )
168 }
169 }
170 #[cfg(not(target_os = "solana"))]
171 {
172 let _ = (index, meta, program, data, accounts);
173 0
174 }
175}
176
177fn read_processed_with<'a>(
178 index: u64,
179 data: &'a mut [u8],
180 accounts: &'a mut [ProcessedInstructionAccount],
181 mut syscall: impl FnMut(
182 u64,
183 &mut ProcessedInstructionMeta,
184 &mut Address,
185 &mut [u8],
186 &mut [ProcessedInstructionAccount],
187 ) -> u64,
188) -> Result<Option<ProcessedInstructionView<'a>>, ProgramError> {
189 let mut meta = ProcessedInstructionMeta {
190 data_len: 0,
191 accounts_len: 0,
192 };
193 let mut program_id = Address::default();
194 let mut probe_data = [0];
196 let mut probe_accounts = [ProcessedInstructionAccount::default()];
197 match syscall(
198 index,
199 &mut meta,
200 &mut program_id,
201 &mut probe_data,
202 &mut probe_accounts,
203 ) {
204 0 => return Ok(None),
205 1 => {}
206 _ => return Err(ProgramError::InvalidAccountData),
207 }
208 let data_len = usize::try_from(meta.data_len).map_err(|_| ProgramError::AccountDataTooSmall)?;
209 let accounts_len =
210 usize::try_from(meta.accounts_len).map_err(|_| ProgramError::AccountDataTooSmall)?;
211 if data_len > data.len() || accounts_len > accounts.len() {
212 return Err(ProgramError::AccountDataTooSmall);
213 }
214 let rc = syscall(
215 index,
216 &mut meta,
217 &mut program_id,
218 &mut data[..data_len],
219 &mut accounts[..accounts_len],
220 );
221 if rc != 1 || meta.data_len != data_len as u64 || meta.accounts_len != accounts_len as u64 {
222 return Err(ProgramError::InvalidAccountData);
223 }
224 Ok(Some(ProcessedInstructionView {
225 program_id,
226 data: &data[..data_len],
227 accounts: &accounts[..accounts_len],
228 }))
229}
230
231#[inline]
237pub fn get_processed_instruction(index: u64) -> Option<ProcessedInstruction> {
238 let mut data = [0; 1232];
239 let mut accounts = core::array::from_fn::<_, 64, _>(|_| ProcessedInstructionAccount::default());
240 let view = get_processed_instruction_into(index, &mut data, &mut accounts).ok()??;
241 let program_id = view.program_id;
242 let data_len = view.data.len();
243 let accounts_len = view.accounts.len();
244 Some(ProcessedInstruction {
245 program_id,
246 data,
247 data_len,
248 accounts_len,
249 })
250}
251
252pub const ED25519_PROGRAM_ID: Address =
254 crate::address!("Ed25519SigVerify111111111111111111111111111");
255
256pub const SECP256K1_PROGRAM_ID: Address =
258 crate::address!("KeccakSecp256k11111111111111111111111111111");
259
260pub const SECP256R1_PROGRAM_ID: Address =
264 crate::address!("Secp256r1SigVerify1111111111111111111111111");
265
266#[inline]
274pub fn require_ed25519_instruction(
275 sibling_index: u64,
276) -> Result<ProcessedInstruction, ProgramError> {
277 let ix = get_processed_instruction(sibling_index).ok_or(ProgramError::InvalidArgument)?;
278
279 if !crate::address::address_eq(&ix.program_id, &ED25519_PROGRAM_ID) {
280 return Err(ProgramError::IncorrectProgramId);
281 }
282
283 Ok(ix)
284}
285
286#[inline]
289pub fn require_secp256k1_instruction(
290 sibling_index: u64,
291) -> Result<ProcessedInstruction, ProgramError> {
292 let ix = get_processed_instruction(sibling_index).ok_or(ProgramError::InvalidArgument)?;
293
294 if !crate::address::address_eq(&ix.program_id, &SECP256K1_PROGRAM_ID) {
295 return Err(ProgramError::IncorrectProgramId);
296 }
297
298 Ok(ix)
299}
300
301#[inline]
311pub fn require_secp256r1_instruction(
312 sibling_index: u64,
313) -> Result<ProcessedInstruction, ProgramError> {
314 let ix = get_processed_instruction(sibling_index).ok_or(ProgramError::InvalidArgument)?;
315
316 if !crate::address::address_eq(&ix.program_id, &SECP256R1_PROGRAM_ID) {
317 return Err(ProgramError::IncorrectProgramId);
318 }
319
320 Ok(ix)
321}
322
323#[repr(C)]
326#[allow(dead_code)]
327struct ProcessedInstructionMeta {
328 data_len: u64,
329 accounts_len: u64,
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn absence_does_not_fabricate_an_instruction_or_touch_outputs() {
338 let mut data = [0xa5; 8];
339 let mut accounts = [ProcessedInstructionAccount::default()];
340 let mut calls = 0;
341 let result = read_processed_with(7, &mut data, &mut accounts, |index, _, _, _, _| {
342 assert_eq!(index, 7);
343 calls += 1;
344 0
345 })
346 .unwrap();
347 assert!(result.is_none());
348 assert_eq!(calls, 1);
349 assert_eq!(data, [0xa5; 8]);
350 assert_eq!(accounts, [ProcessedInstructionAccount::default()]);
351 assert!(get_processed_instruction(0).is_none());
352 }
353
354 #[test]
355 fn probes_then_copies_exact_lengths_and_preserves_unused_capacity() {
356 let mut data = [0xa5; 8];
357 let mut accounts =
358 core::array::from_fn::<_, 3, _>(|_| ProcessedInstructionAccount::default());
359 let expected = ProcessedInstructionAccount {
360 address: Address::new_from_array([9; 32]),
361 is_signer: true,
362 is_writable: false,
363 };
364 let mut calls = 0;
365 let view = read_processed_with(
366 2,
367 &mut data,
368 &mut accounts,
369 |index, meta, program, bytes, metas| {
370 assert_eq!(index, 2);
371 calls += 1;
372 if calls == 1 {
373 assert_eq!((meta.data_len, meta.accounts_len), (0, 0));
374 meta.data_len = 3;
375 meta.accounts_len = 1;
376 } else {
377 assert_eq!((meta.data_len, meta.accounts_len), (3, 1));
378 assert_eq!((bytes.len(), metas.len()), (3, 1));
379 *program = Address::new_from_array([7; 32]);
380 bytes.copy_from_slice(&[4, 5, 6]);
381 metas[0] = expected.clone();
382 }
383 1
384 },
385 )
386 .unwrap()
387 .unwrap();
388 assert_eq!(calls, 2);
389 assert_eq!(view.program_id, Address::new_from_array([7; 32]));
390 assert_eq!(view.data, &[4, 5, 6]);
391 assert_eq!(view.accounts, &[expected]);
392 assert_eq!(&data[3..], &[0xa5; 5]);
393 assert_eq!(
394 &accounts[1..],
395 &[
396 ProcessedInstructionAccount::default(),
397 ProcessedInstructionAccount::default()
398 ]
399 );
400 }
401
402 #[test]
403 fn insufficient_buffers_are_rejected_before_copy() {
404 for (data_len, accounts_len) in [(9, 1), (3, 2), (u64::MAX, 0), (0, u64::MAX)] {
405 let mut calls = 0;
406 let mut data = [0xa5; 8];
407 let mut accounts = [ProcessedInstructionAccount::default()];
408 let result = read_processed_with(0, &mut data, &mut accounts, |_, meta, _, _, _| {
409 calls += 1;
410 meta.data_len = data_len;
411 meta.accounts_len = accounts_len;
412 1
413 });
414 assert_eq!(result.unwrap_err(), ProgramError::AccountDataTooSmall);
415 assert_eq!(calls, 1);
416 assert_eq!(data, [0xa5; 8]);
417 }
418 }
419
420 #[test]
421 fn zero_length_sibling_is_distinct_from_absence() {
422 let mut calls = 0;
423 let view = read_processed_with(0, &mut [], &mut [], |_, meta, program, _, _| {
424 calls += 1;
425 assert_eq!((meta.data_len, meta.accounts_len), (0, 0));
426 *program = Address::new_from_array([8; 32]);
427 1
428 })
429 .unwrap()
430 .unwrap();
431 assert_eq!(calls, 2);
432 assert_eq!(view.program_id, Address::new_from_array([8; 32]));
433 assert!(view.data.is_empty() && view.accounts.is_empty());
434 }
435
436 #[test]
437 fn unexpected_return_or_changing_lengths_fail_closed() {
438 for (probe_rc, copy_rc, change_lengths) in
439 [(2, 1, false), (1, 0, false), (1, 2, false), (1, 1, true)]
440 {
441 let mut calls = 0;
442 let mut data = [0; 8];
443 let mut accounts = [ProcessedInstructionAccount::default()];
444 let result = read_processed_with(0, &mut data, &mut accounts, |_, meta, _, _, _| {
445 calls += 1;
446 if calls == 1 {
447 meta.data_len = 3;
448 probe_rc
449 } else {
450 if change_lengths {
451 meta.data_len = 4;
452 }
453 copy_rc
454 }
455 });
456 assert_eq!(result.unwrap_err(), ProgramError::InvalidAccountData);
457 }
458 }
459}