pub mod lazy;
#[cfg(feature = "alloc")]
pub use alloc::BumpAllocator;
pub use lazy::{InstructionContext, MaybeAccount};
use {
crate::{
account::{AccountView, RuntimeAccount, MAX_PERMITTED_DATA_INCREASE},
Address, ProgramResult, BPF_ALIGN_OF_U128, MAX_TX_ACCOUNTS, SUCCESS,
},
core::{
alloc::{GlobalAlloc, Layout},
cmp::min,
mem::{size_of, MaybeUninit},
ptr::with_exposed_provenance_mut,
slice::{from_raw_parts, from_raw_parts_mut},
},
};
pub const HEAP_START_ADDRESS: u64 = 0x300000000;
#[deprecated(since = "0.10.0", note = "Use `MAX_HEAP_LENGTH` instead")]
pub const HEAP_LENGTH: usize = 32 * 1024;
pub const MAX_HEAP_LENGTH: u32 = 256 * 1024;
pub const NON_DUP_MARKER: u8 = u8::MAX;
const STATIC_ACCOUNT_DATA: usize = size_of::<RuntimeAccount>() + MAX_PERMITTED_DATA_INCREASE;
#[cfg(feature = "alloc")]
#[macro_export]
macro_rules! entrypoint {
( $process_instruction:expr ) => {
$crate::entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
};
( $process_instruction:expr, $maximum:expr ) => {
$crate::program_entrypoint!($process_instruction, $maximum);
$crate::default_allocator!();
$crate::default_panic_handler!();
};
}
#[macro_export]
macro_rules! program_entrypoint {
( $process_instruction:expr ) => {
$crate::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 {
$crate::entrypoint::process_entrypoint::<$maximum>(input, $process_instruction)
}
};
}
#[inline(always)]
pub unsafe fn process_entrypoint<const MAX_ACCOUNTS: usize>(
input: *mut u8,
process_instruction: fn(&Address, &mut [AccountView], &[u8]) -> ProgramResult,
) -> u64 {
const UNINIT: MaybeUninit<AccountView> = MaybeUninit::<AccountView>::uninit();
let mut accounts = [UNINIT; MAX_ACCOUNTS];
let (program_id, count, instruction_data) =
unsafe { deserialize::<MAX_ACCOUNTS>(input, &mut accounts) };
match process_instruction(
program_id,
unsafe { from_raw_parts_mut(accounts.as_mut_ptr() as _, count) },
instruction_data,
) {
Ok(()) => SUCCESS,
Err(error) => error.into(),
}
}
macro_rules! align_pointer {
($ptr:ident) => {
with_exposed_provenance_mut(
($ptr.expose_provenance() + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1),
)
};
}
macro_rules! advance_input_with_account {
($input:ident, $account:expr) => {{
$input = $input.add(STATIC_ACCOUNT_DATA);
$input = $input.add((*$account).data_len as usize);
$input = align_pointer!($input);
}};
}
macro_rules! process_n_accounts {
( () => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {};
( ( _ $($rest:tt)* ) => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
process_n_accounts!(@process_account => ($input, $accounts, $accounts_slice));
process_n_accounts!(($($rest)*) => ($input, $accounts, $accounts_slice));
};
( @process_account => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
$accounts = $accounts.add(1);
let account: *mut RuntimeAccount = $input as *mut RuntimeAccount;
$input = $input.add(size_of::<u64>());
if (*account).borrow_state != NON_DUP_MARKER {
clone_account_view($accounts, $accounts_slice, (*account).borrow_state);
} else {
#[cfg(feature = "account-resize")]
{
(*account).padding = u32::to_le_bytes((*account).data_len as u32);
}
$accounts.write(AccountView::new_unchecked(account));
advance_input_with_account!($input, account);
}
};
}
macro_rules! process_accounts {
( 1 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
process_n_accounts!( (_) => ( $input, $accounts, $accounts_slice ));
};
( 2 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
process_n_accounts!( (_ _) => ( $input, $accounts, $accounts_slice ));
};
( 3 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
process_n_accounts!( (_ _ _) => ( $input, $accounts, $accounts_slice ));
};
( 4 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
process_n_accounts!( (_ _ _ _) => ( $input, $accounts, $accounts_slice ));
};
( 5 => ( $input:ident, $accounts:ident, $accounts_slice:ident ) ) => {
process_n_accounts!( (_ _ _ _ _) => ( $input, $accounts, $accounts_slice ));
};
}
#[allow(clippy::clone_on_copy)]
#[cold]
#[inline(always)]
unsafe fn clone_account_view(
accounts: *mut AccountView,
accounts_slice: *const AccountView,
index: u8,
) {
accounts.write((*accounts_slice.add(index as usize)).clone());
}
#[inline(always)]
pub unsafe fn deserialize<const MAX_ACCOUNTS: usize>(
mut input: *mut u8,
accounts: &mut [MaybeUninit<AccountView>; MAX_ACCOUNTS],
) -> (&'static Address, usize, &'static [u8]) {
const {
assert!(MAX_ACCOUNTS > 0, "MAX_ACCOUNTS must be at least 1");
assert!(
MAX_ACCOUNTS <= MAX_TX_ACCOUNTS,
"MAX_ACCOUNTS must be less than or equal to MAX_TX_ACCOUNTS"
);
}
let mut processed = *(input as *const u64) as usize;
input = input.add(size_of::<u64>());
if processed > 0 {
let mut accounts = accounts.as_mut_ptr() as *mut AccountView;
let accounts_slice = accounts;
let account: *mut RuntimeAccount = input as *mut RuntimeAccount;
#[cfg(feature = "account-resize")]
{
(*account).padding = u32::to_le_bytes((*account).data_len as u32);
}
accounts.write(AccountView::new_unchecked(account));
input = input.add(size_of::<u64>());
advance_input_with_account!(input, account);
if processed > 1 {
let mut to_process_plus_one = if MAX_ACCOUNTS < MAX_TX_ACCOUNTS {
min(processed, MAX_ACCOUNTS)
} else {
processed
};
let mut to_skip = processed - to_process_plus_one;
processed = to_process_plus_one;
if to_process_plus_one == 2 {
process_accounts!(1 => (input, accounts, accounts_slice));
} else {
while to_process_plus_one > 5 {
process_accounts!(5 => (input, accounts, accounts_slice));
to_process_plus_one -= 5;
}
match to_process_plus_one {
5 => {
process_accounts!(4 => (input, accounts, accounts_slice));
}
4 => {
process_accounts!(3 => (input, accounts, accounts_slice));
}
3 => {
process_accounts!(2 => (input, accounts, accounts_slice));
}
2 => {
process_accounts!(1 => (input, accounts, accounts_slice));
}
1 => (),
_ => {
unsafe { core::hint::unreachable_unchecked() }
}
}
}
if MAX_ACCOUNTS < MAX_TX_ACCOUNTS {
while to_skip > 0 {
to_skip -= 1;
let account: *mut RuntimeAccount = input as *mut RuntimeAccount;
input = input.add(size_of::<u64>());
if (*account).borrow_state == NON_DUP_MARKER {
advance_input_with_account!(input, account);
}
}
}
}
}
let instruction_data_len = *(input as *const u64) as usize;
input = input.add(size_of::<u64>());
let instruction_data = { from_raw_parts(input, instruction_data_len) };
let input = input.add(instruction_data_len);
let program_id: &Address = &*(input as *const Address);
(program_id, processed, instruction_data)
}
#[macro_export]
macro_rules! default_panic_handler {
() => {
#[cfg(any(target_os = "solana", target_arch = "bpf"))]
#[no_mangle]
fn custom_panic(info: &core::panic::PanicInfo<'_>) {
if let Some(location) = info.location() {
let location = location.file();
unsafe { $crate::syscalls::sol_log_(location.as_ptr(), location.len() as u64) };
}
const PANICKED: &str = "** PANICKED **";
unsafe { $crate::syscalls::sol_log_(PANICKED.as_ptr(), PANICKED.len() as u64) };
}
};
}
#[macro_export]
macro_rules! nostd_panic_handler {
() => {
#[cfg(any(target_os = "solana", target_arch = "bpf"))]
#[panic_handler]
fn handler(info: &core::panic::PanicInfo<'_>) -> ! {
if let Some(location) = info.location() {
unsafe {
$crate::syscalls::sol_panic_(
location.file().as_ptr(),
location.file().len() as u64,
location.line() as u64,
location.column() as u64,
)
}
} else {
const PANICKED: &str = "** PANICKED **";
unsafe {
$crate::syscalls::sol_log_(PANICKED.as_ptr(), PANICKED.len() as u64);
$crate::syscalls::abort();
}
}
}
#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
mod __private_panic_handler {
extern crate std as __std;
}
};
}
#[cfg(feature = "alloc")]
#[macro_export]
macro_rules! default_allocator {
() => {
#[cfg(any(target_os = "solana", target_arch = "bpf"))]
#[global_allocator]
static A: $crate::entrypoint::BumpAllocator = unsafe {
$crate::entrypoint::BumpAllocator::new_unchecked(
$crate::entrypoint::HEAP_START_ADDRESS as usize,
$crate::entrypoint::MAX_HEAP_LENGTH as usize,
)
};
#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
mod __private_alloc {
extern crate std as __std;
}
};
}
#[macro_export]
macro_rules! no_allocator {
() => {
#[cfg(any(target_os = "solana", target_arch = "bpf"))]
#[global_allocator]
static A: $crate::entrypoint::NoAllocator = $crate::entrypoint::NoAllocator;
#[inline(always)]
pub unsafe fn allocate_unchecked<T: Sized>(offset: usize) -> &'static mut T {
unsafe { &mut *(calculate_offset::<T>(offset) as *mut T) }
}
#[inline(always)]
const fn calculate_offset<T: Sized>(offset: usize) -> usize {
let start = $crate::entrypoint::HEAP_START_ADDRESS as usize + offset;
let end = start + core::mem::size_of::<T>();
assert!(
end <= $crate::entrypoint::HEAP_START_ADDRESS as usize
+ $crate::entrypoint::MAX_HEAP_LENGTH as usize,
"allocation exceeds heap size"
);
assert!(
start % core::mem::align_of::<T>() == 0,
"offset is not aligned"
);
start
}
#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
mod __private_alloc {
extern crate std as __std;
}
};
}
#[cfg_attr(feature = "copy", derive(Copy))]
#[derive(Clone, Debug)]
pub struct NoAllocator;
unsafe impl GlobalAlloc for NoAllocator {
#[inline]
unsafe fn alloc(&self, _: Layout) -> *mut u8 {
panic!("** NoAllocator::alloc() does not allocate memory **");
}
#[inline]
unsafe fn dealloc(&self, _: *mut u8, _: Layout) {
}
}
#[cfg(feature = "alloc")]
mod alloc {
use {
crate::{entrypoint::MAX_HEAP_LENGTH, hint::unlikely},
core::{
alloc::{GlobalAlloc, Layout},
mem::size_of,
ptr::null_mut,
},
};
#[cfg_attr(feature = "copy", derive(Copy))]
#[derive(Clone, Debug)]
pub struct BumpAllocator {
start: usize,
end: usize,
}
impl BumpAllocator {
pub const unsafe fn new_unchecked(start: usize, len: usize) -> Self {
Self {
start,
end: start + len,
}
}
}
#[allow(clippy::arithmetic_side_effects)]
unsafe impl GlobalAlloc for BumpAllocator {
#[inline]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let pos_ptr = self.start as *mut usize;
let mut pos = *pos_ptr;
if unlikely(pos == 0) {
pos = self.start + size_of::<usize>();
}
let allocation = (pos + layout.align() - 1) & !(layout.align() - 1);
if unlikely(layout.size() > MAX_HEAP_LENGTH as usize)
|| unlikely(self.end < allocation + layout.size())
{
return null_mut();
}
*pos_ptr = allocation + layout.size();
allocation as *mut u8
}
#[inline]
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
self.alloc(layout)
}
#[inline]
unsafe fn dealloc(&self, _: *mut u8, _: Layout) {}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
::alloc::{
alloc::{alloc, dealloc, handle_alloc_error},
vec,
},
core::{
alloc::Layout,
ptr::{copy_nonoverlapping, null_mut},
},
};
const MOCK_PROGRAM_ID: Address = Address::new_from_array([5u8; 32]);
const UNINIT: MaybeUninit<AccountView> = MaybeUninit::<AccountView>::uninit();
struct AlignedMemory {
ptr: *mut u8,
layout: Layout,
}
impl AlignedMemory {
pub fn new(len: usize) -> Self {
let layout = Layout::from_size_align(len, BPF_ALIGN_OF_U128).unwrap();
unsafe {
let ptr = alloc(layout);
if ptr.is_null() {
handle_alloc_error(layout);
}
AlignedMemory { ptr, layout }
}
}
pub unsafe fn write(&mut self, data: &[u8], offset: usize) {
copy_nonoverlapping(data.as_ptr(), self.ptr.add(offset), data.len());
}
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.ptr
}
}
impl Drop for AlignedMemory {
fn drop(&mut self) {
unsafe {
dealloc(self.ptr, self.layout);
}
}
}
unsafe fn create_input(accounts: usize, instruction_data: &[u8]) -> AlignedMemory {
let mut input = AlignedMemory::new(1_000_000_000);
input.write(&(accounts as u64).to_le_bytes(), 0);
let mut offset = size_of::<u64>();
for i in 0..accounts {
let mut account = [0u8; STATIC_ACCOUNT_DATA + size_of::<u64>()];
account[0] = NON_DUP_MARKER;
account[80..88].copy_from_slice(&i.to_le_bytes());
input.write(&account, offset);
offset += account.len();
let padding_for_data = (i + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1);
input.write(&vec![0u8; padding_for_data], offset);
offset += padding_for_data;
}
input.write(&instruction_data.len().to_le_bytes(), offset);
offset += size_of::<u64>();
input.write(instruction_data, offset);
offset += instruction_data.len();
input.write(MOCK_PROGRAM_ID.as_array(), offset);
input
}
unsafe fn create_input_with_duplicates(
accounts: usize,
instruction_data: &[u8],
duplicated: usize,
) -> AlignedMemory {
let mut input = AlignedMemory::new(1_000_000_000);
input.write(&(accounts as u64).to_le_bytes(), 0);
let mut offset = size_of::<u64>();
if accounts > 0 {
assert!(
duplicated < accounts,
"Duplicated accounts must be less than total accounts"
);
let unique = accounts - duplicated;
for i in 0..unique {
let mut account = [0u8; STATIC_ACCOUNT_DATA + size_of::<u64>()];
account[0] = NON_DUP_MARKER;
account[80..88].copy_from_slice(&i.to_le_bytes());
input.write(&account, offset);
offset += account.len();
let padding_for_data = (i + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1);
input.write(&vec![0u8; padding_for_data], offset);
offset += padding_for_data;
}
for _ in unique..accounts {
input.write(&[(unique - 1) as u8, 0, 0, 0, 0, 0, 0, 0], offset);
offset += size_of::<u64>();
}
}
input.write(&instruction_data.len().to_le_bytes(), offset);
offset += size_of::<u64>();
input.write(instruction_data, offset);
offset += instruction_data.len();
input.write(MOCK_PROGRAM_ID.as_array(), offset);
input
}
fn assert_accounts(accounts: &[MaybeUninit<AccountView>]) {
for (i, account) in accounts.iter().enumerate() {
let account_view = unsafe { account.assume_init_ref() };
assert_eq!(account_view.data_len(), i);
}
}
fn assert_duplicated_accounts(accounts: &mut [MaybeUninit<AccountView>], duplicated: usize) {
assert!(accounts.len() > duplicated);
let unique = accounts.len() - duplicated;
for (i, account) in accounts[..unique].iter().enumerate() {
let account_view = unsafe { account.assume_init_ref() };
assert_eq!(account_view.data_len(), i);
}
let (unique_accounts, duplicated_accounts) = accounts.split_at_mut(unique);
let last_unique = unsafe { unique_accounts.last_mut().unwrap().assume_init_mut() };
assert!(last_unique.try_borrow_mut().is_ok());
for account in duplicated_accounts.iter_mut() {
let account_view = unsafe { account.assume_init_mut() };
assert_eq!(account_view, last_unique);
assert_eq!(account_view.data_len(), last_unique.data_len());
let borrowed = account_view.try_borrow_mut().unwrap();
assert!(last_unique.try_borrow_mut().is_err());
drop(borrowed);
}
assert!(last_unique.try_borrow_mut().is_ok());
}
#[test]
fn test_deserialize() {
let ix_data = [3u8; 100];
let mut input = unsafe { create_input(0, &ix_data) };
let mut accounts = [UNINIT; 1];
let (program_id, count, parsed_ix_data) =
unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
assert_eq!(count, 0);
assert!(program_id == &MOCK_PROGRAM_ID);
assert_eq!(&ix_data, parsed_ix_data);
let mut input = unsafe { create_input(3, &ix_data) };
let mut accounts = [UNINIT; 1];
let (program_id, count, parsed_ix_data) =
unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
assert_eq!(count, 1);
assert!(program_id == &MOCK_PROGRAM_ID);
assert_eq!(&ix_data, parsed_ix_data);
assert_accounts(&accounts[..count]);
let mut input = unsafe { create_input(MAX_TX_ACCOUNTS, &ix_data) };
let mut accounts = [UNINIT; 64];
let (program_id, count, parsed_ix_data) =
unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
assert_eq!(count, 64);
assert!(program_id == &MOCK_PROGRAM_ID);
assert_eq!(&ix_data, parsed_ix_data);
assert_accounts(&accounts);
}
#[test]
fn test_deserialize_duplicated() {
let ix_data = [3u8; 100];
let mut input = unsafe { create_input_with_duplicates(0, &ix_data, 0) };
let mut accounts = [UNINIT; 1];
let (program_id, count, parsed_ix_data) =
unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
assert_eq!(count, 0);
assert!(program_id == &MOCK_PROGRAM_ID);
assert_eq!(&ix_data, parsed_ix_data);
let mut input = unsafe { create_input_with_duplicates(3, &ix_data, 2) };
let mut accounts = [UNINIT; 2];
let (program_id, count, parsed_ix_data) =
unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
assert_eq!(count, 2);
assert!(program_id == &MOCK_PROGRAM_ID);
assert_eq!(&ix_data, parsed_ix_data);
assert_duplicated_accounts(&mut accounts[..count], 1);
let mut input = unsafe {
create_input_with_duplicates(MAX_TX_ACCOUNTS, &ix_data, MAX_TX_ACCOUNTS - 32)
};
let mut accounts = [UNINIT; 64];
let (program_id, count, parsed_ix_data) =
unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
assert_eq!(count, 64);
assert!(program_id == &MOCK_PROGRAM_ID);
assert_eq!(&ix_data, parsed_ix_data);
assert_duplicated_accounts(&mut accounts, 32);
}
#[test]
fn test_bump_allocator() {
{
let mut heap = AlignedMemory::new(128);
unsafe { heap.write(&[0; 128], 0) };
let allocator = unsafe {
BumpAllocator::new_unchecked(heap.as_mut_ptr() as usize, heap.layout.size())
};
for i in 0..128 - size_of::<*mut u8>() {
let ptr = unsafe {
allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap())
};
assert_eq!(
ptr as usize,
heap.as_mut_ptr() as usize + size_of::<*mut u8>() + i
);
}
assert_eq!(null_mut(), unsafe {
allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap())
});
}
{
let mut heap = AlignedMemory::new(128);
unsafe { heap.write(&[0; 128], 0) };
let allocator = unsafe {
BumpAllocator::new_unchecked(heap.as_mut_ptr() as usize, heap.layout.size())
};
let ptr =
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u8>()).unwrap()) };
assert_eq!(0, ptr.align_offset(size_of::<u8>()));
let ptr =
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u16>()).unwrap()) };
assert_eq!(0, ptr.align_offset(size_of::<u16>()));
let ptr =
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u32>()).unwrap()) };
assert_eq!(0, ptr.align_offset(size_of::<u32>()));
let ptr =
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u64>()).unwrap()) };
assert_eq!(0, ptr.align_offset(size_of::<u64>()));
let ptr =
unsafe { allocator.alloc(Layout::from_size_align(1, size_of::<u128>()).unwrap()) };
assert_eq!(0, ptr.align_offset(size_of::<u128>()));
let ptr = unsafe { allocator.alloc(Layout::from_size_align(1, 64).unwrap()) };
assert_eq!(0, ptr.align_offset(64));
}
{
let mut heap = AlignedMemory::new(128);
unsafe { heap.write(&[0; 128], 0) };
let allocator = unsafe {
BumpAllocator::new_unchecked(heap.as_mut_ptr() as usize, heap.layout.size())
};
let ptr = unsafe {
allocator.alloc(
Layout::from_size_align(
heap.layout.size() - size_of::<usize>(),
size_of::<u8>(),
)
.unwrap(),
)
};
assert_ne!(ptr, null_mut());
assert_eq!(0, ptr.align_offset(size_of::<u64>()));
}
}
}