use core::mem::MaybeUninit;
use crate::account_view::AccountView;
use crate::address::Address;
use crate::raw_account::RuntimeAccount;
use crate::MAX_PERMITTED_DATA_INCREASE;
const BPF_ALIGN_OF_U128: usize = 8;
#[inline(never)]
#[cold]
pub(crate) fn malformed_duplicate_marker(marker: u8, slot: usize) -> ! {
#[cfg(target_os = "solana")]
unsafe {
const MSG: &[u8] = b"hopper: malformed duplicate marker";
crate::syscalls::sol_panic_(MSG.as_ptr(), MSG.len() as u64, slot as u64, marker as u64);
}
#[cfg(not(target_os = "solana"))]
{
panic!(
"hopper: malformed duplicate marker at slot {}: marker {} points forward",
slot, marker
);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RawAccountIndex {
pub original_index: usize,
pub duplicate_of: Option<usize>,
}
impl RawAccountIndex {
#[inline(always)]
pub const fn is_duplicate(&self) -> bool {
self.duplicate_of.is_some()
}
}
#[derive(Clone)]
pub struct RawInstructionFrame {
pub accounts_start: *mut u8,
pub account_count: usize,
pub instruction_data: &'static [u8],
pub program_id: Address,
}
#[inline(always)]
const fn next_record_offset(offset: usize, data_len: usize) -> usize {
(offset
+ RuntimeAccount::SIZE
+ data_len
+ MAX_PERMITTED_DATA_INCREASE
+ 8
+ (BPF_ALIGN_OF_U128 - 1))
& !(BPF_ALIGN_OF_U128 - 1)
}
#[inline(always)]
pub unsafe fn deserialize_accounts<'info, const MAX: usize>(
input: *mut u8,
accounts: &mut [MaybeUninit<AccountView<'info>>; MAX],
) -> (&'info Address, usize, &'info [u8]) {
let num_accounts = unsafe { core::ptr::read_unaligned(input as *const u64) as usize };
let addressable = if num_accounts > 254 {
254
} else {
num_accounts
};
let count = if addressable > MAX { MAX } else { addressable };
let mut offset = 8usize;
let mut slot = 0usize;
while slot < count {
let marker = unsafe { *input.add(offset) };
if marker == u8::MAX {
let raw = unsafe { input.add(offset) as *mut RuntimeAccount };
let view = unsafe { AccountView::new_unchecked(raw) };
unsafe { view.initialize_original_data_len() };
unsafe {
*accounts.get_unchecked_mut(slot) = MaybeUninit::new(view);
}
let data_len = unsafe { (*raw).data_len as usize };
offset = next_record_offset(offset, data_len);
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
malformed_duplicate_marker(marker, slot);
}
let raw = unsafe {
accounts
.get_unchecked(duplicate_of)
.assume_init_ref()
.raw_ptr()
};
unsafe {
*accounts.get_unchecked_mut(slot) =
MaybeUninit::new(AccountView::new_unchecked(raw))
};
offset += 8;
}
slot += 1;
}
while slot < num_accounts {
let marker = unsafe { *input.add(offset) };
if marker == u8::MAX {
let data_len =
unsafe { (*(input.add(offset) as *const RuntimeAccount)).data_len } as usize;
offset = next_record_offset(offset, data_len);
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
malformed_duplicate_marker(marker, slot);
}
offset += 8;
}
slot += 1;
}
let ix_data_len =
unsafe { core::ptr::read_unaligned(input.add(offset) as *const u64) as usize };
offset += 8;
let instruction_data =
unsafe { core::slice::from_raw_parts(input.add(offset) as *const u8, ix_data_len) };
offset += ix_data_len;
let program_id: &'info Address = unsafe { &*(input.add(offset) as *const Address) };
(program_id, count, instruction_data)
}
#[inline(always)]
pub unsafe fn deserialize_leading_accounts<'info, const MAX: usize>(
input: *mut u8,
accounts: &mut [MaybeUninit<AccountView<'info>>; MAX],
limit: usize,
) -> usize {
let num_accounts = unsafe { core::ptr::read_unaligned(input as *const u64) as usize };
let limit = if limit > MAX { MAX } else { limit };
let count = if num_accounts > limit {
limit
} else {
num_accounts
};
let mut offset = 8usize;
let mut slot = 0usize;
while slot < MAX {
if slot >= count {
break;
}
let marker = unsafe { *input.add(offset) };
if marker == u8::MAX {
let raw = unsafe { input.add(offset) as *mut RuntimeAccount };
let view = unsafe { AccountView::new_unchecked(raw) };
unsafe { view.initialize_original_data_len() };
unsafe {
*accounts.get_unchecked_mut(slot) = MaybeUninit::new(view);
}
let data_len = unsafe { (*raw).data_len as usize };
offset = next_record_offset(offset, data_len);
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
malformed_duplicate_marker(marker, slot);
}
let raw = unsafe {
accounts
.get_unchecked(duplicate_of)
.assume_init_ref()
.raw_ptr()
};
unsafe {
*accounts.get_unchecked_mut(slot) =
MaybeUninit::new(AccountView::new_unchecked(raw))
};
offset += 8;
}
slot += 1;
}
count
}
#[inline(always)]
pub unsafe fn deserialize_accounts_fast<'info, const MAX: usize>(
input: *mut u8,
accounts: &mut [MaybeUninit<AccountView<'info>>; MAX],
instruction_data: &'info [u8],
program_id: &'info Address,
) -> (&'info Address, usize, &'info [u8]) {
let num_accounts = unsafe { core::ptr::read_unaligned(input as *const u64) as usize };
let addressable = if num_accounts > 254 {
254
} else {
num_accounts
};
let count = addressable.min(MAX);
let mut offset = 8usize;
let mut slot = 0usize;
while slot < count {
let marker = unsafe { *input.add(offset) };
if marker == u8::MAX {
let raw = unsafe { input.add(offset) as *mut RuntimeAccount };
let view = unsafe { AccountView::new_unchecked(raw) };
unsafe { view.initialize_original_data_len() };
unsafe {
*accounts.get_unchecked_mut(slot) = MaybeUninit::new(view);
}
let data_len = unsafe { (*raw).data_len as usize };
offset = next_record_offset(offset, data_len);
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
malformed_duplicate_marker(marker, slot);
}
let raw = unsafe {
accounts
.get_unchecked(duplicate_of)
.assume_init_ref()
.raw_ptr()
};
unsafe {
*accounts.get_unchecked_mut(slot) =
MaybeUninit::new(AccountView::new_unchecked(raw))
};
offset += 8;
}
slot += 1;
}
(program_id, count, instruction_data)
}
pub const SIMD_0449_TABLE_ENABLED: bool = cfg!(feature = "simd-0449");
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DirectMappingError {
NullInput,
ArithmeticOverflow,
TruncatedInput,
TooManyAccounts { count: usize, capacity: usize },
MalformedDuplicate { slot: usize, duplicate_of: usize },
InstructionDataMismatch,
PointerTableMisaligned,
PointerOutOfBounds { slot: usize },
PointerMisaligned { slot: usize },
NonCanonicalPointer { slot: usize },
}
#[inline(always)]
fn checked_end(offset: usize, size: usize, input_len: usize) -> Result<usize, DirectMappingError> {
let end = offset
.checked_add(size)
.ok_or(DirectMappingError::ArithmeticOverflow)?;
if end > input_len {
return Err(DirectMappingError::TruncatedInput);
}
Ok(end)
}
pub unsafe fn deserialize_accounts_0449_checked<'info, const MAX: usize>(
input: *mut u8,
input_len: usize,
accounts: &mut [MaybeUninit<AccountView<'info>>; MAX],
instruction_data: &'info [u8],
) -> Result<(Address, usize, &'info [u8]), DirectMappingError> {
if input.is_null() {
return Err(DirectMappingError::NullInput);
}
checked_end(0, 8, input_len)?;
let base = input as usize;
let num_accounts = unsafe { core::ptr::read_unaligned(input as *const u64) as usize };
if num_accounts > MAX {
return Err(DirectMappingError::TooManyAccounts {
count: num_accounts,
capacity: MAX,
});
}
let mut canonical_offsets = [0usize; MAX];
let mut offset = 8usize;
let mut slot = 0usize;
while slot < num_accounts {
checked_end(offset, 1, input_len)?;
let marker = unsafe { *input.add(offset) };
if marker == u8::MAX {
checked_end(offset, RuntimeAccount::SIZE, input_len)?;
canonical_offsets[slot] = offset;
let data_len =
unsafe { core::ptr::read_unaligned(input.add(offset + 80) as *const u64) as usize };
let body_end = offset
.checked_add(RuntimeAccount::SIZE)
.and_then(|v| v.checked_add(data_len))
.and_then(|v| v.checked_add(MAX_PERMITTED_DATA_INCREASE))
.ok_or(DirectMappingError::ArithmeticOverflow)?;
let aligned = body_end
.checked_add(BPF_ALIGN_OF_U128 - 1)
.ok_or(DirectMappingError::ArithmeticOverflow)?
& !(BPF_ALIGN_OF_U128 - 1);
offset = checked_end(aligned, 8, input_len)?;
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
return Err(DirectMappingError::MalformedDuplicate { slot, duplicate_of });
}
canonical_offsets[slot] = canonical_offsets[duplicate_of];
offset = checked_end(offset, 8, input_len)?;
}
slot += 1;
}
let ix_len_end = checked_end(offset, 8, input_len)?;
let ix_len = unsafe { core::ptr::read_unaligned(input.add(offset) as *const u64) as usize };
let ix_offset = ix_len_end;
let ix_end = checked_end(ix_offset, ix_len, input_len)?;
if instruction_data.as_ptr() as usize != base + ix_offset || instruction_data.len() != ix_len {
return Err(DirectMappingError::InstructionDataMismatch);
}
let program_end = checked_end(ix_end, 32, input_len)?;
let program_id = Address::new_from_array(unsafe {
core::ptr::read_unaligned(input.add(ix_end) as *const [u8; 32])
});
let table_offset = program_end
.checked_add(BPF_ALIGN_OF_U128 - 1)
.ok_or(DirectMappingError::ArithmeticOverflow)?
& !(BPF_ALIGN_OF_U128 - 1);
if !(base + table_offset).is_multiple_of(BPF_ALIGN_OF_U128) {
return Err(DirectMappingError::PointerTableMisaligned);
}
let table_bytes = num_accounts
.checked_mul(core::mem::size_of::<u64>())
.ok_or(DirectMappingError::ArithmeticOverflow)?;
checked_end(table_offset, table_bytes, input_len)?;
let frame_end = base
.checked_add(input_len)
.ok_or(DirectMappingError::ArithmeticOverflow)?;
slot = 0;
while slot < num_accounts {
let pointer = unsafe {
core::ptr::read_unaligned(input.add(table_offset + slot * 8) as *const u64) as usize
};
let pointer_end = pointer
.checked_add(RuntimeAccount::SIZE)
.ok_or(DirectMappingError::ArithmeticOverflow)?;
if pointer < base || pointer_end > frame_end {
return Err(DirectMappingError::PointerOutOfBounds { slot });
}
if pointer % BPF_ALIGN_OF_U128 != 0 {
return Err(DirectMappingError::PointerMisaligned { slot });
}
let expected = base
.checked_add(canonical_offsets[slot])
.ok_or(DirectMappingError::ArithmeticOverflow)?;
if pointer != expected {
return Err(DirectMappingError::NonCanonicalPointer { slot });
}
slot += 1;
}
slot = 0;
while slot < num_accounts {
let pointer = base + canonical_offsets[slot];
let view = unsafe { AccountView::new_unchecked(pointer as *mut RuntimeAccount) };
unsafe { view.initialize_original_data_len() };
accounts[slot] = MaybeUninit::new(view);
slot += 1;
}
Ok((program_id, num_accounts, instruction_data))
}
const _: () = assert!(
core::mem::size_of::<AccountView<'static>>() == 8
&& core::mem::align_of::<AccountView<'static>>() == 8,
"AccountView must stay a single 8-byte pointer for the SIMD-0449 table cast"
);
#[inline(always)]
pub unsafe fn deserialize_accounts_0449<'info>(
input: *mut u8,
instruction_data: &'info [u8],
) -> &'info [AccountView<'info>] {
let num_accounts = unsafe { core::ptr::read_unaligned(input as *const u64) as usize };
let tail_end = instruction_data.as_ptr() as usize + instruction_data.len() + 32;
let table = ((tail_end + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1))
as *const AccountView<'info>;
let views = unsafe { core::slice::from_raw_parts(table, num_accounts) };
let mut slot = 0usize;
while slot < num_accounts {
unsafe { views.get_unchecked(slot).initialize_original_data_len() };
slot += 1;
}
views
}
#[inline(always)]
pub unsafe fn deserialize_accounts_0449_into<'info, const MAX: usize>(
input: *mut u8,
accounts: &mut [MaybeUninit<AccountView<'info>>; MAX],
instruction_data: &'info [u8],
program_id: &'info Address,
) -> (&'info Address, usize, &'info [u8]) {
let table = unsafe { deserialize_accounts_0449(input, instruction_data) };
let addressable = if table.len() > 254 { 254 } else { table.len() };
let count = addressable.min(MAX);
let mut slot = 0usize;
while slot < count {
unsafe {
*accounts.get_unchecked_mut(slot) = MaybeUninit::new(table.get_unchecked(slot).clone());
}
slot += 1;
}
(program_id, count, instruction_data)
}
#[inline(always)]
pub unsafe fn scan_instruction_frame(input: *mut u8) -> RawInstructionFrame {
let mut scan = input;
let num_accounts = unsafe { core::ptr::read_unaligned(scan as *const u64) as usize };
scan = unsafe { scan.add(8) };
let accounts_start = scan;
let mut slot = 0usize;
while slot < num_accounts {
let marker = unsafe { *scan };
if marker == u8::MAX {
let raw = scan as *const RuntimeAccount;
let data_len = unsafe { (*raw).data_len as usize };
let mut step = RuntimeAccount::SIZE + data_len + MAX_PERMITTED_DATA_INCREASE;
step += unsafe { scan.add(step).align_offset(BPF_ALIGN_OF_U128) };
step += 8;
scan = unsafe { scan.add(step) };
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
malformed_duplicate_marker(marker, slot);
}
scan = unsafe { scan.add(8) };
}
slot += 1;
}
let data_len = unsafe { core::ptr::read_unaligned(scan as *const u64) as usize };
scan = unsafe { scan.add(8) };
let instruction_data = unsafe { core::slice::from_raw_parts(scan as *const u8, data_len) };
scan = unsafe { scan.add(data_len) };
let program_id_ptr = scan as *const [u8; 32];
let program_id = Address::new_from_array(unsafe { *program_id_ptr });
RawInstructionFrame {
accounts_start,
account_count: num_accounts.min(254),
instruction_data,
program_id,
}
}
pub const MAX_SAFE_ACCOUNT_SLOTS: usize = 256;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FrameInfo {
pub account_count: usize,
pub instruction_data_range: core::ops::Range<usize>,
pub program_id_offset: usize,
pub slot_offsets: [usize; MAX_SAFE_ACCOUNT_SLOTS],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FrameError {
UnexpectedEof { needed: usize, at: usize },
AccountCountOutOfRange(u64),
MalformedDuplicateMarker { slot: usize, marker: u8 },
DataLenOutOfRange { slot: usize, data_len: u64 },
OffsetOverflow { slot: usize },
}
impl core::fmt::Display for FrameError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnexpectedEof { needed, at } => {
write!(f, "unexpected EOF: need {needed} bytes at offset {at}")
}
Self::AccountCountOutOfRange(n) => {
write!(f, "account count {n} exceeds cap 256")
}
Self::MalformedDuplicateMarker { slot, marker } => {
write!(
f,
"malformed duplicate marker at slot {slot}: marker {marker} does not refer to an earlier slot"
)
}
Self::DataLenOutOfRange { slot, data_len } => {
write!(
f,
"slot {slot}: data_len {data_len} exceeds remaining buffer"
)
}
Self::OffsetOverflow { slot } => {
write!(f, "slot {slot}: offset arithmetic overflow")
}
}
}
}
pub fn parse_instruction_frame_checked(buf: &[u8]) -> Result<FrameInfo, FrameError> {
fn read_u64_le(buf: &[u8], pos: &mut usize) -> Result<u64, FrameError> {
let end = pos
.checked_add(8)
.ok_or(FrameError::OffsetOverflow { slot: 0 })?;
let slice = buf.get(*pos..end).ok_or(FrameError::UnexpectedEof {
needed: 8,
at: *pos,
})?;
let mut bytes = [0u8; 8];
bytes.copy_from_slice(slice);
*pos = end;
Ok(u64::from_le_bytes(bytes))
}
fn read_u8(buf: &[u8], pos: &mut usize) -> Result<u8, FrameError> {
let byte = *buf.get(*pos).ok_or(FrameError::UnexpectedEof {
needed: 1,
at: *pos,
})?;
*pos += 1;
Ok(byte)
}
fn advance(buf: &[u8], pos: &mut usize, n: usize) -> Result<(), FrameError> {
let end = pos
.checked_add(n)
.ok_or(FrameError::OffsetOverflow { slot: 0 })?;
if end > buf.len() {
return Err(FrameError::UnexpectedEof {
needed: n,
at: *pos,
});
}
*pos = end;
Ok(())
}
let mut pos = 0usize;
let account_count = read_u64_le(buf, &mut pos)?;
if account_count > MAX_SAFE_ACCOUNT_SLOTS as u64 {
return Err(FrameError::AccountCountOutOfRange(account_count));
}
let account_count = account_count as usize;
let mut slot_offsets = [0usize; MAX_SAFE_ACCOUNT_SLOTS];
#[allow(clippy::needless_range_loop)]
for slot in 0..account_count {
let slot_start = pos;
slot_offsets[slot] = slot_start;
let marker = read_u8(buf, &mut pos)?;
if marker == u8::MAX {
advance(buf, &mut pos, RuntimeAccount::SIZE - 1).map_err(|_| {
FrameError::UnexpectedEof {
needed: RuntimeAccount::SIZE - 1,
at: pos,
}
})?;
let data_len_pos = slot_start
.checked_add(80)
.ok_or(FrameError::OffsetOverflow { slot })?;
let mut dl_bytes = [0u8; 8];
let dl_slice =
buf.get(data_len_pos..data_len_pos + 8)
.ok_or(FrameError::UnexpectedEof {
needed: 8,
at: data_len_pos,
})?;
dl_bytes.copy_from_slice(dl_slice);
let data_len = u64::from_le_bytes(dl_bytes);
let data_sz: usize = (data_len as usize)
.checked_add(MAX_PERMITTED_DATA_INCREASE)
.ok_or(FrameError::DataLenOutOfRange { slot, data_len })?;
advance(buf, &mut pos, data_sz)
.map_err(|_| FrameError::DataLenOutOfRange { slot, data_len })?;
let pad = pos.wrapping_neg() & (BPF_ALIGN_OF_U128 - 1);
advance(buf, &mut pos, pad).map_err(|_| FrameError::UnexpectedEof {
needed: pad,
at: pos,
})?;
advance(buf, &mut pos, 8)
.map_err(|_| FrameError::UnexpectedEof { needed: 8, at: pos })?;
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
return Err(FrameError::MalformedDuplicateMarker { slot, marker });
}
advance(buf, &mut pos, 7)
.map_err(|_| FrameError::UnexpectedEof { needed: 7, at: pos })?;
}
}
let ix_data_len = read_u64_le(buf, &mut pos)? as usize;
let ix_start = pos;
advance(buf, &mut pos, ix_data_len).map_err(|_| FrameError::UnexpectedEof {
needed: ix_data_len,
at: pos,
})?;
let instruction_data_range = ix_start..pos;
let program_id_offset = pos;
advance(buf, &mut pos, 32).map_err(|_| FrameError::UnexpectedEof {
needed: 32,
at: pos,
})?;
Ok(FrameInfo {
account_count,
instruction_data_range,
program_id_offset,
slot_offsets,
})
}
#[cfg(test)]
mod checked_parser_tests {
use super::*;
const MINIMAL_FRAME_LEN: usize = 8 + 88 + MAX_PERMITTED_DATA_INCREASE + 8 + 8 + 32;
fn build_minimal_frame() -> [u8; MINIMAL_FRAME_LEN] {
let mut buf = [0u8; MINIMAL_FRAME_LEN];
buf[0..8].copy_from_slice(&1u64.to_le_bytes()); buf[8] = 0xFF; buf
}
#[test]
fn parses_minimal_valid_frame() {
let buf = build_minimal_frame();
let frame = parse_instruction_frame_checked(&buf).expect("well-formed");
assert_eq!(frame.account_count, 1);
assert_eq!(frame.instruction_data_range.len(), 0);
assert_eq!(frame.program_id_offset + 32, buf.len());
}
#[test]
fn truncated_header_is_rejected() {
let buf = [0u8; 4]; let err = parse_instruction_frame_checked(&buf).unwrap_err();
assert!(matches!(err, FrameError::UnexpectedEof { .. }));
}
#[test]
fn oversized_account_count_is_rejected() {
let mut buf = [0u8; 8];
buf.copy_from_slice(&1_000u64.to_le_bytes());
let err = parse_instruction_frame_checked(&buf).unwrap_err();
assert!(matches!(err, FrameError::AccountCountOutOfRange(1000)));
}
#[test]
fn forward_duplicate_marker_is_rejected() {
let mut buf = [0u8; 16];
buf[0..8].copy_from_slice(&2u64.to_le_bytes());
buf[8] = 1; let err = parse_instruction_frame_checked(&buf).unwrap_err();
assert!(matches!(
err,
FrameError::MalformedDuplicateMarker { slot: 0, marker: 1 }
));
}
#[test]
fn self_duplicate_marker_is_rejected() {
let mut buf = [0u8; 16];
buf[0..8].copy_from_slice(&1u64.to_le_bytes());
buf[8] = 0; let err = parse_instruction_frame_checked(&buf).unwrap_err();
assert!(matches!(
err,
FrameError::MalformedDuplicateMarker { slot: 0, marker: 0 }
));
}
#[test]
fn arbitrary_short_input_never_panics() {
let buf = [0u8; 256];
for len in 0..=256 {
let _ = parse_instruction_frame_checked(&buf[..len]);
}
}
#[test]
fn arbitrary_ff_input_never_panics() {
let buf = [0xFFu8; 256];
for len in 0..=256 {
let _ = parse_instruction_frame_checked(&buf[..len]);
}
}
}
#[cfg(test)]
mod fused_walk_tests {
extern crate std;
use std::vec;
use std::vec::Vec;
use super::*;
enum Slot {
Fresh { data: Vec<u8>, lamports: u64 },
Dup(u8),
}
fn fresh(data_len: usize, lamports: u64) -> Slot {
Slot::Fresh {
data: vec![0xABu8; data_len],
lamports,
}
}
struct Frame {
words: Vec<u64>,
}
impl Frame {
fn as_mut_ptr(&mut self) -> *mut u8 {
self.words.as_mut_ptr() as *mut u8
}
}
fn build_frame(slots: &[Slot], ix_data: &[u8], program_id: [u8; 32]) -> Frame {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&(slots.len() as u64).to_le_bytes());
for (i, slot) in slots.iter().enumerate() {
match slot {
Slot::Fresh { data, lamports } => {
let mut header = [0u8; RuntimeAccount::SIZE];
header[0] = 0xFF; header[1] = 1; header[2] = 1; header[8..40].copy_from_slice(&[i as u8 + 1; 32]);
header[40..72].copy_from_slice(&[0x55; 32]);
header[72..80].copy_from_slice(&lamports.to_le_bytes());
header[80..88].copy_from_slice(&(data.len() as u64).to_le_bytes());
buf.extend_from_slice(&header);
buf.extend_from_slice(data);
buf.extend_from_slice(&vec![0u8; MAX_PERMITTED_DATA_INCREASE]);
while !buf.len().is_multiple_of(BPF_ALIGN_OF_U128) {
buf.push(0);
}
buf.extend_from_slice(&u64::MAX.to_le_bytes());
}
Slot::Dup(of) => {
buf.push(*of);
buf.extend_from_slice(&[0u8; 7]);
}
}
}
buf.extend_from_slice(&(ix_data.len() as u64).to_le_bytes());
buf.extend_from_slice(ix_data);
buf.extend_from_slice(&program_id);
let mut words = vec![0u64; buf.len().div_ceil(8)];
unsafe {
core::ptr::copy_nonoverlapping(buf.as_ptr(), words.as_mut_ptr() as *mut u8, buf.len());
}
Frame { words }
}
fn uninit_views<'a, const MAX: usize>() -> [MaybeUninit<AccountView<'a>>; MAX] {
unsafe { MaybeUninit::uninit().assume_init() }
}
const PID: [u8; 32] = [0xC4; 32];
#[test]
fn zero_accounts_finds_ix_data_and_program_id() {
let mut frame = build_frame(&[], &[9, 8, 7], PID);
let mut views = uninit_views::<4>();
let (pid, count, ix) = unsafe { deserialize_accounts::<4>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, 0);
assert_eq!(ix, &[9, 8, 7]);
assert_eq!(pid.as_array(), &PID);
}
#[test]
fn one_account_materializes_and_finds_tail() {
let mut frame = build_frame(&[fresh(11, 42)], &[1, 2, 3, 4], PID);
let mut views = uninit_views::<4>();
let (pid, count, ix) = unsafe { deserialize_accounts::<4>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, 1);
let view = unsafe { views[0].assume_init_ref() };
assert_eq!(view.data_len(), 11);
assert_eq!(view.lamports(), 42);
assert!(view.is_signer());
assert_eq!(ix, &[1, 2, 3, 4]);
assert_eq!(pid.as_array(), &PID);
}
#[test]
fn leading_prefix_materializes_only_the_declared_accounts() {
let slots = [
fresh(9, 7),
Slot::Dup(0),
fresh(3, 8),
fresh(5, 9),
fresh(1, 10),
];
let mut frame = build_frame(&slots, &[0x11], PID);
let mut views = uninit_views::<3>();
let count = unsafe { deserialize_leading_accounts::<3>(frame.as_mut_ptr(), &mut views, 3) };
assert_eq!(count, 3);
let (a, b, c) = unsafe {
(
views[0].assume_init_ref(),
views[1].assume_init_ref(),
views[2].assume_init_ref(),
)
};
assert_eq!(a.data_len(), 9);
assert_eq!(b.raw_ptr(), a.raw_ptr(), "the duplicate aliases slot 0");
assert_eq!(c.data_len(), 3);
assert_eq!(c.lamports(), 8);
let mut frame = build_frame(&[fresh(2, 1)], &[0x11], PID);
let mut views = uninit_views::<3>();
let count = unsafe { deserialize_leading_accounts::<3>(frame.as_mut_ptr(), &mut views, 3) };
assert_eq!(count, 1);
let mut frame = build_frame(&slots, &[0x11], PID);
let mut views = uninit_views::<3>();
let count = unsafe { deserialize_leading_accounts::<3>(frame.as_mut_ptr(), &mut views, 2) };
assert_eq!(count, 2);
}
#[test]
fn exactly_max_accounts() {
let slots: Vec<Slot> = (0..4).map(|i| fresh(i * 3 + 1, 100 + i as u64)).collect();
let mut frame = build_frame(&slots, &[0xEE; 5], PID);
let mut views = uninit_views::<4>();
let (pid, count, ix) = unsafe { deserialize_accounts::<4>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, 4);
for (i, view) in views.iter().enumerate() {
let view = unsafe { view.assume_init_ref() };
assert_eq!(view.data_len(), i * 3 + 1);
assert_eq!(view.lamports(), 100 + i as u64);
}
assert_eq!(ix, &[0xEE; 5]);
assert_eq!(pid.as_array(), &PID);
}
#[test]
fn beyond_max_is_skip_only_and_tail_still_found() {
let slots: Vec<Slot> = (0..7).map(|i| fresh(i * 5 + 2, i as u64)).collect();
let mut frame = build_frame(&slots, &[0xD1, 0xD2], PID);
let mut views = uninit_views::<4>();
let (pid, count, ix) = unsafe { deserialize_accounts::<4>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, 4);
for (i, view) in views.iter().enumerate() {
let view = unsafe { view.assume_init_ref() };
assert_eq!(view.data_len(), i * 5 + 2);
}
assert_eq!(ix, &[0xD1, 0xD2]);
assert_eq!(pid.as_array(), &PID);
}
#[test]
fn duplicates_alias_the_canonical_record() {
let slots = [fresh(9, 7), Slot::Dup(0), fresh(3, 8), Slot::Dup(2)];
let mut frame = build_frame(&slots, &[0x11], PID);
let mut views = uninit_views::<8>();
let (_, count, ix) = unsafe { deserialize_accounts::<8>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, 4);
let (v0, v1, v2, v3) = unsafe {
(
views[0].assume_init_ref(),
views[1].assume_init_ref(),
views[2].assume_init_ref(),
views[3].assume_init_ref(),
)
};
assert_eq!(v0.raw_ptr(), v1.raw_ptr(), "dup slot aliases canonical");
assert_eq!(v2.raw_ptr(), v3.raw_ptr(), "dup slot aliases canonical");
assert_ne!(v0.raw_ptr(), v2.raw_ptr());
assert_eq!(v1.data_len(), 9);
assert_eq!(v3.data_len(), 3);
assert_eq!(ix, &[0x11]);
}
#[test]
fn duplicate_in_skip_only_tail_advances_eight_bytes() {
let slots = [fresh(5, 1), fresh(6, 2), fresh(7, 3), Slot::Dup(1)];
let mut frame = build_frame(&slots, &[0xAA, 0xBB, 0xCC], PID);
let mut views = uninit_views::<2>();
let (pid, count, ix) = unsafe { deserialize_accounts::<2>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, 2);
assert_eq!(ix, &[0xAA, 0xBB, 0xCC]);
assert_eq!(pid.as_array(), &PID);
}
#[test]
fn every_data_len_alignment_residue_walks_correctly() {
for base in [0usize, 8] {
let slots: Vec<Slot> = (0..8).map(|r| fresh(base + r, r as u64)).collect();
let mut frame = build_frame(&slots, &[0x42; 9], PID);
let mut views = uninit_views::<8>();
let (pid, count, ix) =
unsafe { deserialize_accounts::<8>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, 8);
for (r, view) in views.iter().enumerate() {
let view = unsafe { view.assume_init_ref() };
assert_eq!(view.data_len(), base + r);
}
assert_eq!(ix, &[0x42; 9]);
assert_eq!(pid.as_array(), &PID);
}
}
#[test]
fn folded_stride_matches_align_offset_formula() {
let backing = [0u64; 1];
let base = backing.as_ptr() as *const u8;
assert_eq!(base as usize % 8, 0, "test base must be 8-aligned");
for start in [8usize, 96, 10344, 20696] {
for data_len in 0usize..64 {
let mut old = start;
old += RuntimeAccount::SIZE;
old += data_len + MAX_PERMITTED_DATA_INCREASE;
old += base.wrapping_add(old).align_offset(BPF_ALIGN_OF_U128);
old += 8;
let new = next_record_offset(start, data_len);
assert_eq!(
old, new,
"stride mismatch at start={start} data_len={data_len}"
);
}
}
}
#[test]
fn huge_data_len_near_region_end() {
let big = 100_003usize; let mut frame = build_frame(&[fresh(big, 5)], &[0x77, 0x66], PID);
let mut views = uninit_views::<2>();
let (pid, count, ix) = unsafe { deserialize_accounts::<2>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, 1);
assert_eq!(unsafe { views[0].assume_init_ref() }.data_len(), big);
assert_eq!(ix, &[0x77, 0x66]);
assert_eq!(pid.as_array(), &PID);
}
#[test]
fn account_count_clamps_at_254_materialized_slots() {
let mut slots: Vec<Slot> = vec![fresh(4, 9)];
slots.extend((0..259).map(|_| Slot::Dup(0)));
let mut frame = build_frame(&slots, &[0x0F; 3], PID);
let mut views = uninit_views::<255>();
let (pid, count, ix) =
unsafe { deserialize_accounts::<255>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, 254);
assert_eq!(ix, &[0x0F; 3]);
assert_eq!(pid.as_array(), &PID);
}
#[test]
#[should_panic(expected = "malformed duplicate marker")]
fn forward_duplicate_marker_traps_in_materialize_range() {
let slots = [fresh(1, 1), Slot::Dup(1)]; let mut frame = build_frame(&slots, &[], PID);
let mut views = uninit_views::<4>();
let _ = unsafe { deserialize_accounts::<4>(frame.as_mut_ptr(), &mut views) };
}
#[test]
#[should_panic(expected = "malformed duplicate marker")]
fn forward_duplicate_marker_traps_in_skip_only_tail() {
let slots = [fresh(1, 1), Slot::Dup(5)];
let mut frame = build_frame(&slots, &[], PID);
let mut views = uninit_views::<1>();
let _ = unsafe { deserialize_accounts::<1>(frame.as_mut_ptr(), &mut views) };
}
#[test]
fn fast_variant_uses_same_stride_and_aliases_duplicates() {
let slots = [fresh(13, 3), Slot::Dup(0), fresh(6, 4)];
let mut frame = build_frame(&slots, &[0x99], PID);
let mut views = uninit_views::<4>();
let ix: &[u8] = &[0x99];
let program_id = Address::new_from_array(PID);
let (pid, count, out_ix) = unsafe {
deserialize_accounts_fast::<4>(frame.as_mut_ptr(), &mut views, ix, &program_id)
};
assert_eq!(count, 3);
let (v0, v1, v2) = unsafe {
(
views[0].assume_init_ref(),
views[1].assume_init_ref(),
views[2].assume_init_ref(),
)
};
assert_eq!(v0.raw_ptr(), v1.raw_ptr());
assert_eq!(v0.data_len(), 13);
assert_eq!(v2.data_len(), 6);
assert_eq!(out_ix, ix);
assert_eq!(pid.as_array(), &PID);
}
#[test]
fn fused_walk_agrees_with_checked_parser() {
let slots = [fresh(7, 1), Slot::Dup(0), fresh(0, 2), fresh(33, 3)];
let ix_data = [5u8, 4, 3, 2, 1];
let mut frame = build_frame(&slots, &ix_data, PID);
let byte_len = frame.words.len() * 8;
let bytes: &[u8] =
unsafe { core::slice::from_raw_parts(frame.words.as_ptr() as *const u8, byte_len) };
let checked = parse_instruction_frame_checked(bytes).expect("well-formed");
let mut views = uninit_views::<8>();
let (pid, count, ix) = unsafe { deserialize_accounts::<8>(frame.as_mut_ptr(), &mut views) };
assert_eq!(count, checked.account_count);
assert_eq!(ix, &bytes[checked.instruction_data_range.clone()]);
assert_eq!(
pid.as_array().as_slice(),
&bytes[checked.program_id_offset..checked.program_id_offset + 32]
);
}
}
#[cfg(kani)]
mod kani_proofs {
use super::*;
const MM_INPUT_START: usize = 0x4_0000_0000;
const LOADER_MAX_DATA_LEN: usize = 10_485_760;
const MAX_REGION_OFFSET: usize = u32::MAX as usize;
const MAX_DL: usize = 8;
const MAX_IX: usize = 8;
const RECORD_MAX: usize = next_record_offset(0, MAX_DL);
const fn frame_len(n: usize) -> usize {
8 + n * RECORD_MAX + 8 + MAX_IX + 32
}
const IX_SENTINEL: [u8; MAX_IX] = [0xA5; MAX_IX];
const PID_SENTINEL: [u8; 32] = [0xC4; 32];
const PID_WORD: [u8; 8] = [0xC4; 8];
#[repr(C, align(8))]
struct AlignedBuf<const LEN: usize>([u8; LEN]);
fn any_valid_marker(i: usize) -> u8 {
let m: u8 = kani::any();
kani::assume(m == u8::MAX || (m as usize) < i);
m
}
fn any_bounded_data_len() -> usize {
let dl: usize = kani::any();
kani::assume(dl <= MAX_DL);
dl
}
fn any_bounded_ix_len() -> usize {
let n: usize = kani::any();
kani::assume(n <= MAX_IX);
n
}
fn write_frame<const N: usize>(
buf: &mut [u8],
markers: &[u8; N],
data_lens: &[usize; N],
ix_len: usize,
) -> usize {
buf[0..8].copy_from_slice(&(N as u64).to_le_bytes());
let mut pos = 8usize;
let mut i = 0;
while i < N {
buf[pos] = markers[i];
if markers[i] == u8::MAX {
buf[pos + 80..pos + 88].copy_from_slice(&(data_lens[i] as u64).to_le_bytes());
pos = next_record_offset(pos, data_lens[i]);
} else {
pos += 8;
}
i += 1;
}
buf[pos..pos + 8].copy_from_slice(&(ix_len as u64).to_le_bytes());
pos += 8;
buf[pos..pos + ix_len].copy_from_slice(&IX_SENTINEL[..ix_len]);
pos += ix_len;
let mut w = 0;
while w < 4 {
buf[pos + w * 8..pos + w * 8 + 8].copy_from_slice(&PID_WORD);
w += 1;
}
pos + 32
}
fn resolve_canonical<const N: usize>(markers: &[u8; N], mut i: usize) -> usize {
while markers[i] != u8::MAX {
i = markers[i] as usize;
}
i
}
#[kani::proof]
fn stride_lemma_matches_checked_align_offset_formula() {
let offset: usize = kani::any();
let data_len: usize = kani::any();
kani::assume(offset <= MAX_REGION_OFFSET);
kani::assume(data_len <= LOADER_MAX_DATA_LEN);
let unpadded = offset
.checked_add(RuntimeAccount::SIZE)
.and_then(|x| x.checked_add(data_len))
.and_then(|x| x.checked_add(MAX_PERMITTED_DATA_INCREASE))
.expect("pre-alignment cursor must not overflow");
let absolute = MM_INPUT_START
.checked_add(unpadded)
.expect("absolute address must not overflow");
let pad_absolute = absolute.wrapping_neg() & (BPF_ALIGN_OF_U128 - 1);
let pad_relative = unpadded.wrapping_neg() & (BPF_ALIGN_OF_U128 - 1);
assert_eq!(pad_absolute, pad_relative);
let expected = unpadded
.checked_add(pad_absolute)
.and_then(|x| x.checked_add(8))
.expect("aligned cursor must not overflow");
let got = next_record_offset(offset, data_len);
assert_eq!(got, expected);
assert_eq!(got & (BPF_ALIGN_OF_U128 - 1), 0);
assert!(got > offset);
}
fn check_fused_walk_against_oracle<const N: usize, const MAX: usize, const LEN: usize>() {
let mut markers = [0u8; N];
let mut data_lens = [0usize; N];
let mut i = 0;
while i < N {
markers[i] = any_valid_marker(i);
data_lens[i] = any_bounded_data_len();
i += 1;
}
let ix_len = any_bounded_ix_len();
let mut backing = AlignedBuf::<LEN>([0u8; LEN]);
write_frame::<N>(&mut backing.0, &markers, &data_lens, ix_len);
let oracle = parse_instruction_frame_checked(&backing.0)
.expect("oracle must accept a well-formed loader frame");
assert_eq!(oracle.account_count, N);
assert_eq!(oracle.instruction_data_range.len(), ix_len);
let base = backing.0.as_ptr() as usize;
let mut views: [MaybeUninit<AccountView<'_>>; MAX] =
unsafe { MaybeUninit::uninit().assume_init() };
let (pid, count, ix) =
unsafe { deserialize_accounts::<MAX>(backing.0.as_mut_ptr(), &mut views) };
let expected_count = if N > MAX { MAX } else { N };
assert_eq!(count, expected_count);
let mut s = 0;
while s < count {
let canon = resolve_canonical::<N>(&markers, s);
let got = unsafe { views[s].assume_init_ref() }.raw_ptr() as usize;
assert_eq!(got - base, oracle.slot_offsets[canon]);
s += 1;
}
assert_eq!(ix.len(), oracle.instruction_data_range.len());
assert_eq!(
ix.as_ptr() as usize - base,
oracle.instruction_data_range.start
);
assert_eq!(oracle.program_id_offset, oracle.instruction_data_range.end);
let pid_bytes = pid.as_array();
let poff = oracle.program_id_offset;
let mut w = 0;
while w < 4 {
let o = w * 8;
let got = u64::from_le_bytes([
pid_bytes[o],
pid_bytes[o + 1],
pid_bytes[o + 2],
pid_bytes[o + 3],
pid_bytes[o + 4],
pid_bytes[o + 5],
pid_bytes[o + 6],
pid_bytes[o + 7],
]);
let want = u64::from_le_bytes([
backing.0[poff + o],
backing.0[poff + o + 1],
backing.0[poff + o + 2],
backing.0[poff + o + 3],
backing.0[poff + o + 4],
backing.0[poff + o + 5],
backing.0[poff + o + 6],
backing.0[poff + o + 7],
]);
assert_eq!(got, want);
w += 1;
}
}
#[kani::proof]
#[kani::unwind(10)]
fn differential_zero_accounts() {
check_fused_walk_against_oracle::<0, 4, { frame_len(0) }>();
}
#[kani::proof]
#[kani::unwind(10)]
fn differential_one_canonical_account() {
check_fused_walk_against_oracle::<1, 4, { frame_len(1) }>();
}
#[kani::proof]
#[kani::unwind(10)]
fn differential_two_accounts_symbolic_markers() {
check_fused_walk_against_oracle::<2, 4, { frame_len(2) }>();
}
#[kani::proof]
#[kani::unwind(10)]
fn differential_three_accounts_symbolic_markers() {
check_fused_walk_against_oracle::<3, 4, { frame_len(3) }>();
}
#[kani::proof]
#[kani::unwind(10)]
fn differential_skip_only_tail_beyond_max() {
check_fused_walk_against_oracle::<3, 1, { frame_len(3) }>();
}
#[kani::proof]
#[kani::unwind(10)]
fn differential_fast_walk_two_accounts() {
const N: usize = 2;
const LEN: usize = frame_len(N);
let markers = [u8::MAX, any_valid_marker(1)];
let data_lens = [any_bounded_data_len(), any_bounded_data_len()];
let mut backing = AlignedBuf::<LEN>([0u8; LEN]);
write_frame::<N>(&mut backing.0, &markers, &data_lens, 0);
let oracle = parse_instruction_frame_checked(&backing.0)
.expect("oracle must accept a well-formed loader frame");
let base = backing.0.as_ptr() as usize;
let mut views: [MaybeUninit<AccountView<'_>>; 4] =
unsafe { MaybeUninit::uninit().assume_init() };
static EMPTY_IX: [u8; 0] = [];
let program_id = Address::new_from_array(PID_SENTINEL);
let (pid, count, ix) = unsafe {
deserialize_accounts_fast::<4>(
backing.0.as_mut_ptr(),
&mut views,
&EMPTY_IX,
&program_id,
)
};
assert_eq!(count, N);
assert_eq!(ix.len(), 0);
assert_eq!(pid.as_array(), &PID_SENTINEL);
let mut s = 0;
while s < count {
let canon = resolve_canonical::<N>(&markers, s);
let got = unsafe { views[s].assume_init_ref() }.raw_ptr() as usize;
assert_eq!(got - base, oracle.slot_offsets[canon]);
s += 1;
}
}
#[kani::proof]
#[kani::unwind(10)]
fn differential_scan_frame_two_accounts() {
const N: usize = 2;
const LEN: usize = frame_len(N);
let markers = [u8::MAX, any_valid_marker(1)];
let data_lens = [any_bounded_data_len(), any_bounded_data_len()];
let ix_len = any_bounded_ix_len();
let mut backing = AlignedBuf::<LEN>([0u8; LEN]);
write_frame::<N>(&mut backing.0, &markers, &data_lens, ix_len);
let oracle = parse_instruction_frame_checked(&backing.0)
.expect("oracle must accept a well-formed loader frame");
let base = backing.0.as_ptr() as usize;
let frame = unsafe { scan_instruction_frame(backing.0.as_mut_ptr()) };
assert_eq!(frame.account_count, N);
assert_eq!(frame.accounts_start as usize - base, 8);
assert_eq!(
frame.instruction_data.len(),
oracle.instruction_data_range.len()
);
assert_eq!(
frame.instruction_data.as_ptr() as usize - base,
oracle.instruction_data_range.start
);
assert_eq!(
frame.program_id.as_array().as_slice(),
&backing.0[oracle.program_id_offset..oracle.program_id_offset + 32]
);
}
const fn trap_frame_len(dl: usize) -> usize {
next_record_offset(8, dl) + 8 + 8 + 32
}
const TRAP_LEN_DL0: usize = trap_frame_len(0);
const TRAP_LEN_DL_NONZERO: usize = trap_frame_len(1);
const TRAP_LEN_SLOT_ZERO: usize = 8 + 8 + 8 + 32;
const _: () = {
let mut dl = 1;
while dl <= MAX_DL {
assert!(trap_frame_len(dl) == TRAP_LEN_DL_NONZERO);
dl += 1;
}
assert!(TRAP_LEN_DL0 < TRAP_LEN_DL_NONZERO);
};
fn build_two_slot_trap_frame<const LEN: usize>(
dl_min: usize,
dl_max: usize,
) -> (AlignedBuf<LEN>, usize) {
let bad: u8 = kani::any();
kani::assume(bad != u8::MAX && bad as usize >= 1);
let dl: usize = kani::any();
kani::assume(dl >= dl_min && dl <= dl_max);
let mut backing = AlignedBuf::<LEN>([0u8; LEN]);
let end = write_frame::<2>(&mut backing.0, &[u8::MAX, bad], &[dl, 0], 0);
(backing, end)
}
fn build_slot_zero_trap_frame(marker: u8) -> (AlignedBuf<TRAP_LEN_SLOT_ZERO>, usize) {
let mut backing = AlignedBuf::<TRAP_LEN_SLOT_ZERO>([0u8; TRAP_LEN_SLOT_ZERO]);
let end = write_frame::<1>(&mut backing.0, &[marker], &[0], 0);
(backing, end)
}
#[kani::proof]
#[kani::unwind(10)]
fn trap_frame_layout_is_exact_dl0() {
let (_backing, end) = build_two_slot_trap_frame::<TRAP_LEN_DL0>(0, 0);
assert_eq!(end, TRAP_LEN_DL0);
}
#[kani::proof]
#[kani::unwind(10)]
fn trap_frame_layout_is_exact_dl_nonzero() {
let (_backing, end) = build_two_slot_trap_frame::<TRAP_LEN_DL_NONZERO>(1, MAX_DL);
assert_eq!(end, TRAP_LEN_DL_NONZERO);
}
#[kani::proof]
#[kani::unwind(10)]
fn trap_frame_layout_is_exact_slot_zero() {
let marker: u8 = kani::any();
kani::assume(marker != u8::MAX);
let (_backing, end) = build_slot_zero_trap_frame(marker);
assert_eq!(end, TRAP_LEN_SLOT_ZERO);
}
fn trap_deserialize_two_slot<const MAX: usize, const LEN: usize>(dl_min: usize, dl_max: usize) {
let (mut backing, _end) = build_two_slot_trap_frame::<LEN>(dl_min, dl_max);
let mut views: [MaybeUninit<AccountView<'_>>; MAX] =
unsafe { MaybeUninit::uninit().assume_init() };
let _ = unsafe { deserialize_accounts::<MAX>(backing.0.as_mut_ptr(), &mut views) };
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_fires_on_malformed_marker_in_materialize_range_dl0() {
trap_deserialize_two_slot::<4, TRAP_LEN_DL0>(0, 0);
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_fires_on_malformed_marker_in_materialize_range_dl_nonzero() {
trap_deserialize_two_slot::<4, TRAP_LEN_DL_NONZERO>(1, MAX_DL);
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_fires_on_malformed_marker_in_skip_only_tail_dl0() {
trap_deserialize_two_slot::<1, TRAP_LEN_DL0>(0, 0);
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_fires_on_malformed_marker_in_skip_only_tail_dl_nonzero() {
trap_deserialize_two_slot::<1, TRAP_LEN_DL_NONZERO>(1, MAX_DL);
}
fn trap_fast_walk_two_slot<const LEN: usize>(dl_min: usize, dl_max: usize) {
let (mut backing, _end) = build_two_slot_trap_frame::<LEN>(dl_min, dl_max);
let mut views: [MaybeUninit<AccountView<'_>>; 4] =
unsafe { MaybeUninit::uninit().assume_init() };
static EMPTY_IX: [u8; 0] = [];
let program_id = Address::new_from_array(PID_SENTINEL);
let _ = unsafe {
deserialize_accounts_fast::<4>(
backing.0.as_mut_ptr(),
&mut views,
&EMPTY_IX,
&program_id,
)
};
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_fires_in_fast_walk_dl0() {
trap_fast_walk_two_slot::<TRAP_LEN_DL0>(0, 0);
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_fires_in_fast_walk_dl_nonzero() {
trap_fast_walk_two_slot::<TRAP_LEN_DL_NONZERO>(1, MAX_DL);
}
fn trap_scan_frame_two_slot<const LEN: usize>(dl_min: usize, dl_max: usize) {
let (mut backing, _end) = build_two_slot_trap_frame::<LEN>(dl_min, dl_max);
let _ = unsafe { scan_instruction_frame(backing.0.as_mut_ptr()) };
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_fires_in_scan_frame_dl0() {
trap_scan_frame_two_slot::<TRAP_LEN_DL0>(0, 0);
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_fires_in_scan_frame_dl_nonzero() {
trap_scan_frame_two_slot::<TRAP_LEN_DL_NONZERO>(1, MAX_DL);
}
fn trap_slot_zero(marker: u8) {
let (mut backing, _end) = build_slot_zero_trap_frame(marker);
let mut views: [MaybeUninit<AccountView<'_>>; 4] =
unsafe { MaybeUninit::uninit().assume_init() };
let _ = unsafe { deserialize_accounts::<4>(backing.0.as_mut_ptr(), &mut views) };
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_fires_on_any_duplicate_marker_at_slot_zero() {
let bad: u8 = kani::any();
kani::assume(bad != u8::MAX);
trap_slot_zero(bad);
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_slot_zero_marker_0x00_self_reference() {
trap_slot_zero(0x00);
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_slot_zero_marker_0x01_forward_reference() {
trap_slot_zero(0x01);
}
#[kani::proof]
#[kani::unwind(10)]
#[kani::should_panic]
fn trap_slot_zero_marker_0xfe_max_forward_reference() {
trap_slot_zero(0xFE);
}
#[kani::proof]
#[kani::unwind(10)]
fn oracle_rejects_exactly_the_malformed_markers() {
const LEN: usize = frame_len(2);
let m0: u8 = kani::any();
let m1: u8 = kani::any();
let data_lens = [any_bounded_data_len(), any_bounded_data_len()];
let ix_len = any_bounded_ix_len();
let mut backing = AlignedBuf::<LEN>([0u8; LEN]);
write_frame::<2>(&mut backing.0, &[m0, m1], &data_lens, ix_len);
let result = parse_instruction_frame_checked(&backing.0);
let well_formed = m0 == u8::MAX && (m1 == u8::MAX || m1 == 0);
assert_eq!(result.is_ok(), well_formed);
if let Err(err) = result {
assert!(matches!(err, FrameError::MalformedDuplicateMarker { .. }));
}
}
}