use crate::address::Address;
use crate::error::ProgramError;
#[inline(always)]
pub fn get_stack_height() -> u64 {
#[cfg(target_os = "solana")]
{
unsafe { crate::syscalls::sol_get_stack_height() }
}
#[cfg(not(target_os = "solana"))]
{
1 }
}
#[inline(always)]
pub fn is_top_level() -> bool {
get_stack_height() <= 1
}
#[inline(always)]
pub fn is_cpi() -> bool {
get_stack_height() > 1
}
#[inline(always)]
pub fn require_top_level() -> Result<(), ProgramError> {
if is_top_level() {
Ok(())
} else {
Err(ProgramError::InvalidArgument)
}
}
#[inline(always)]
pub fn require_cpi() -> Result<(), ProgramError> {
if is_cpi() {
Ok(())
} else {
Err(ProgramError::InvalidArgument)
}
}
#[derive(Clone, Debug)]
pub struct ProcessedInstruction {
pub program_id: Address,
pub data: [u8; 1232],
pub data_len: usize,
pub accounts_len: usize,
}
#[repr(C)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ProcessedInstructionAccount {
pub address: Address,
pub is_signer: bool,
pub is_writable: bool,
}
const _: () = {
assert!(core::mem::size_of::<ProcessedInstructionAccount>() == 34);
assert!(core::mem::align_of::<ProcessedInstructionAccount>() == 1);
assert!(core::mem::offset_of!(ProcessedInstructionAccount, address) == 0);
assert!(core::mem::offset_of!(ProcessedInstructionAccount, is_signer) == 32);
assert!(core::mem::offset_of!(ProcessedInstructionAccount, is_writable) == 33);
};
#[derive(Debug)]
pub struct ProcessedInstructionView<'a> {
pub program_id: Address,
pub data: &'a [u8],
pub accounts: &'a [ProcessedInstructionAccount],
}
#[inline]
pub fn get_processed_instruction_into<'a>(
index: u64,
data: &'a mut [u8],
accounts: &'a mut [ProcessedInstructionAccount],
) -> Result<Option<ProcessedInstructionView<'a>>, ProgramError> {
read_processed_with(index, data, accounts, sibling_syscall)
}
fn sibling_syscall(
index: u64,
meta: &mut ProcessedInstructionMeta,
program: &mut Address,
data: &mut [u8],
accounts: &mut [ProcessedInstructionAccount],
) -> u64 {
#[cfg(target_os = "solana")]
{
unsafe {
crate::syscalls::sol_get_processed_sibling_instruction(
index,
meta as *mut _ as *mut u8,
program.0.as_mut_ptr(),
data.as_mut_ptr(),
accounts.as_mut_ptr().cast(),
)
}
}
#[cfg(not(target_os = "solana"))]
{
let _ = (index, meta, program, data, accounts);
0
}
}
fn read_processed_with<'a>(
index: u64,
data: &'a mut [u8],
accounts: &'a mut [ProcessedInstructionAccount],
mut syscall: impl FnMut(
u64,
&mut ProcessedInstructionMeta,
&mut Address,
&mut [u8],
&mut [ProcessedInstructionAccount],
) -> u64,
) -> Result<Option<ProcessedInstructionView<'a>>, ProgramError> {
let mut meta = ProcessedInstructionMeta {
data_len: 0,
accounts_len: 0,
};
let mut program_id = Address::default();
let mut probe_data = [0];
let mut probe_accounts = [ProcessedInstructionAccount::default()];
match syscall(
index,
&mut meta,
&mut program_id,
&mut probe_data,
&mut probe_accounts,
) {
0 => return Ok(None),
1 => {}
_ => return Err(ProgramError::InvalidAccountData),
}
let data_len = usize::try_from(meta.data_len).map_err(|_| ProgramError::AccountDataTooSmall)?;
let accounts_len =
usize::try_from(meta.accounts_len).map_err(|_| ProgramError::AccountDataTooSmall)?;
if data_len > data.len() || accounts_len > accounts.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let rc = syscall(
index,
&mut meta,
&mut program_id,
&mut data[..data_len],
&mut accounts[..accounts_len],
);
if rc != 1 || meta.data_len != data_len as u64 || meta.accounts_len != accounts_len as u64 {
return Err(ProgramError::InvalidAccountData);
}
Ok(Some(ProcessedInstructionView {
program_id,
data: &data[..data_len],
accounts: &accounts[..accounts_len],
}))
}
#[inline]
pub fn get_processed_instruction(index: u64) -> Option<ProcessedInstruction> {
let mut data = [0; 1232];
let mut accounts = core::array::from_fn::<_, 64, _>(|_| ProcessedInstructionAccount::default());
let view = get_processed_instruction_into(index, &mut data, &mut accounts).ok()??;
let program_id = view.program_id;
let data_len = view.data.len();
let accounts_len = view.accounts.len();
Some(ProcessedInstruction {
program_id,
data,
data_len,
accounts_len,
})
}
pub const ED25519_PROGRAM_ID: Address =
crate::address!("Ed25519SigVerify111111111111111111111111111");
pub const SECP256K1_PROGRAM_ID: Address =
crate::address!("KeccakSecp256k11111111111111111111111111111");
pub const SECP256R1_PROGRAM_ID: Address =
crate::address!("Secp256r1SigVerify1111111111111111111111111");
#[inline]
pub fn require_ed25519_instruction(
sibling_index: u64,
) -> Result<ProcessedInstruction, ProgramError> {
let ix = get_processed_instruction(sibling_index).ok_or(ProgramError::InvalidArgument)?;
if !crate::address::address_eq(&ix.program_id, &ED25519_PROGRAM_ID) {
return Err(ProgramError::IncorrectProgramId);
}
Ok(ix)
}
#[inline]
pub fn require_secp256k1_instruction(
sibling_index: u64,
) -> Result<ProcessedInstruction, ProgramError> {
let ix = get_processed_instruction(sibling_index).ok_or(ProgramError::InvalidArgument)?;
if !crate::address::address_eq(&ix.program_id, &SECP256K1_PROGRAM_ID) {
return Err(ProgramError::IncorrectProgramId);
}
Ok(ix)
}
#[inline]
pub fn require_secp256r1_instruction(
sibling_index: u64,
) -> Result<ProcessedInstruction, ProgramError> {
let ix = get_processed_instruction(sibling_index).ok_or(ProgramError::InvalidArgument)?;
if !crate::address::address_eq(&ix.program_id, &SECP256R1_PROGRAM_ID) {
return Err(ProgramError::IncorrectProgramId);
}
Ok(ix)
}
#[repr(C)]
#[allow(dead_code)]
struct ProcessedInstructionMeta {
data_len: u64,
accounts_len: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn absence_does_not_fabricate_an_instruction_or_touch_outputs() {
let mut data = [0xa5; 8];
let mut accounts = [ProcessedInstructionAccount::default()];
let mut calls = 0;
let result = read_processed_with(7, &mut data, &mut accounts, |index, _, _, _, _| {
assert_eq!(index, 7);
calls += 1;
0
})
.unwrap();
assert!(result.is_none());
assert_eq!(calls, 1);
assert_eq!(data, [0xa5; 8]);
assert_eq!(accounts, [ProcessedInstructionAccount::default()]);
assert!(get_processed_instruction(0).is_none());
}
#[test]
fn probes_then_copies_exact_lengths_and_preserves_unused_capacity() {
let mut data = [0xa5; 8];
let mut accounts =
core::array::from_fn::<_, 3, _>(|_| ProcessedInstructionAccount::default());
let expected = ProcessedInstructionAccount {
address: Address::new_from_array([9; 32]),
is_signer: true,
is_writable: false,
};
let mut calls = 0;
let view = read_processed_with(
2,
&mut data,
&mut accounts,
|index, meta, program, bytes, metas| {
assert_eq!(index, 2);
calls += 1;
if calls == 1 {
assert_eq!((meta.data_len, meta.accounts_len), (0, 0));
meta.data_len = 3;
meta.accounts_len = 1;
} else {
assert_eq!((meta.data_len, meta.accounts_len), (3, 1));
assert_eq!((bytes.len(), metas.len()), (3, 1));
*program = Address::new_from_array([7; 32]);
bytes.copy_from_slice(&[4, 5, 6]);
metas[0] = expected.clone();
}
1
},
)
.unwrap()
.unwrap();
assert_eq!(calls, 2);
assert_eq!(view.program_id, Address::new_from_array([7; 32]));
assert_eq!(view.data, &[4, 5, 6]);
assert_eq!(view.accounts, &[expected]);
assert_eq!(&data[3..], &[0xa5; 5]);
assert_eq!(
&accounts[1..],
&[
ProcessedInstructionAccount::default(),
ProcessedInstructionAccount::default()
]
);
}
#[test]
fn insufficient_buffers_are_rejected_before_copy() {
for (data_len, accounts_len) in [(9, 1), (3, 2), (u64::MAX, 0), (0, u64::MAX)] {
let mut calls = 0;
let mut data = [0xa5; 8];
let mut accounts = [ProcessedInstructionAccount::default()];
let result = read_processed_with(0, &mut data, &mut accounts, |_, meta, _, _, _| {
calls += 1;
meta.data_len = data_len;
meta.accounts_len = accounts_len;
1
});
assert_eq!(result.unwrap_err(), ProgramError::AccountDataTooSmall);
assert_eq!(calls, 1);
assert_eq!(data, [0xa5; 8]);
}
}
#[test]
fn zero_length_sibling_is_distinct_from_absence() {
let mut calls = 0;
let view = read_processed_with(0, &mut [], &mut [], |_, meta, program, _, _| {
calls += 1;
assert_eq!((meta.data_len, meta.accounts_len), (0, 0));
*program = Address::new_from_array([8; 32]);
1
})
.unwrap()
.unwrap();
assert_eq!(calls, 2);
assert_eq!(view.program_id, Address::new_from_array([8; 32]));
assert!(view.data.is_empty() && view.accounts.is_empty());
}
#[test]
fn unexpected_return_or_changing_lengths_fail_closed() {
for (probe_rc, copy_rc, change_lengths) in
[(2, 1, false), (1, 0, false), (1, 2, false), (1, 1, true)]
{
let mut calls = 0;
let mut data = [0; 8];
let mut accounts = [ProcessedInstructionAccount::default()];
let result = read_processed_with(0, &mut data, &mut accounts, |_, meta, _, _, _| {
calls += 1;
if calls == 1 {
meta.data_len = 3;
probe_rc
} else {
if change_lengths {
meta.data_len = 4;
}
copy_rc
}
});
assert_eq!(result.unwrap_err(), ProgramError::InvalidAccountData);
}
}
}