|
@@ -11,7 +11,7 @@ use {
|
|
|
std::{
|
|
std::{
|
|
|
alloc::Layout,
|
|
alloc::Layout,
|
|
|
cell::RefCell,
|
|
cell::RefCell,
|
|
|
- mem::size_of,
|
|
|
|
|
|
|
+ mem::{size_of, MaybeUninit},
|
|
|
ptr::null_mut,
|
|
ptr::null_mut,
|
|
|
rc::Rc,
|
|
rc::Rc,
|
|
|
result::Result as ResultGeneric,
|
|
result::Result as ResultGeneric,
|
|
@@ -139,6 +139,59 @@ macro_rules! entrypoint {
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/// Declare the program entrypoint and set up global handlers.
|
|
|
|
|
+///
|
|
|
|
|
+/// This is similar to the `entrypoint!` macro, except that it does not perform
|
|
|
|
|
+/// any dynamic allocations, and instead writes the input accounts into a pre-
|
|
|
|
|
+/// allocated array.
|
|
|
|
|
+///
|
|
|
|
|
+/// This version reduces compute unit usage by 20-30 compute units per unique
|
|
|
|
|
+/// account in the instruction. It may become the default option in a future
|
|
|
|
|
+/// release.
|
|
|
|
|
+///
|
|
|
|
|
+/// For more information about how the program entrypoint behaves and what it
|
|
|
|
|
+/// does, please see the documentation for [`entrypoint!`].
|
|
|
|
|
+///
|
|
|
|
|
+/// NOTE: This entrypoint has a hard-coded limit of 64 input accounts.
|
|
|
|
|
+#[macro_export]
|
|
|
|
|
+macro_rules! entrypoint_no_alloc {
|
|
|
|
|
+ ($process_instruction:ident) => {
|
|
|
|
|
+ /// # Safety
|
|
|
|
|
+ #[no_mangle]
|
|
|
|
|
+ pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
|
|
|
|
|
+ use std::mem::MaybeUninit;
|
|
|
|
|
+ // Clippy complains about this because a `const` with interior
|
|
|
|
|
+ // mutability `RefCell` should use `static` instead to make it
|
|
|
|
|
+ // clear that it can change.
|
|
|
|
|
+ // In our case, however, we want to create an array of `AccountInfo`s,
|
|
|
|
|
+ // and the only way to do it is through a `const` expression, and
|
|
|
|
|
+ // we don't expect to mutate the internals of this `const` type.
|
|
|
|
|
+ #[allow(clippy::declare_interior_mutable_const)]
|
|
|
|
|
+ const UNINIT_ACCOUNT_INFO: MaybeUninit<AccountInfo> =
|
|
|
|
|
+ MaybeUninit::<AccountInfo>::uninit();
|
|
|
|
|
+ const MAX_ACCOUNT_INFOS: usize = 64;
|
|
|
|
|
+ let mut accounts = [UNINIT_ACCOUNT_INFO; MAX_ACCOUNT_INFOS];
|
|
|
|
|
+ let (program_id, num_accounts, instruction_data) =
|
|
|
|
|
+ unsafe { $crate::entrypoint::deserialize_into(input, &mut accounts) };
|
|
|
|
|
+ // Use `slice_assume_init_ref` once it's stabilized
|
|
|
|
|
+ let accounts = &*(&accounts[..num_accounts] as *const [MaybeUninit<AccountInfo<'_>>]
|
|
|
|
|
+ as *const [AccountInfo<'_>]);
|
|
|
|
|
+
|
|
|
|
|
+ #[inline(never)]
|
|
|
|
|
+ fn call_program(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> u64 {
|
|
|
|
|
+ match $process_instruction(program_id, accounts, data) {
|
|
|
|
|
+ Ok(()) => $crate::entrypoint::SUCCESS,
|
|
|
|
|
+ Err(error) => error.into(),
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ call_program(&program_id, accounts, &instruction_data)
|
|
|
|
|
+ }
|
|
|
|
|
+ $crate::custom_heap_default!();
|
|
|
|
|
+ $crate::custom_panic_default!();
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/// Define the default global allocator.
|
|
/// Define the default global allocator.
|
|
|
///
|
|
///
|
|
|
/// The default global allocator is enabled only if the calling crate has not
|
|
/// The default global allocator is enabled only if the calling crate has not
|
|
@@ -265,6 +318,86 @@ pub const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10;
|
|
|
/// `assert_eq(std::mem::align_of::<u128>(), 8)` is true for BPF but not for some host machines
|
|
/// `assert_eq(std::mem::align_of::<u128>(), 8)` is true for BPF but not for some host machines
|
|
|
pub const BPF_ALIGN_OF_U128: usize = 8;
|
|
pub const BPF_ALIGN_OF_U128: usize = 8;
|
|
|
|
|
|
|
|
|
|
+#[allow(clippy::arithmetic_side_effects)]
|
|
|
|
|
+#[inline(always)] // this reduces CU usage
|
|
|
|
|
+unsafe fn deserialize_instruction_data<'a>(input: *mut u8, mut offset: usize) -> (&'a [u8], usize) {
|
|
|
|
|
+ #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
+ let instruction_data_len = *(input.add(offset) as *const u64) as usize;
|
|
|
|
|
+ offset += size_of::<u64>();
|
|
|
|
|
+
|
|
|
|
|
+ let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
|
|
|
|
|
+ offset += instruction_data_len;
|
|
|
|
|
+
|
|
|
|
|
+ (instruction_data, offset)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#[allow(clippy::arithmetic_side_effects)]
|
|
|
|
|
+#[inline(always)] // this reduces CU usage by half!
|
|
|
|
|
+unsafe fn deserialize_account_info<'a>(
|
|
|
|
|
+ input: *mut u8,
|
|
|
|
|
+ mut offset: usize,
|
|
|
|
|
+) -> (AccountInfo<'a>, usize) {
|
|
|
|
|
+ #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
+ let is_signer = *(input.add(offset) as *const u8) != 0;
|
|
|
|
|
+ offset += size_of::<u8>();
|
|
|
|
|
+
|
|
|
|
|
+ #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
+ let is_writable = *(input.add(offset) as *const u8) != 0;
|
|
|
|
|
+ offset += size_of::<u8>();
|
|
|
|
|
+
|
|
|
|
|
+ #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
+ let executable = *(input.add(offset) as *const u8) != 0;
|
|
|
|
|
+ offset += size_of::<u8>();
|
|
|
|
|
+
|
|
|
|
|
+ // The original data length is stored here because these 4 bytes were
|
|
|
|
|
+ // originally only used for padding and served as a good location to
|
|
|
|
|
+ // track the original size of the account data in a compatible way.
|
|
|
|
|
+ let original_data_len_offset = offset;
|
|
|
|
|
+ offset += size_of::<u32>();
|
|
|
|
|
+
|
|
|
|
|
+ let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
|
|
|
|
+ offset += size_of::<Pubkey>();
|
|
|
|
|
+
|
|
|
|
|
+ let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
|
|
|
|
+ offset += size_of::<Pubkey>();
|
|
|
|
|
+
|
|
|
|
|
+ #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
+ let lamports = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64)));
|
|
|
|
|
+ offset += size_of::<u64>();
|
|
|
|
|
+
|
|
|
|
|
+ #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
+ let data_len = *(input.add(offset) as *const u64) as usize;
|
|
|
|
|
+ offset += size_of::<u64>();
|
|
|
|
|
+
|
|
|
|
|
+ // Store the original data length for detecting invalid reallocations and
|
|
|
|
|
+ // requires that MAX_PERMITTED_DATA_LENGTH fits in a u32
|
|
|
|
|
+ *(input.add(original_data_len_offset) as *mut u32) = data_len as u32;
|
|
|
|
|
+
|
|
|
|
|
+ let data = Rc::new(RefCell::new({
|
|
|
|
|
+ from_raw_parts_mut(input.add(offset), data_len)
|
|
|
|
|
+ }));
|
|
|
|
|
+ offset += data_len + MAX_PERMITTED_DATA_INCREASE;
|
|
|
|
|
+ offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128); // padding
|
|
|
|
|
+
|
|
|
|
|
+ #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
+ let rent_epoch = *(input.add(offset) as *const u64);
|
|
|
|
|
+ offset += size_of::<u64>();
|
|
|
|
|
+
|
|
|
|
|
+ (
|
|
|
|
|
+ AccountInfo {
|
|
|
|
|
+ key,
|
|
|
|
|
+ is_signer,
|
|
|
|
|
+ is_writable,
|
|
|
|
|
+ lamports,
|
|
|
|
|
+ data,
|
|
|
|
|
+ owner,
|
|
|
|
|
+ executable,
|
|
|
|
|
+ rent_epoch,
|
|
|
|
|
+ },
|
|
|
|
|
+ offset,
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/// Deserialize the input arguments
|
|
/// Deserialize the input arguments
|
|
|
///
|
|
///
|
|
|
/// The integer arithmetic in this method is safe when called on a buffer that was
|
|
/// The integer arithmetic in this method is safe when called on a buffer that was
|
|
@@ -273,7 +406,6 @@ pub const BPF_ALIGN_OF_U128: usize = 8;
|
|
|
///
|
|
///
|
|
|
/// # Safety
|
|
/// # Safety
|
|
|
#[allow(clippy::arithmetic_side_effects)]
|
|
#[allow(clippy::arithmetic_side_effects)]
|
|
|
-#[allow(clippy::type_complexity)]
|
|
|
|
|
pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
|
|
pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
|
|
|
let mut offset: usize = 0;
|
|
let mut offset: usize = 0;
|
|
|
|
|
|
|
@@ -290,62 +422,9 @@ pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a
|
|
|
let dup_info = *(input.add(offset) as *const u8);
|
|
let dup_info = *(input.add(offset) as *const u8);
|
|
|
offset += size_of::<u8>();
|
|
offset += size_of::<u8>();
|
|
|
if dup_info == NON_DUP_MARKER {
|
|
if dup_info == NON_DUP_MARKER {
|
|
|
- #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
- let is_signer = *(input.add(offset) as *const u8) != 0;
|
|
|
|
|
- offset += size_of::<u8>();
|
|
|
|
|
-
|
|
|
|
|
- #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
- let is_writable = *(input.add(offset) as *const u8) != 0;
|
|
|
|
|
- offset += size_of::<u8>();
|
|
|
|
|
-
|
|
|
|
|
- #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
- let executable = *(input.add(offset) as *const u8) != 0;
|
|
|
|
|
- offset += size_of::<u8>();
|
|
|
|
|
-
|
|
|
|
|
- // The original data length is stored here because these 4 bytes were
|
|
|
|
|
- // originally only used for padding and served as a good location to
|
|
|
|
|
- // track the original size of the account data in a compatible way.
|
|
|
|
|
- let original_data_len_offset = offset;
|
|
|
|
|
- offset += size_of::<u32>();
|
|
|
|
|
-
|
|
|
|
|
- let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
|
|
|
|
- offset += size_of::<Pubkey>();
|
|
|
|
|
-
|
|
|
|
|
- let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
|
|
|
|
- offset += size_of::<Pubkey>();
|
|
|
|
|
-
|
|
|
|
|
- #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
- let lamports = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64)));
|
|
|
|
|
- offset += size_of::<u64>();
|
|
|
|
|
-
|
|
|
|
|
- #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
- let data_len = *(input.add(offset) as *const u64) as usize;
|
|
|
|
|
- offset += size_of::<u64>();
|
|
|
|
|
-
|
|
|
|
|
- // Store the original data length for detecting invalid reallocations and
|
|
|
|
|
- // requires that MAX_PERMITTED_DATA_LENGTH fits in a u32
|
|
|
|
|
- *(input.add(original_data_len_offset) as *mut u32) = data_len as u32;
|
|
|
|
|
-
|
|
|
|
|
- let data = Rc::new(RefCell::new({
|
|
|
|
|
- from_raw_parts_mut(input.add(offset), data_len)
|
|
|
|
|
- }));
|
|
|
|
|
- offset += data_len + MAX_PERMITTED_DATA_INCREASE;
|
|
|
|
|
- offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128); // padding
|
|
|
|
|
-
|
|
|
|
|
- #[allow(clippy::cast_ptr_alignment)]
|
|
|
|
|
- let rent_epoch = *(input.add(offset) as *const u64);
|
|
|
|
|
- offset += size_of::<u64>();
|
|
|
|
|
-
|
|
|
|
|
- accounts.push(AccountInfo {
|
|
|
|
|
- key,
|
|
|
|
|
- is_signer,
|
|
|
|
|
- is_writable,
|
|
|
|
|
- lamports,
|
|
|
|
|
- data,
|
|
|
|
|
- owner,
|
|
|
|
|
- executable,
|
|
|
|
|
- rent_epoch,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ let (account_info, new_offset) = deserialize_account_info(input, offset);
|
|
|
|
|
+ offset = new_offset;
|
|
|
|
|
+ accounts.push(account_info);
|
|
|
} else {
|
|
} else {
|
|
|
offset += 7; // padding
|
|
offset += 7; // padding
|
|
|
|
|
|
|
@@ -356,18 +435,77 @@ pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a
|
|
|
|
|
|
|
|
// Instruction data
|
|
// Instruction data
|
|
|
|
|
|
|
|
|
|
+ let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
|
|
|
|
|
+ offset = new_offset;
|
|
|
|
|
+
|
|
|
|
|
+ // Program Id
|
|
|
|
|
+
|
|
|
|
|
+ let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
|
|
|
|
+
|
|
|
|
|
+ (program_id, accounts, instruction_data)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// Deserialize the input arguments
|
|
|
|
|
+///
|
|
|
|
|
+/// Differs from `deserialize` by writing the account infos into an uninitialized
|
|
|
|
|
+/// slice, which provides better performance, roughly 30 CUs per unique account
|
|
|
|
|
+/// provided to the instruction.
|
|
|
|
|
+///
|
|
|
|
|
+/// Panics if the input slice is not large enough.
|
|
|
|
|
+///
|
|
|
|
|
+/// The integer arithmetic in this method is safe when called on a buffer that was
|
|
|
|
|
+/// serialized by runtime. Use with buffers serialized otherwise is unsupported and
|
|
|
|
|
+/// done at one's own risk.
|
|
|
|
|
+///
|
|
|
|
|
+/// # Safety
|
|
|
|
|
+#[allow(clippy::arithmetic_side_effects)]
|
|
|
|
|
+pub unsafe fn deserialize_into<'a>(
|
|
|
|
|
+ input: *mut u8,
|
|
|
|
|
+ accounts: &mut [MaybeUninit<AccountInfo<'a>>],
|
|
|
|
|
+) -> (&'a Pubkey, usize, &'a [u8]) {
|
|
|
|
|
+ let mut offset: usize = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // Number of accounts present
|
|
|
|
|
+
|
|
|
#[allow(clippy::cast_ptr_alignment)]
|
|
#[allow(clippy::cast_ptr_alignment)]
|
|
|
- let instruction_data_len = *(input.add(offset) as *const u64) as usize;
|
|
|
|
|
|
|
+ let num_accounts = *(input.add(offset) as *const u64) as usize;
|
|
|
offset += size_of::<u64>();
|
|
offset += size_of::<u64>();
|
|
|
|
|
|
|
|
- let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
|
|
|
|
|
- offset += instruction_data_len;
|
|
|
|
|
|
|
+ if num_accounts > accounts.len() {
|
|
|
|
|
+ panic!(
|
|
|
|
|
+ "{} accounts provided, but only {} are supported",
|
|
|
|
|
+ num_accounts,
|
|
|
|
|
+ accounts.len()
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Account Infos
|
|
|
|
|
+
|
|
|
|
|
+ for i in 0..num_accounts {
|
|
|
|
|
+ let dup_info = *(input.add(offset) as *const u8);
|
|
|
|
|
+ offset += size_of::<u8>();
|
|
|
|
|
+ if dup_info == NON_DUP_MARKER {
|
|
|
|
|
+ let (account_info, new_offset) = deserialize_account_info(input, offset);
|
|
|
|
|
+ offset = new_offset;
|
|
|
|
|
+ accounts[i].write(account_info);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ offset += 7; // padding
|
|
|
|
|
+
|
|
|
|
|
+ // Duplicate account, clone the original
|
|
|
|
|
+ accounts[i].write(accounts[dup_info as usize].assume_init_ref().clone());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Instruction data
|
|
|
|
|
+
|
|
|
|
|
+ let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
|
|
|
|
|
+ offset = new_offset;
|
|
|
|
|
|
|
|
// Program Id
|
|
// Program Id
|
|
|
|
|
|
|
|
let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
|
let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
|
|
|
|
|
|
|
|
- (program_id, accounts, instruction_data)
|
|
|
|
|
|
|
+ (program_id, num_accounts, instruction_data)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
#[cfg(test)]
|