use core::mem::MaybeUninit;
use hopper_native::raw_input::{
deserialize_accounts, deserialize_accounts_0449, deserialize_accounts_0449_checked,
DirectMappingError,
};
use hopper_native::{AccountView, ProgramError, RuntimeAccount, MAX_PERMITTED_DATA_INCREASE};
const ALIGN: usize = 8;
enum Slot {
Fresh { data_len: usize, lamports: u64 },
Dup(u8),
}
struct Frame {
words: Vec<u64>,
canonical_offsets: Vec<usize>,
}
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], with_0449: bool) -> Frame {
let mut buf: Vec<u8> = Vec::new();
let mut canonical_offsets: Vec<usize> = 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 } => {
canonical_offsets.push(buf.len());
let mut header = [0u8; RuntimeAccount::SIZE];
header[0] = 0xFF; header[1] = 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(ALIGN) {
buf.push(0);
}
buf.extend_from_slice(&u64::MAX.to_le_bytes()); }
Slot::Dup(of) => {
canonical_offsets.push(canonical_offsets[*of as usize]);
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 table_offset = None;
if with_0449 {
while !buf.len().is_multiple_of(ALIGN) {
buf.push(0);
}
table_offset = Some(buf.len());
for _ in slots {
buf.extend_from_slice(&0u64.to_le_bytes());
}
}
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());
}
let mut frame = Frame {
words,
canonical_offsets,
};
if let Some(table_at) = table_offset {
let base = frame.as_mut_ptr() as usize;
for (i, rec_off) in frame.canonical_offsets.clone().iter().enumerate() {
let entry = (base + rec_off) as u64;
unsafe {
core::ptr::write_unaligned(
(frame.as_mut_ptr()).add(table_at + 8 * i) as *mut u64,
entry,
);
}
}
}
frame
}
fn walk<const MAX: usize>(frame: &mut Frame) -> (Vec<AccountView<'static>>, Vec<u8>, [u8; 32]) {
const UNINIT: MaybeUninit<AccountView<'static>> = MaybeUninit::uninit();
let mut accounts = [UNINIT; MAX];
let (program_id, count, ix) =
unsafe { deserialize_accounts::<MAX>(frame.as_mut_ptr(), &mut accounts) };
let views = accounts[..count]
.iter()
.map(|slot| unsafe { slot.assume_init_ref() }.to_owned())
.collect();
(views, ix.to_vec(), *program_id.as_array())
}
fn locate_0449_tail(frame: &mut Frame, ix: &[u8], program_id: [u8; 32]) -> (*mut u8, usize, usize) {
let base = frame.as_mut_ptr();
let len = frame.words.len() * 8;
let bytes = unsafe { core::slice::from_raw_parts(base as *const u8, len) };
let ix_offset = bytes
.windows(ix.len() + 32)
.position(|w| w[..ix.len()] == *ix && w[ix.len()..] == program_id)
.expect("instruction tail present exactly once");
let table_address = (base as usize + ix_offset + ix.len() + 32 + 7) & !7usize;
(base, ix_offset, table_address - base as usize)
}
#[cfg(feature = "simd-0321")]
mod fast_entrypoint_expands {
use hopper_native::{Address, ProgramResult};
hopper_native::hopper_fast_entrypoint!(process, 4);
pub fn process(
_program_id: &Address,
_accounts: &[hopper_native::AccountView],
_instruction_data: &[u8],
) -> ProgramResult {
Ok(())
}
}
#[test]
fn stride_walk_pins_the_loader_geometry_across_data_len_residues() {
for residue in 0..=16usize {
let ix = [9u8, 8, 7];
let pid = [0x77u8; 32];
let mut frame = build_frame(
&[
Slot::Fresh {
data_len: residue,
lamports: 11,
},
Slot::Dup(0),
Slot::Fresh {
data_len: 100 + residue,
lamports: 22,
},
],
&ix,
pid,
false,
);
let (views, got_ix, got_pid) = walk::<8>(&mut frame);
assert_eq!(views.len(), 3, "residue {residue}");
assert_eq!(views[0].address().as_array(), &[1u8; 32]);
assert_eq!(views[2].address().as_array(), &[3u8; 32]);
assert_eq!(views[0].lamports(), 11);
assert_eq!(views[2].lamports(), 22);
assert_eq!(views[0].data_len(), residue);
assert_eq!(views[2].data_len(), 100 + residue);
assert_eq!(views[1], views[0], "dup must alias slot 0");
assert_eq!(got_ix, ix, "residue {residue}");
assert_eq!(got_pid, pid, "residue {residue}");
}
}
#[test]
fn account_view_stays_pointer_shaped_for_the_0449_table_cast() {
assert_eq!(core::mem::size_of::<AccountView<'static>>(), 8);
assert_eq!(core::mem::align_of::<AccountView<'static>>(), 8);
}
#[test]
fn simd_0449_table_resolves_to_exactly_the_stride_walk_views() {
let ix = [1u8, 2, 3, 4, 5];
let pid = [0x66u8; 32];
let slots = [
Slot::Fresh {
data_len: 32,
lamports: 5,
},
Slot::Fresh {
data_len: 7, lamports: 6,
},
Slot::Dup(1),
Slot::Fresh {
data_len: 0,
lamports: 7,
},
];
let mut frame = build_frame(&slots, &ix, pid, true);
let (walk_views, walk_ix, _) = walk::<8>(&mut frame);
assert_eq!(walk_views.len(), 4);
let base = frame.as_mut_ptr();
assert_eq!(walk_ix, ix);
let ix_in_frame = {
let frame_bytes = unsafe {
core::slice::from_raw_parts(base as *const u8, frame.words.len() * 8)
};
let pos = frame_bytes
.windows(ix.len() + 32)
.position(|w| w[..ix.len()] == ix && w[ix.len()..] == pid)
.expect("instruction tail present exactly once");
unsafe { core::slice::from_raw_parts(base.add(pos) as *const u8, ix.len()) }
};
let table_views = unsafe { deserialize_accounts_0449(base, ix_in_frame) };
assert_eq!(table_views.len(), walk_views.len());
for (i, (t, w)) in table_views.iter().zip(walk_views.iter()).enumerate() {
assert_eq!(t, w, "slot {i}: table view must alias the walk view");
}
assert_eq!(table_views[2], table_views[1]);
}
#[test]
fn checked_0449_decoder_matches_the_stride_walk_for_every_alignment_residue() {
for residue in 0..=16usize {
let ix = [0x31, residue as u8, 0x7F];
let pid = [0x42; 32];
let slots = [
Slot::Fresh {
data_len: residue,
lamports: 10,
},
Slot::Dup(0),
Slot::Fresh {
data_len: 33 + residue,
lamports: 20,
},
];
let mut frame = build_frame(&slots, &ix, pid, true);
let (base, ix_offset, _) = locate_0449_tail(&mut frame, &ix, pid);
let input_len = frame.words.len() * 8;
let ix_in_frame = unsafe { core::slice::from_raw_parts(base.add(ix_offset), ix.len()) };
const UNINIT: MaybeUninit<AccountView<'static>> = MaybeUninit::uninit();
let mut out = [UNINIT; 8];
let (got_pid, count, got_ix) =
unsafe { deserialize_accounts_0449_checked(base, input_len, &mut out, ix_in_frame) }
.expect("canonical table validates");
assert_eq!(count, 3, "residue {residue}");
assert_eq!(got_pid.as_array(), &pid);
assert_eq!(got_ix, ix);
let first = unsafe { out[0].assume_init_ref() };
let duplicate = unsafe { out[1].assume_init_ref() };
let last = unsafe { out[2].assume_init_ref() };
assert_eq!(first, duplicate, "residue {residue}: duplicate aliases");
assert_eq!(first.data_len(), residue);
assert_eq!(first.original_data_len(), residue);
assert_eq!(duplicate.original_data_len(), residue);
assert_eq!(last.data_len(), 33 + residue);
assert_eq!(last.original_data_len(), 33 + residue);
}
}
#[test]
fn resize_uses_entry_baseline_after_cpi_and_duplicate_aliases_share_it() {
let ix = [0x31];
let pid = [0x42; 32];
let mut frame = build_frame(
&[
Slot::Fresh {
data_len: 16,
lamports: 9,
},
Slot::Dup(0),
],
&ix,
pid,
false,
);
let (views, _, _) = walk::<4>(&mut frame);
let canonical = &views[0];
let duplicate = &views[1];
assert_eq!(canonical.original_data_len(), 16);
assert_eq!(duplicate.original_data_len(), 16);
let raw = canonical.account_ptr() as *mut RuntimeAccount;
unsafe {
(*raw).data_len = 128;
}
assert_eq!(canonical.data_len(), 128);
assert_eq!(duplicate.data_len(), 128);
assert_eq!(canonical.original_data_len(), 16);
assert_eq!(duplicate.original_data_len(), 16);
let rejected = 16 + MAX_PERMITTED_DATA_INCREASE + 1;
assert_eq!(
duplicate.resize_raw(rejected),
Err(ProgramError::InvalidRealloc)
);
assert_eq!(
canonical.data_len(),
128,
"rejection must not change data_len"
);
assert_eq!(canonical.original_data_len(), 16);
let maximum = 16 + MAX_PERMITTED_DATA_INCREASE;
duplicate.resize_raw(maximum).unwrap();
assert_eq!(canonical.data_len(), maximum);
assert_eq!(canonical.original_data_len(), 16);
assert_eq!(canonical.resize_delta(), MAX_PERMITTED_DATA_INCREASE as i32);
}
#[test]
fn direct_pointer_table_initializes_resize_baselines_before_views_escape() {
let ix = [0x51, 0x52];
let pid = [0x63; 32];
let mut frame = build_frame(
&[
Slot::Fresh {
data_len: 24,
lamports: 3,
},
Slot::Dup(0),
],
&ix,
pid,
true,
);
let canonical_offset = frame.canonical_offsets[0];
let (base, ix_offset, _) = locate_0449_tail(&mut frame, &ix, pid);
assert_eq!(
unsafe { (*(base.add(canonical_offset) as *const RuntimeAccount)).resize_delta },
0
);
let ix_in_frame = unsafe { core::slice::from_raw_parts(base.add(ix_offset), ix.len()) };
let views = unsafe { deserialize_accounts_0449(base, ix_in_frame) };
assert_eq!(views[0].original_data_len(), 24);
assert_eq!(views[1].original_data_len(), 24);
let raw = views[0].account_ptr() as *mut RuntimeAccount;
unsafe {
(*raw).data_len = 96;
}
assert_eq!(
views[1].resize_raw(24 + MAX_PERMITTED_DATA_INCREASE + 1),
Err(ProgramError::InvalidRealloc)
);
assert_eq!(views[0].data_len(), 96);
assert_eq!(views[0].original_data_len(), 24);
}
#[test]
fn safe_resize_preflights_borrows_and_writable_privilege_without_mutation() {
let ix = [0x71];
let pid = [0x82; 32];
let mut frame = build_frame(
&[Slot::Fresh {
data_len: 16,
lamports: 5,
}],
&ix,
pid,
false,
);
let (views, _, _) = walk::<2>(&mut frame);
let account = &views[0];
let reserve_byte = unsafe { account.data_ptr_unchecked().add(16) };
unsafe {
*reserve_byte = 0xA5;
}
let shared = account.try_borrow().unwrap();
assert_eq!(account.resize(20), Err(ProgramError::AccountBorrowFailed));
assert_eq!(account.data_len(), 16);
assert_eq!(
unsafe { *reserve_byte },
0xA5,
"rejected growth must not zero reserve"
);
drop(shared);
let exclusive = account.try_borrow_mut().unwrap();
assert_eq!(
account.resize_raw(8),
Err(ProgramError::AccountBorrowFailed)
);
assert_eq!(account.data_len(), 16);
drop(exclusive);
let raw = account.account_ptr() as *mut RuntimeAccount;
unsafe {
(*raw).is_writable = 0;
}
assert_eq!(account.resize(20), Err(ProgramError::Immutable));
assert_eq!(account.data_len(), 16);
assert_eq!(unsafe { *reserve_byte }, 0xA5);
}
#[test]
fn checked_0449_decoder_rejects_equal_but_out_of_frame_instruction_bytes() {
let ix = [1, 2, 3];
let pid = [7; 32];
let mut frame = build_frame(
&[Slot::Fresh {
data_len: 8,
lamports: 1,
}],
&ix,
pid,
true,
);
let base = frame.as_mut_ptr();
let input_len = frame.words.len() * 8;
const UNINIT: MaybeUninit<AccountView<'static>> = MaybeUninit::uninit();
let mut out = [UNINIT; 2];
let foreign = ix;
let err = unsafe { deserialize_accounts_0449_checked(base, input_len, &mut out, &foreign) }
.unwrap_err();
assert_eq!(err, DirectMappingError::InstructionDataMismatch);
}
#[test]
fn checked_0449_decoder_rejects_out_of_region_and_noncanonical_pointers() {
let ix = [9, 9];
let pid = [8; 32];
let slots = [
Slot::Fresh {
data_len: 4,
lamports: 1,
},
Slot::Fresh {
data_len: 5,
lamports: 2,
},
Slot::Dup(0),
];
let mut frame = build_frame(&slots, &ix, pid, true);
let (base, ix_offset, table_offset) = locate_0449_tail(&mut frame, &ix, pid);
let input_len = frame.words.len() * 8;
unsafe {
core::ptr::write_unaligned(base.add(table_offset) as *mut u64, 0);
}
let ix_in_frame = unsafe { core::slice::from_raw_parts(base.add(ix_offset), ix.len()) };
const UNINIT: MaybeUninit<AccountView<'static>> = MaybeUninit::uninit();
let mut out = [UNINIT; 4];
let err = unsafe { deserialize_accounts_0449_checked(base, input_len, &mut out, ix_in_frame) }
.unwrap_err();
assert_eq!(err, DirectMappingError::PointerOutOfBounds { slot: 0 });
let mut frame = build_frame(&slots, &ix, pid, true);
let (base, ix_offset, table_offset) = locate_0449_tail(&mut frame, &ix, pid);
let input_len = frame.words.len() * 8;
let wrong = base as usize + frame.canonical_offsets[1];
unsafe {
core::ptr::write_unaligned(base.add(table_offset) as *mut u64, wrong as u64);
}
let ix_in_frame = unsafe { core::slice::from_raw_parts(base.add(ix_offset), ix.len()) };
let mut out = [UNINIT; 4];
let err = unsafe { deserialize_accounts_0449_checked(base, input_len, &mut out, ix_in_frame) }
.unwrap_err();
assert_eq!(err, DirectMappingError::NonCanonicalPointer { slot: 0 });
let mut frame = build_frame(&slots, &ix, pid, true);
let (base, ix_offset, table_offset) = locate_0449_tail(&mut frame, &ix, pid);
let input_len = frame.words.len() * 8;
let wrong = base as usize + frame.canonical_offsets[1];
unsafe {
core::ptr::write_unaligned(
base.add(table_offset + 2 * core::mem::size_of::<u64>()) as *mut u64,
wrong as u64,
);
}
let ix_in_frame = unsafe { core::slice::from_raw_parts(base.add(ix_offset), ix.len()) };
let mut out = [UNINIT; 4];
let err = unsafe { deserialize_accounts_0449_checked(base, input_len, &mut out, ix_in_frame) }
.unwrap_err();
assert_eq!(err, DirectMappingError::NonCanonicalPointer { slot: 2 });
}
#[test]
fn checked_0449_decoder_rejects_malformed_duplicates_and_truncation() {
let ix = [5];
let pid = [6; 32];
let mut frame = build_frame(
&[Slot::Fresh {
data_len: 0,
lamports: 1,
}],
&ix,
pid,
true,
);
let (base, ix_offset, _) = locate_0449_tail(&mut frame, &ix, pid);
let input_len = frame.words.len() * 8;
unsafe {
*base.add(8) = 0;
}
let ix_in_frame = unsafe { core::slice::from_raw_parts(base.add(ix_offset), ix.len()) };
const UNINIT: MaybeUninit<AccountView<'static>> = MaybeUninit::uninit();
let mut out = [UNINIT; 2];
let err = unsafe { deserialize_accounts_0449_checked(base, input_len, &mut out, ix_in_frame) }
.unwrap_err();
assert_eq!(
err,
DirectMappingError::MalformedDuplicate {
slot: 0,
duplicate_of: 0
}
);
let mut frame = build_frame(
&[Slot::Fresh {
data_len: 0,
lamports: 1,
}],
&ix,
pid,
true,
);
let (base, ix_offset, table_offset) = locate_0449_tail(&mut frame, &ix, pid);
let ix_in_frame = unsafe { core::slice::from_raw_parts(base.add(ix_offset), ix.len()) };
let mut out = [UNINIT; 2];
let err =
unsafe { deserialize_accounts_0449_checked(base, table_offset, &mut out, ix_in_frame) }
.unwrap_err();
assert_eq!(err, DirectMappingError::TruncatedInput);
}
#[test]
fn checked_table_and_stride_views_have_identical_mutation_semantics() {
let ix = [0xA4, 0x01];
let pid = [0x91; 32];
let mut frame = build_frame(
&[
Slot::Fresh {
data_len: 32,
lamports: 50,
},
Slot::Dup(0),
],
&ix,
pid,
true,
);
let (walk_views, _, _) = walk::<4>(&mut frame);
let (base, ix_offset, _) = locate_0449_tail(&mut frame, &ix, pid);
let input_len = frame.words.len() * 8;
let ix_in_frame = unsafe { core::slice::from_raw_parts(base.add(ix_offset), ix.len()) };
const UNINIT: MaybeUninit<AccountView<'static>> = MaybeUninit::uninit();
let mut out = [UNINIT; 4];
let (_, count, _) =
unsafe { deserialize_accounts_0449_checked(base, input_len, &mut out, ix_in_frame) }
.unwrap();
assert_eq!(count, 2);
let table_first = unsafe { out[0].assume_init_ref() };
let table_duplicate = unsafe { out[1].assume_init_ref() };
{
let mut byte = table_first.segment_mut::<u8>(31, 1).unwrap();
*byte = 0xCD;
}
assert_eq!(walk_views[0].try_borrow().unwrap()[31], 0xCD);
assert_eq!(table_duplicate.try_borrow().unwrap()[31], 0xCD);
walk_views[0].resize(16).unwrap();
assert_eq!(table_first.data_len(), 16);
table_duplicate.set_lamports(77);
assert_eq!(walk_views[0].lamports(), 77);
let new_owner = hopper_native::Address::new_from_array([0xE1; 32]);
unsafe {
table_first.assign(&new_owner);
}
assert_eq!(walk_views[0].read_owner(), new_owner);
}