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