pub mod args;
pub mod phase;
use crate::account::SliceCursor;
use crate::account::{FixedLayout, Pod, HEADER_LEN};
use hopper_runtime::segment_borrow::SegmentBorrowRegistry;
use hopper_runtime::{
error::ProgramError, AccountView, Address, ProgramResult, Ref, RefMut, SegRef, SegRefMut,
SegmentLease,
};
pub const MAX_FRAME_ACCOUNTS: usize = 64;
pub struct Frame<'a> {
program_id: &'a Address,
accounts: &'a [AccountView],
ix_data: SliceCursor<'a>,
mutable_borrows: u64,
segment_borrows: SegmentBorrowRegistry,
}
impl<'a> Frame<'a> {
#[inline(always)]
pub fn new(
program_id: &'a Address,
accounts: &'a [AccountView],
instruction_data: &'a [u8],
) -> Result<Self, ProgramError> {
if accounts.len() > MAX_FRAME_ACCOUNTS {
return Err(ProgramError::InvalidArgument);
}
Ok(Self {
program_id,
accounts,
ix_data: SliceCursor::new(instruction_data),
mutable_borrows: 0,
segment_borrows: SegmentBorrowRegistry::new(),
})
}
#[inline(always)]
pub fn program_id(&self) -> &Address {
self.program_id
}
#[inline(always)]
pub fn account_count(&self) -> usize {
self.accounts.len()
}
#[inline(always)]
pub fn account_view(&self, index: usize) -> Result<&AccountView, ProgramError> {
self.accounts
.get(index)
.ok_or(ProgramError::NotEnoughAccountKeys)
}
#[inline(always)]
pub fn ix_data(&mut self) -> &mut SliceCursor<'a> {
&mut self.ix_data
}
#[inline(always)]
pub fn ix_data_raw(&self) -> &[u8] {
self.ix_data.data_from_position()
}
#[inline(always)]
pub fn account(&self, index: usize) -> Result<FrameAccount<'_>, ProgramError> {
let view = self
.accounts
.get(index)
.ok_or(ProgramError::NotEnoughAccountKeys)?;
Ok(FrameAccount { view })
}
#[inline]
pub fn account_mut(&mut self, index: usize) -> Result<FrameAccountMut<'_>, ProgramError> {
if index >= self.accounts.len() {
return Err(ProgramError::NotEnoughAccountKeys);
}
let bit = 1u64 << (index as u32);
if self.mutable_borrows & bit != 0 {
return Err(ProgramError::AccountBorrowFailed);
}
self.mutable_borrows |= bit;
let view = &self.accounts[index];
Ok(FrameAccountMut {
view,
borrow_mask: &mut self.mutable_borrows,
bit,
})
}
#[inline(always)]
pub fn segment_borrows(&self) -> &SegmentBorrowRegistry {
&self.segment_borrows
}
#[inline(always)]
pub fn segment_borrows_mut(&mut self) -> &mut SegmentBorrowRegistry {
&mut self.segment_borrows
}
#[inline]
pub fn segment_ref<'f, T: Pod + FixedLayout>(
&'f mut self,
index: usize,
offset: u32,
) -> Result<SegRef<'f, T>, ProgramError> {
let view = self
.accounts
.get(index)
.ok_or(ProgramError::NotEnoughAccountKeys)?;
let data = view.try_borrow()?;
let abs_offset = (HEADER_LEN as u32)
.checked_add(offset)
.ok_or(ProgramError::ArithmeticOverflow)?;
let end = abs_offset
.checked_add(T::SIZE as u32)
.ok_or(ProgramError::ArithmeticOverflow)?;
if end as usize > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let borrow = self.segment_borrows.register_leased_read(
view.address(),
abs_offset,
T::SIZE as u32,
)?;
let ptr = unsafe { data.as_bytes_ptr().add(abs_offset as usize) as *const T };
let inner: Ref<'f, T> = unsafe { data.project(ptr) };
let lease: SegmentLease<'f> =
unsafe { SegmentLease::new(&mut self.segment_borrows, borrow) };
Ok(SegRef::new(inner, lease))
}
#[inline]
pub fn segment_mut<'f, T: Pod + FixedLayout>(
&'f mut self,
index: usize,
offset: u32,
) -> Result<SegRefMut<'f, T>, ProgramError> {
let view = self
.accounts
.get(index)
.ok_or(ProgramError::NotEnoughAccountKeys)?;
if !view.is_writable() {
return Err(ProgramError::InvalidAccountData);
}
let data = view.try_borrow_mut()?;
let abs_offset = (HEADER_LEN as u32)
.checked_add(offset)
.ok_or(ProgramError::ArithmeticOverflow)?;
let end = abs_offset
.checked_add(T::SIZE as u32)
.ok_or(ProgramError::ArithmeticOverflow)?;
if end as usize > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let borrow = self.segment_borrows.register_leased_write(
view.address(),
abs_offset,
T::SIZE as u32,
)?;
let bytes_ptr = (&*data) as *const [u8] as *mut [u8] as *mut u8;
let ptr = unsafe { bytes_ptr.add(abs_offset as usize) as *mut T };
let inner: RefMut<'f, T> = unsafe { data.project(ptr) };
let lease: SegmentLease<'f> =
unsafe { SegmentLease::new(&mut self.segment_borrows, borrow) };
Ok(SegRefMut::new(inner, lease))
}
#[inline(always)]
pub unsafe fn segment_mut_unchecked<T: Pod + FixedLayout>(
&self,
index: usize,
offset: u32,
) -> Result<RefMut<'_, T>, ProgramError> {
let view = self
.accounts
.get(index)
.ok_or(ProgramError::NotEnoughAccountKeys)?;
let data = view.try_borrow_mut()?;
let abs_offset = (HEADER_LEN as u32)
.checked_add(offset)
.ok_or(ProgramError::ArithmeticOverflow)?;
let end = abs_offset
.checked_add(T::SIZE as u32)
.ok_or(ProgramError::ArithmeticOverflow)?;
if end as usize > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let bytes_ptr = (&*data) as *const [u8] as *mut [u8] as *mut u8;
let ptr = unsafe { bytes_ptr.add(abs_offset as usize) as *mut T };
Ok(unsafe { data.project(ptr) })
}
#[inline(always)]
pub fn require_signer(&self, index: usize) -> ProgramResult {
crate::check::check_signer(self.account_view(index)?)
}
#[inline(always)]
pub fn require_writable(&self, index: usize) -> ProgramResult {
crate::check::check_writable(self.account_view(index)?)
}
#[inline(always)]
pub fn require_owned(&self, index: usize) -> ProgramResult {
crate::check::check_owner(self.account_view(index)?, self.program_id)
}
#[inline(always)]
pub fn require_authority(&self, index: usize) -> ProgramResult {
let view = self.account_view(index)?;
crate::check::check_signer(view)?;
crate::check::check_writable(view)?;
Ok(())
}
#[inline(always)]
pub fn require_unique(&self, a: usize, b: usize) -> ProgramResult {
let va = self.account_view(a)?;
let vb = self.account_view(b)?;
crate::check::check_accounts_unique(va, vb)
}
#[inline(always)]
pub fn require_program(&self, index: usize, program: &Address) -> ProgramResult {
crate::check::check_address(self.account_view(index)?, program)
}
}
pub struct FrameAccount<'a> {
view: &'a AccountView,
}
impl<'a> FrameAccount<'a> {
#[inline(always)]
pub fn view(&self) -> &AccountView {
self.view
}
#[inline(always)]
pub fn address(&self) -> &Address {
self.view.address()
}
#[inline(always)]
pub fn data(&self) -> Result<Ref<'a, [u8]>, ProgramError> {
self.view.try_borrow()
}
#[inline(always)]
pub fn lamports(&self) -> u64 {
self.view.lamports()
}
#[inline(always)]
pub fn is_signer(&self) -> bool {
self.view.is_signer()
}
#[inline(always)]
pub fn is_writable(&self) -> bool {
self.view.is_writable()
}
}
pub struct FrameAccountMut<'a> {
view: &'a AccountView,
borrow_mask: &'a mut u64,
bit: u64,
}
impl<'a> FrameAccountMut<'a> {
#[inline(always)]
pub fn view(&self) -> &AccountView {
self.view
}
#[inline(always)]
pub fn address(&self) -> &Address {
self.view.address()
}
#[inline(always)]
pub fn data(&self) -> Result<Ref<'a, [u8]>, ProgramError> {
self.view.try_borrow()
}
#[inline(always)]
pub fn data_mut(&self) -> Result<RefMut<'a, [u8]>, ProgramError> {
self.view.try_borrow_mut()
}
#[inline(always)]
pub fn lamports(&self) -> u64 {
self.view.lamports()
}
}
impl<'a> Drop for FrameAccountMut<'a> {
fn drop(&mut self) {
*self.borrow_mask &= !self.bit;
}
}
#[cfg(all(test, feature = "hopper-native-backend"))]
mod audit_tests {
use super::*;
use hopper_native::{
AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
};
#[repr(C)]
#[derive(Clone, Copy)]
struct Counter {
value: u64,
}
unsafe impl hopper_runtime::__hopper_native::bytemuck::Zeroable for Counter {}
unsafe impl hopper_runtime::__hopper_native::bytemuck::Pod for Counter {}
unsafe impl hopper_runtime::Pod for Counter {}
impl crate::account::FixedLayout for Counter {
const SIZE: usize = 8;
}
fn make_account(data_len: usize, seed: u8) -> (std::vec::Vec<u8>, AccountView) {
let mut backing = std::vec![0u8; RuntimeAccount::SIZE + data_len];
let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 1,
is_writable: 1,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array([seed; 32]),
owner: NativeAddress::new_from_array([2; 32]),
lamports: 42,
data_len: data_len as u64,
});
}
let backend = unsafe { NativeAccountView::new_unchecked(raw) };
let view = unsafe { core::mem::transmute::<NativeAccountView, AccountView>(backend) };
(backing, view)
}
fn new_frame<'a>(program_id: &'a Address, accounts: &'a [AccountView]) -> Frame<'a> {
Frame::new(program_id, accounts, &[]).unwrap()
}
#[test]
fn frame_segment_mut_writes_through_ref_mut() {
let (_backing, account) = make_account(HEADER_LEN + 8, 1);
let program_id = NativeAddress::new_from_array([9; 32]);
let hopper_program_id =
unsafe { core::mem::transmute::<NativeAddress, Address>(program_id) };
let accounts = [account];
let mut frame = new_frame(&hopper_program_id, &accounts);
{
let mut counter: SegRefMut<'_, Counter> = frame.segment_mut::<Counter>(0, 0).unwrap();
counter.value = 7;
}
let bytes = frame.account(0).unwrap().data().unwrap();
let slice: &[u8] = &*bytes;
let raw_u64 =
unsafe { core::ptr::read_unaligned(slice.as_ptr().add(HEADER_LEN) as *const u64) };
assert_eq!(raw_u64, 7);
}
#[test]
fn frame_segment_ref_returns_live_guard() {
let (_backing, account) = make_account(HEADER_LEN + 8, 2);
{
let mut bytes = account.try_borrow_mut().unwrap();
let slot = unsafe { bytes.as_bytes_mut_ptr().add(HEADER_LEN) as *mut u64 };
unsafe { core::ptr::write_unaligned(slot, 99) };
}
let program_id = NativeAddress::new_from_array([9; 32]);
let hopper_program_id =
unsafe { core::mem::transmute::<NativeAddress, Address>(program_id) };
let accounts = [account];
let mut frame = new_frame(&hopper_program_id, &accounts);
let reader: SegRef<'_, Counter> = frame.segment_ref::<Counter>(0, 0).unwrap();
assert_eq!(reader.value, 99);
}
#[test]
fn frame_segment_lease_releases_on_drop() {
let (_backing, account) = make_account(HEADER_LEN + 8, 3);
let program_id = NativeAddress::new_from_array([9; 32]);
let hopper_program_id =
unsafe { core::mem::transmute::<NativeAddress, Address>(program_id) };
let accounts = [account];
let mut frame = new_frame(&hopper_program_id, &accounts);
{
let mut w: SegRefMut<'_, Counter> = frame.segment_mut::<Counter>(0, 0).unwrap();
w.value = 50;
}
assert_eq!(frame.segment_borrows().len(), 0);
{
let mut w: SegRefMut<'_, Counter> = frame.segment_mut::<Counter>(0, 0).unwrap();
assert_eq!(w.value, 50);
w.value = 77;
}
assert_eq!(frame.segment_borrows().len(), 0);
let r: SegRef<'_, Counter> = frame.segment_ref::<Counter>(0, 0).unwrap();
assert_eq!(r.value, 77);
}
}