use core::cell::Cell;
use core::mem::MaybeUninit;
use crate::account_view::AccountView;
use crate::address::Address;
use crate::error::ProgramError;
use crate::raw_account::RuntimeAccount;
use crate::MAX_PERMITTED_DATA_INCREASE;
const BPF_ALIGN_OF_U128: usize = 8;
#[inline(always)]
const fn non_dup_stride(data_len: usize) -> usize {
RuntimeAccount::SIZE
+ MAX_PERMITTED_DATA_INCREASE
+ 8
+ ((data_len + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1))
}
pub struct LazyContext<'info> {
cursor: *mut u8,
total_accounts: usize,
declared_accounts: usize,
parsed_count: usize,
tail: Cell<*const u8>,
resolved: [MaybeUninit<AccountView<'info>>; 254],
}
#[cfg(target_os = "solana")]
unsafe impl<'info> Send for LazyContext<'info> {}
#[cfg(target_os = "solana")]
unsafe impl<'info> Sync for LazyContext<'info> {}
impl<'info> LazyContext<'info> {
#[inline]
fn tail_ptr(&self) -> *const u8 {
let cached = self.tail.get();
if !cached.is_null() {
return cached;
}
let mut scan = self.cursor as *const u8;
let mut slot = self.parsed_count;
unsafe {
while slot < self.declared_accounts {
let marker = *scan;
if marker == u8::MAX {
let raw = scan as *const RuntimeAccount;
let data_len = (*raw).data_len as usize;
scan = scan.add(non_dup_stride(data_len));
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
crate::raw_input::malformed_duplicate_marker(marker, slot);
}
scan = scan.add(8);
}
slot += 1;
}
}
self.tail.set(scan);
scan
}
#[inline(always)]
pub fn instruction_data(&self) -> &[u8] {
let tail = self.tail_ptr();
unsafe {
let len = core::ptr::read_unaligned(tail as *const u64) as usize;
core::slice::from_raw_parts(tail.add(8), len)
}
}
#[inline(always)]
pub fn program_id(&self) -> &Address {
let tail = self.tail_ptr();
unsafe {
let len = core::ptr::read_unaligned(tail as *const u64) as usize;
&*(tail.add(8 + len) as *const Address)
}
}
#[inline(always)]
pub fn total_accounts(&self) -> usize {
self.total_accounts
}
#[inline(always)]
pub fn parsed_count(&self) -> usize {
self.parsed_count
}
#[inline(always)]
pub fn remaining(&self) -> usize {
self.total_accounts - self.parsed_count
}
#[inline]
pub fn next_account(&mut self) -> Result<AccountView<'info>, ProgramError> {
if self.parsed_count >= self.total_accounts {
return Err(ProgramError::NotEnoughAccountKeys);
}
let view = unsafe { self.parse_one_account() };
self.resolved[self.parsed_count] = MaybeUninit::new(view.clone());
self.parsed_count += 1;
Ok(view)
}
#[inline]
pub fn next_signer(&mut self) -> Result<AccountView<'info>, ProgramError> {
let acct = self.next_account()?;
acct.require_signer()?;
Ok(acct)
}
#[inline]
pub fn next_writable(&mut self) -> Result<AccountView<'info>, ProgramError> {
let acct = self.next_account()?;
acct.require_writable()?;
Ok(acct)
}
#[inline]
pub fn next_payer(&mut self) -> Result<AccountView<'info>, ProgramError> {
let acct = self.next_account()?;
acct.require_payer()?;
Ok(acct)
}
#[inline]
pub fn next_owned_by(&mut self, program: &Address) -> Result<AccountView<'info>, ProgramError> {
let acct = self.next_account()?;
acct.require_owned_by(program)?;
Ok(acct)
}
#[inline]
pub fn skip(&mut self, n: usize) -> Result<(), ProgramError> {
for _ in 0..n {
if self.parsed_count >= self.total_accounts {
return Err(ProgramError::NotEnoughAccountKeys);
}
let view = unsafe { self.parse_one_account() };
self.resolved[self.parsed_count] = MaybeUninit::new(view);
self.parsed_count += 1;
}
Ok(())
}
#[inline]
pub fn drain_remaining(&mut self) -> Result<&[AccountView<'info>], ProgramError> {
let start = self.parsed_count;
while self.parsed_count < self.total_accounts {
let view = unsafe { self.parse_one_account() };
self.resolved[self.parsed_count] = MaybeUninit::new(view);
self.parsed_count += 1;
}
unsafe {
Ok(core::slice::from_raw_parts(
self.resolved.as_ptr().add(start) as *const AccountView<'info>,
self.parsed_count - start,
))
}
}
#[inline(always)]
pub fn get(&self, index: usize) -> Option<&AccountView<'info>> {
if index < self.parsed_count {
Some(unsafe { self.resolved[index].assume_init_ref() })
} else {
None
}
}
#[inline(always)]
unsafe fn parse_one_account(&mut self) -> AccountView<'info> {
unsafe {
let dup_marker = *self.cursor;
if dup_marker == u8::MAX {
let raw = self.cursor as *mut RuntimeAccount;
let view = AccountView::new_unchecked(raw);
view.initialize_original_data_len();
let data_len = (*raw).data_len as usize;
self.cursor = self.cursor.add(non_dup_stride(data_len));
view
} else {
let original_idx = dup_marker as usize;
self.cursor = self.cursor.add(8); if original_idx >= self.parsed_count {
crate::raw_input::malformed_duplicate_marker(dup_marker, self.parsed_count);
}
self.resolved[original_idx].assume_init_ref().clone()
}
}
}
}
#[inline(always)]
pub unsafe fn lazy_deserialize<'info>(input: *mut u8) -> LazyContext<'info> {
let num_accounts = unsafe { core::ptr::read_unaligned(input as *const u64) as usize };
let accounts_start = unsafe { input.add(8) };
let total_accounts = if num_accounts > 254 {
254
} else {
num_accounts
};
let resolved: [MaybeUninit<AccountView<'info>>; 254] =
unsafe { MaybeUninit::uninit().assume_init() };
LazyContext {
cursor: accounts_start,
total_accounts,
declared_accounts: num_accounts,
parsed_count: 0,
tail: Cell::new(core::ptr::null()),
resolved,
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::vec;
use std::vec::Vec;
use super::*;
use crate::raw_input::parse_instruction_frame_checked;
enum Slot {
Fresh {
data_len: usize,
lamports: u64,
signer: bool,
},
Dup(u8),
}
fn fresh(data_len: usize, lamports: u64) -> Slot {
Slot::Fresh {
data_len,
lamports,
signer: true,
}
}
struct Frame {
words: Vec<u64>,
byte_len: usize,
}
impl Frame {
fn as_mut_ptr(&mut self) -> *mut u8 {
self.words.as_mut_ptr() as *mut u8
}
fn as_bytes(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.words.as_ptr() as *const u8, self.byte_len) }
}
}
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_len,
lamports,
signer,
} => {
let mut header = [0u8; RuntimeAccount::SIZE];
header[0] = 0xFF; header[1] = if *signer { 1 } else { 0 }; 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(&vec![0xABu8; *data_len]);
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 byte_len = buf.len();
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, byte_len }
}
const PID: [u8; 32] = [0xC4; 32];
fn assert_base_aligned(frame: &mut Frame) {
assert_eq!(
frame.as_mut_ptr() as usize % 8,
0,
"fixture base must be 8-aligned"
);
}
#[test]
fn zero_accounts_serves_ix_and_pid_before_any_consume() {
let mut frame = build_frame(&[], &[9, 8, 7], PID);
assert_base_aligned(&mut frame);
let ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
assert_eq!(ctx.total_accounts(), 0);
assert_eq!(ctx.remaining(), 0);
assert_eq!(ctx.instruction_data(), &[9, 8, 7]);
assert_eq!(ctx.program_id().as_array(), &PID);
}
#[test]
fn one_account_ix_before_consume_then_account() {
let mut frame = build_frame(&[fresh(11, 42)], &[1, 2, 3, 4], PID);
assert_base_aligned(&mut frame);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
assert_eq!(ctx.instruction_data(), &[1, 2, 3, 4]);
assert_eq!(ctx.program_id().as_array(), &PID);
let a = ctx.next_account().expect("one account");
assert_eq!(a.data_len(), 11);
assert_eq!(a.lamports(), 42);
assert!(a.is_signer());
assert_eq!(ctx.remaining(), 0);
assert!(ctx.next_account().is_err());
}
#[test]
fn ix_after_consuming_all_accounts() {
let slots = [fresh(3, 1), fresh(0, 2), fresh(9, 3)];
let mut frame = build_frame(&slots, &[0xEE, 0xEF], PID);
assert_base_aligned(&mut frame);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
for _ in 0..3 {
ctx.next_account().unwrap();
}
assert_eq!(ctx.instruction_data(), &[0xEE, 0xEF]);
assert_eq!(ctx.program_id().as_array(), &PID);
}
#[test]
fn ix_after_consuming_some_accounts() {
let slots = [fresh(5, 1), fresh(6, 2), fresh(7, 3), fresh(8, 4)];
let mut frame = build_frame(&slots, &[0xD1, 0xD2, 0xD3], PID);
assert_base_aligned(&mut frame);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
ctx.next_account().unwrap();
ctx.next_account().unwrap();
assert_eq!(ctx.instruction_data(), &[0xD1, 0xD2, 0xD3]);
assert_eq!(ctx.next_account().unwrap().data_len(), 7);
assert_eq!(ctx.next_account().unwrap().data_len(), 8);
assert!(ctx.next_account().is_err());
}
#[test]
fn memoized_tail_is_stable_across_calls_and_consumes() {
let slots = [fresh(4, 1), fresh(5, 2)];
let mut frame = build_frame(&slots, &[7, 7, 7], PID);
assert_base_aligned(&mut frame);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
let d1 = ctx.instruction_data().as_ptr();
ctx.next_account().unwrap();
let d2 = ctx.instruction_data().as_ptr();
ctx.next_account().unwrap();
let d3 = ctx.instruction_data().as_ptr();
assert_eq!(d1, d2);
assert_eq!(d2, d3);
assert_eq!(ctx.instruction_data(), &[7, 7, 7]);
}
fn assert_lazy_agrees(slots: &[Slot], ix_data: &[u8]) {
let mut frame = build_frame(slots, ix_data, PID);
assert_base_aligned(&mut frame);
let bytes = frame.as_bytes().to_vec();
let checked = parse_instruction_frame_checked(&bytes).expect("well-formed");
let base = frame.as_mut_ptr() as usize;
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
let visible = checked.account_count.min(254);
assert_eq!(ctx.total_accounts(), visible);
for i in 0..visible {
let view = ctx.next_account().expect("account in range");
let off = checked.slot_offsets[i];
let ptr_off = view.account_ptr() as usize - base;
if bytes[off] == 0xFF {
assert_eq!(ptr_off, off, "canonical slot {i} pointer mismatch");
} else {
let dup_of = bytes[off] as usize;
assert_eq!(
ptr_off, checked.slot_offsets[dup_of],
"dup slot {i} must alias canonical {dup_of}"
);
}
assert_eq!(ctx.get(i).unwrap().account_ptr(), view.account_ptr());
}
assert_eq!(
ctx.instruction_data(),
&bytes[checked.instruction_data_range.clone()]
);
assert_eq!(
ctx.program_id().as_array().as_slice(),
&bytes[checked.program_id_offset..checked.program_id_offset + 32]
);
}
#[test]
fn agrees_zero_accounts() {
assert_lazy_agrees(&[], &[]);
assert_lazy_agrees(&[], &[1, 2, 3]);
}
#[test]
fn agrees_one_account() {
assert_lazy_agrees(&[fresh(0, 1)], &[]); assert_lazy_agrees(&[fresh(1, 1)], &[9]);
}
#[test]
fn agrees_with_duplicates() {
let slots = [
fresh(9, 7),
Slot::Dup(0),
fresh(3, 8),
Slot::Dup(2),
Slot::Dup(0),
];
assert_lazy_agrees(&slots, &[0x11, 0x22]);
}
#[test]
fn agrees_every_data_len_residue() {
for base in [0usize, 8] {
let slots: Vec<Slot> = (0..8).map(|r| fresh(base + r, r as u64)).collect();
assert_lazy_agrees(&slots, &[0x42; 5]);
}
}
#[test]
fn agrees_huge_data_len() {
let big = 100_003usize; assert_lazy_agrees(&[fresh(big, 5), fresh(2, 6)], &[0x77, 0x66]);
}
#[test]
fn agrees_exactly_max_254_accounts() {
let mut slots: Vec<Slot> = vec![fresh(4, 9)];
slots.extend((0..253).map(|_| Slot::Dup(0)));
assert_eq!(slots.len(), 254);
assert_lazy_agrees(&slots, &[0x0F; 3]);
}
#[test]
fn max_plus_accounts_clamp_and_tail_still_found() {
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);
assert_base_aligned(&mut frame);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
assert_eq!(ctx.total_accounts(), 254);
assert_eq!(ctx.instruction_data(), &[0x0F; 3]);
assert_eq!(ctx.program_id().as_array(), &PID);
for _ in 0..254 {
ctx.next_account().unwrap();
}
assert_eq!(ctx.remaining(), 0);
assert!(ctx.next_account().is_err());
}
#[test]
fn skip_advances_and_populates_get() {
let slots = [fresh(1, 1), fresh(2, 2), fresh(3, 3)];
let mut frame = build_frame(&slots, &[0xAB], PID);
assert_base_aligned(&mut frame);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
ctx.skip(2).unwrap();
assert_eq!(ctx.parsed_count(), 2);
assert_eq!(ctx.get(0).unwrap().data_len(), 1);
assert_eq!(ctx.get(1).unwrap().data_len(), 2);
assert!(ctx.get(2).is_none());
assert_eq!(ctx.next_account().unwrap().data_len(), 3);
assert_eq!(ctx.instruction_data(), &[0xAB]);
}
#[test]
fn skip_past_end_errors() {
let slots = [fresh(1, 1)];
let mut frame = build_frame(&slots, &[], PID);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
assert!(ctx.skip(2).is_err());
}
#[test]
fn drain_remaining_returns_all() {
let slots = [fresh(4, 1), fresh(5, 2), fresh(6, 3)];
let mut frame = build_frame(&slots, &[0x01], PID);
assert_base_aligned(&mut frame);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
ctx.next_account().unwrap();
let rest = ctx.drain_remaining().unwrap();
assert_eq!(rest.len(), 2);
assert_eq!(rest[0].data_len(), 5);
assert_eq!(rest[1].data_len(), 6);
assert_eq!(ctx.remaining(), 0);
assert_eq!(ctx.instruction_data(), &[0x01]);
}
#[test]
fn drain_with_duplicates_aliases_canonical() {
let slots = [fresh(9, 1), Slot::Dup(0), fresh(3, 2)];
let mut frame = build_frame(&slots, &[], PID);
assert_base_aligned(&mut frame);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
let all = ctx.drain_remaining().unwrap();
assert_eq!(all.len(), 3);
assert_eq!(
all[0].account_ptr(),
all[1].account_ptr(),
"dup aliases canonical"
);
assert_ne!(all[0].account_ptr(), all[2].account_ptr());
assert_eq!(all[1].data_len(), 9);
}
#[test]
#[should_panic(expected = "malformed duplicate marker")]
fn forward_duplicate_traps_on_consume() {
let slots = [fresh(1, 1), Slot::Dup(1)]; let mut frame = build_frame(&slots, &[], PID);
let mut ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
ctx.next_account().unwrap();
let _ = ctx.next_account();
}
#[test]
#[should_panic(expected = "malformed duplicate marker")]
fn forward_duplicate_traps_in_tail_scan() {
let slots = [fresh(1, 1), Slot::Dup(5)];
let mut frame = build_frame(&slots, &[], PID);
let ctx = unsafe { lazy_deserialize(frame.as_mut_ptr()) };
let _ = ctx.instruction_data();
}
}