use core::mem::MaybeUninit;
use crate::account_view::AccountView;
use crate::address::Address;
use crate::error::ProgramError;
#[cold]
#[inline(never)]
pub fn err_to_u64(e: ProgramError) -> u64 {
e.into()
}
#[inline(always)]
pub unsafe fn process_entrypoint<const MAX: usize>(
input: *mut u8,
process_instruction: for<'info> fn(
&'info Address,
&'info [AccountView<'info>],
&'info [u8],
) -> crate::ProgramResult,
) -> u64 {
const UNINIT: MaybeUninit<AccountView<'static>> = MaybeUninit::uninit();
let mut accounts = [UNINIT; 254];
let (program_id, count, instruction_data) =
unsafe { crate::raw_input::deserialize_accounts::<254>(input, &mut accounts) };
let effective_count = count.min(MAX);
let account_slice = unsafe {
core::slice::from_raw_parts(accounts.as_ptr() as *const AccountView<'_>, effective_count)
};
match process_instruction(program_id, account_slice, instruction_data) {
Ok(()) => crate::SUCCESS,
Err(error) => err_to_u64(error),
}
}
#[macro_export]
macro_rules! hopper_program_entrypoint {
( $process_instruction:expr ) => {
$crate::hopper_program_entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
};
( $process_instruction:expr, $maximum:expr ) => {
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
const UNINIT: core::mem::MaybeUninit<$crate::AccountView<'static>> =
core::mem::MaybeUninit::<$crate::AccountView<'static>>::uninit();
let mut accounts = [UNINIT; $maximum];
let (program_id, count, instruction_data) = unsafe {
$crate::raw_input::deserialize_accounts::<$maximum>(input, &mut accounts)
};
match $process_instruction(
program_id,
unsafe {
core::slice::from_raw_parts(
accounts.as_ptr() as *const $crate::AccountView<'_>,
count,
)
},
instruction_data,
) {
Ok(()) => $crate::SUCCESS,
Err(error) => $crate::entrypoint::err_to_u64(error),
}
}
};
}
#[macro_export]
macro_rules! program_entrypoint {
( $process_instruction:expr ) => {
$crate::hopper_program_entrypoint!($process_instruction);
};
( $process_instruction:expr, $maximum:expr ) => {
$crate::hopper_program_entrypoint!($process_instruction, $maximum);
};
}
#[cfg(feature = "simd-0321")]
#[macro_export]
macro_rules! hopper_fast_entrypoint {
( $process_instruction:expr ) => {
$crate::hopper_fast_entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
};
( $process_instruction:expr, $maximum:expr ) => {
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data: *const u8) -> u64 {
const UNINIT: core::mem::MaybeUninit<$crate::AccountView<'static>> =
core::mem::MaybeUninit::<$crate::AccountView<'static>>::uninit();
let mut accounts = [UNINIT; $maximum];
let (program_id, count, instruction_data) = if ix_data.is_null() {
unsafe { $crate::raw_input::deserialize_accounts::<$maximum>(input, &mut accounts) }
} else {
let ix_len =
unsafe { core::ptr::read_unaligned(ix_data.sub(8) as *const u64) as usize };
let instruction_data: &'static [u8] =
unsafe { core::slice::from_raw_parts(ix_data, ix_len) };
let program_id: &'static $crate::Address =
unsafe { &*(ix_data.add(ix_len) as *const $crate::Address) };
if $crate::raw_input::SIMD_0449_TABLE_ENABLED {
unsafe {
$crate::raw_input::deserialize_accounts_0449_into::<$maximum>(
input,
&mut accounts,
instruction_data,
program_id,
)
}
} else {
unsafe {
$crate::raw_input::deserialize_accounts_fast::<$maximum>(
input,
&mut accounts,
instruction_data,
program_id,
)
}
}
};
match $process_instruction(
program_id,
unsafe {
core::slice::from_raw_parts(
accounts.as_ptr() as *const $crate::AccountView<'_>,
count,
)
},
instruction_data,
) {
Ok(()) => $crate::SUCCESS,
Err(error) => $crate::entrypoint::err_to_u64(error),
}
}
};
}
#[cfg(not(feature = "simd-0321"))]
#[macro_export]
macro_rules! hopper_fast_entrypoint {
( $process_instruction:expr ) => {
$crate::hopper_program_entrypoint!($process_instruction);
};
( $process_instruction:expr, $maximum:expr ) => {
$crate::hopper_program_entrypoint!($process_instruction, $maximum);
};
}
#[macro_export]
macro_rules! fast_entrypoint {
( $process_instruction:expr ) => {
$crate::hopper_fast_entrypoint!($process_instruction);
};
( $process_instruction:expr, $maximum:expr ) => {
$crate::hopper_fast_entrypoint!($process_instruction, $maximum);
};
}
#[macro_export]
macro_rules! hopper_lazy_entrypoint {
( $process:expr ) => {
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
let mut ctx = unsafe { $crate::lazy::lazy_deserialize(input) };
match $process(&mut ctx) {
Ok(()) => $crate::SUCCESS,
Err(error) => $crate::entrypoint::err_to_u64(error),
}
}
};
}
#[macro_export]
macro_rules! lazy_entrypoint {
( $process:expr ) => {
$crate::hopper_lazy_entrypoint!($process);
};
}
#[macro_export]
macro_rules! no_allocator {
() => {
#[cfg(target_os = "solana")]
mod __hopper_allocator {
struct NoAlloc;
unsafe impl core::alloc::GlobalAlloc for NoAlloc {
unsafe fn alloc(&self, _layout: core::alloc::Layout) -> *mut u8 {
unsafe { $crate::syscalls::abort() }
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: core::alloc::Layout) {}
}
#[global_allocator]
static ALLOCATOR: NoAlloc = NoAlloc;
}
};
}
pub const HEAP_START_ADDRESS: usize = 0x3_0000_0000;
pub const HEAP_LENGTH: usize = 32 * 1024;
pub const HEAP_RUNTIME_RESERVED: usize = 20 * 1024;
pub struct BumpAllocator {
pub start: usize,
pub len: usize,
}
unsafe impl core::alloc::GlobalAlloc for BumpAllocator {
#[inline]
unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
let pos_ptr = self.start as *mut usize;
let mut pos = unsafe { *pos_ptr };
if pos == 0 {
pos = self.start + self.len;
}
pos = pos.saturating_sub(layout.size());
pos &= !(layout.align().wrapping_sub(1));
if pos < self.start + core::mem::size_of::<usize>() + HEAP_RUNTIME_RESERVED {
return core::ptr::null_mut();
}
unsafe { *pos_ptr = pos };
pos as *mut u8
}
#[inline]
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: core::alloc::Layout) {
}
}
#[macro_export]
macro_rules! default_allocator {
() => {
#[cfg(target_os = "solana")]
#[global_allocator]
static ALLOCATOR: $crate::BumpAllocator = $crate::BumpAllocator {
start: $crate::HEAP_START_ADDRESS,
len: $crate::HEAP_LENGTH,
};
};
}
#[macro_export]
macro_rules! nostd_panic_handler {
() => {
#[cfg(target_os = "solana")]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe { $crate::syscalls::abort() }
}
};
}
#[cfg(test)]
mod entrypoint_tail_tests {
extern crate std;
use std::vec;
use std::vec::Vec;
use super::*;
fn build_zero_account_frame(ix_data: &[u8], program_id: [u8; 32]) -> Vec<u64> {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&0u64.to_le_bytes()); 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());
}
words
}
fn ok_handler<'a>(
_: &'a Address,
_: &'a [AccountView<'a>],
_: &'a [u8],
) -> crate::ProgramResult {
Ok(())
}
fn custom_err_handler<'a>(
_: &'a Address,
_: &'a [AccountView<'a>],
_: &'a [u8],
) -> crate::ProgramResult {
Err(ProgramError::Custom(4242))
}
fn builtin_err_handler<'a>(
_: &'a Address,
_: &'a [AccountView<'a>],
_: &'a [u8],
) -> crate::ProgramResult {
Err(ProgramError::MissingRequiredSignature)
}
#[test]
fn ok_returns_bare_success_zero() {
let mut frame = build_zero_account_frame(&[1, 2, 3], [7u8; 32]);
let code = unsafe { process_entrypoint::<4>(frame.as_mut_ptr() as *mut u8, ok_handler) };
assert_eq!(code, 0);
assert_eq!(code, crate::SUCCESS);
}
#[test]
fn custom_err_maps_through_cold_outline_unchanged() {
let mut frame = build_zero_account_frame(&[], [0u8; 32]);
let code =
unsafe { process_entrypoint::<4>(frame.as_mut_ptr() as *mut u8, custom_err_handler) };
assert_eq!(code, u64::from(ProgramError::Custom(4242)));
assert_eq!(code, err_to_u64(ProgramError::Custom(4242)));
assert_eq!(code, 4242);
}
#[test]
fn builtin_err_maps_through_cold_outline_unchanged() {
let mut frame = build_zero_account_frame(&[], [0u8; 32]);
let code =
unsafe { process_entrypoint::<4>(frame.as_mut_ptr() as *mut u8, builtin_err_handler) };
assert_eq!(code, u64::from(ProgramError::MissingRequiredSignature));
assert_eq!(code, err_to_u64(ProgramError::MissingRequiredSignature));
}
#[test]
fn err_to_u64_matches_from_impl_for_all_variants() {
let cases = [
ProgramError::Custom(0),
ProgramError::Custom(1),
ProgramError::Custom(u32::MAX),
ProgramError::InvalidArgument,
ProgramError::MissingRequiredSignature,
ProgramError::AccountBorrowFailed,
ProgramError::ArithmeticOverflow,
ProgramError::IncorrectAuthority,
];
for e in cases {
assert_eq!(err_to_u64(e.clone()), u64::from(e));
}
}
}