From cfa295a35f014fee51e97134b4bfab8fd013be35 Mon Sep 17 00:00:00 2001 From: febo Date: Sun, 20 Oct 2024 19:28:29 +0100 Subject: [PATCH 01/12] Add lazy_entrypoint --- sdk/pinocchio/src/lazy_entrypoint.rs | 219 +++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 sdk/pinocchio/src/lazy_entrypoint.rs diff --git a/sdk/pinocchio/src/lazy_entrypoint.rs b/sdk/pinocchio/src/lazy_entrypoint.rs new file mode 100644 index 0000000..176e3c4 --- /dev/null +++ b/sdk/pinocchio/src/lazy_entrypoint.rs @@ -0,0 +1,219 @@ +use crate::{ + account_info::{Account, AccountInfo, MAX_PERMITTED_DATA_INCREASE}, + program_error::ProgramError, + pubkey::Pubkey, + BPF_ALIGN_OF_U128, NON_DUP_MARKER, +}; + +/// Declare the program entrypoint. +/// +/// This entrypoint is defined as *lazy* because it does not read the accounts upfront +/// nor set up global handlers. Instead, it provides an [`InstructionContext`] to the +/// access input information on demand. This is useful when the program needs more control +/// over the compute units it uses. The trade-off is that the program is responsible for +/// managing potential duplicated accounts and set up a `global allocator` +/// and `panic handler`. +/// +/// This macro emits the boilerplate necessary to begin program execution, calling a +/// provided function to process the program instruction supplied by the runtime, and reporting +/// its result to the runtime. +/// +/// The only argument is the name of a function with this type signature: +/// +/// ```ignore +/// fn process_instruction( +/// mut context: InstructionContext, // wrapper around the input buffer +/// ) -> ProgramResult; +/// ``` +/// +/// # Examples +/// +/// Defining an entrypoint and making it conditional on the `bpf-entrypoint` feature. Although +/// the `entrypoint` module is written inline in this example, it is common to put it into its +/// own file. +/// +/// ```no_run +/// #[cfg(feature = "bpf-entrypoint")] +/// pub mod entrypoint { +/// +/// use pinocchio::{ +/// lazy_entrypoint, +/// lazy_entrypoint::InstructionContext, +/// msg, +/// ProgramResult +/// }; +/// +/// lazy_entrypoint!(process_instruction); +/// +/// pub fn process_instruction( +/// mut context: InstructionContext, +/// ) -> ProgramResult { +/// msg!("Hello from my lazy program!"); +/// +/// Ok(()) +/// } +/// +/// } +/// ``` +#[macro_export] +macro_rules! lazy_entrypoint { + ( $process_instruction:ident ) => { + /// Program entrypoint. + #[no_mangle] + pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { + match $process_instruction($crate::lazy_entrypoint::InstructionContext::new(input)) { + Ok(_) => $crate::SUCCESS, + Err(error) => error.into(), + } + } + }; +} + +/// Context to access data from the input buffer for the instruction. +/// +/// This is a wrapper around the input buffer that provides methods to read the accounts +/// and instruction data. It is used by the lazy entrypoint to access the input data on demand. +pub struct InstructionContext { + /// Pointer to the runtime input buffer for the instruction. + input: *mut u8, + + /// Number of available accounts. + /// + /// This value is decremented each time [`next_account`] is called. + available: u64, + + /// Current memory offset on the input buffer. + offset: usize, +} + +impl InstructionContext { + /// Creates a new [`InstructionContext`] for the input buffer. + pub fn new(input: *mut u8) -> Self { + let available = unsafe { *(input as *const u64) }; + + Self { + input, + available, + offset: core::mem::size_of::(), + } + } + + /// Reads the next account for the instruction. + /// + /// The account is represented as a [`MaybeAccount`], since it can either + /// represent and [`AccountInfo`] or the index of a duplicated account. It is up to the + /// caller to handle the mapping back to the source account. + #[inline(always)] + pub fn next_account(&mut self) -> Result { + self.available = self + .available + .checked_sub(1) + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + Ok(unsafe { read_account(self.input, &mut self.offset) }) + } + + /// Returns the next account for the instruction. + /// + /// Note that this method does *not* decrement the number of available accounts. + /// + /// # Safety + /// + /// It is up to the caller to guarantee that there is an account available; calling this when + /// there are no more available accounts results in undefined behavior. Additionally, when the + /// next account is duplicated will result in a panic. + #[inline(always)] + pub unsafe fn next_account_unchecked(&mut self) -> AccountInfo { + read_account(self.input, &mut self.offset).assume_account() + } + + /// Returns the number of available accounts. + /// + /// This value is decremented each time [`next_account`] is called. + #[inline(always)] + pub fn available(&self) -> u64 { + self.available + } + + /// Returns the instruction data for the instruction. + /// + /// This method can only be used after all accounts have been read; otherwise, it will + /// return a [`ProgramError::InvalidInstructionData`] error. + #[inline(always)] + pub fn instruction_data(&mut self) -> Result<(&[u8], &Pubkey), ProgramError> { + if self.available > 0 { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(unsafe { self.instruction_data_unchecked() }) + } + + /// Returns the instruction data for the instruction. + /// + /// # Safety + /// + /// It is up to the caller to guarantee that all accounts have been read; calling this method + /// before reading all accounts will result in undefined behavior. + #[inline(always)] + pub unsafe fn instruction_data_unchecked(&mut self) -> (&[u8], &Pubkey) { + let data_len = unsafe { *(self.input.add(self.offset) as *const usize) }; + // shadowing the offset to avoid leaving it in an inconsistent state + let offset = self.offset + core::mem::size_of::(); + let data = unsafe { core::slice::from_raw_parts(self.input.add(offset), data_len) }; + + (data, unsafe { + &*(self.input.add(offset + data_len) as *const Pubkey) + }) + } +} + +/// Wrapper type around an [`AccountInfo`] that may be a duplicate. +pub enum MaybeAccount { + /// An [`AccountInfo`] that is not a duplicate. + Account(AccountInfo), + + /// The index of the original account that was duplicated. + Duplicated(u8), +} + +impl MaybeAccount { + /// Extracts the wrapped [`AccountInfo`]. + /// + /// It is up to the caller to guarantee that the [`MaybeAccount`] really is in an + /// [`MaybeAccount::Account`]. Calling this method when the variant is a + /// [`MaybeAccount::Duplicated`] will result in a panic. + #[inline(always)] + pub fn assume_account(self) -> AccountInfo { + let MaybeAccount::Account(account) = self else { + panic!("Duplicated account") + }; + account + } +} + +/// Read an account from the input buffer. +/// +/// This can only be called with a buffer that was serialized by the runtime as +/// it assumes a specific memory layout. +#[allow(clippy::cast_ptr_alignment, clippy::missing_safety_doc)] +#[inline(always)] +unsafe fn read_account(input: *mut u8, offset: &mut usize) -> MaybeAccount { + let account: *mut Account = input.add(*offset) as *mut _; + + if (*account).borrow_state == NON_DUP_MARKER { + // repurpose the borrow state to track borrows + (*account).borrow_state = 0b_0000_0000; + + *offset += core::mem::size_of::(); + *offset += (*account).data_len as usize; + *offset += MAX_PERMITTED_DATA_INCREASE; + *offset += (*offset as *const u8).align_offset(BPF_ALIGN_OF_U128); + *offset += core::mem::size_of::(); + + MaybeAccount::Account(AccountInfo { raw: account }) + } else { + *offset += core::mem::size_of::(); + //the caller will handle the mapping to the original account + MaybeAccount::Duplicated((*account).borrow_state) + } +} From e8082b60d5e21900d227154de8f84249dcb0f64a Mon Sep 17 00:00:00 2001 From: febo Date: Sun, 20 Oct 2024 19:28:46 +0100 Subject: [PATCH 02/12] Move constants to crate root --- sdk/pinocchio/src/entrypoint.rs | 31 +++++++------------------------ sdk/pinocchio/src/lib.rs | 25 +++++++++++++++++++++++++ sdk/pinocchio/src/program.rs | 2 +- sdk/pinocchio/src/pubkey.rs | 4 ++-- sdk/pinocchio/src/sysvars/mod.rs | 2 +- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/sdk/pinocchio/src/entrypoint.rs b/sdk/pinocchio/src/entrypoint.rs index 3d5bbd3..1fd56bb 100644 --- a/sdk/pinocchio/src/entrypoint.rs +++ b/sdk/pinocchio/src/entrypoint.rs @@ -5,8 +5,8 @@ use core::{alloc::Layout, mem::size_of, ptr::null_mut, slice::from_raw_parts}; use crate::{ account_info::{Account, AccountInfo, MAX_PERMITTED_DATA_INCREASE}, - program_error::ProgramError, pubkey::Pubkey, + BPF_ALIGN_OF_U128, NON_DUP_MARKER, }; /// Start address of the memory region used for program heap. @@ -15,29 +15,12 @@ pub const HEAP_START_ADDRESS: u64 = 0x300000000; /// Length of the heap memory region used for program heap. pub const HEAP_LENGTH: usize = 32 * 1024; -/// Maximum number of accounts that a transaction may process. -/// -/// This value is used to set the maximum number of accounts that a program -/// is expecting and statically initialize the array of `AccountInfo`. -/// -/// This is based on the current [maximum number of accounts] that a transaction -/// may lock in a block. -/// -/// [maximum number of accounts]: https://github.com/anza-xyz/agave/blob/2e6ca8c1f62db62c1db7f19c9962d4db43d0d550/runtime/src/bank.rs#L3209-L3221 -pub const MAX_TX_ACCOUNTS: usize = 128; - -/// `assert_eq(core::mem::align_of::(), 8)` is true for BPF but not -/// for some host machines. -pub const BPF_ALIGN_OF_U128: usize = 8; - -/// Value used to indicate that a serialized account is not a duplicate. -pub const NON_DUP_MARKER: u8 = u8::MAX; - -/// Return value for a successful program execution. -pub const SUCCESS: u64 = 0; - +#[deprecated( + since = "0.6.0", + note = "Use `ProgramResult` from the crate root instead" +)] /// The result of a program execution. -pub type ProgramResult = Result<(), ProgramError>; +pub type ProgramResult = super::ProgramResult; /// Declare the program entrypoint and set up global handlers. /// @@ -126,7 +109,7 @@ macro_rules! entrypoint { core::slice::from_raw_parts(accounts.as_ptr() as _, count), &instruction_data, ) { - Ok(()) => $crate::entrypoint::SUCCESS, + Ok(()) => $crate::SUCCESS, Err(error) => error.into(), } } diff --git a/sdk/pinocchio/src/lib.rs b/sdk/pinocchio/src/lib.rs index ca8325f..230fc54 100644 --- a/sdk/pinocchio/src/lib.rs +++ b/sdk/pinocchio/src/lib.rs @@ -13,6 +13,7 @@ pub mod account_info; pub mod entrypoint; pub mod instruction; +pub mod lazy_entrypoint; pub mod log; pub mod memory; pub mod program; @@ -20,3 +21,27 @@ pub mod program_error; pub mod pubkey; pub mod syscalls; pub mod sysvars; + +/// Maximum number of accounts that a transaction may process. +/// +/// This value is used to set the maximum number of accounts that a program +/// is expecting and statically initialize the array of `AccountInfo`. +/// +/// This is based on the current [maximum number of accounts] that a transaction +/// may lock in a block. +/// +/// [maximum number of accounts]: https://github.com/anza-xyz/agave/blob/2e6ca8c1f62db62c1db7f19c9962d4db43d0d550/runtime/src/bank.rs#L3209-L3221 +pub const MAX_TX_ACCOUNTS: usize = 128; + +/// `assert_eq(core::mem::align_of::(), 8)` is true for BPF but not +/// for some host machines. +const BPF_ALIGN_OF_U128: usize = 8; + +/// Value used to indicate that a serialized account is not a duplicate. +const NON_DUP_MARKER: u8 = u8::MAX; + +/// Return value for a successful program execution. +pub const SUCCESS: u64 = 0; + +/// The result of a program execution. +pub type ProgramResult = Result<(), program_error::ProgramError>; diff --git a/sdk/pinocchio/src/program.rs b/sdk/pinocchio/src/program.rs index 104db6d..e642712 100644 --- a/sdk/pinocchio/src/program.rs +++ b/sdk/pinocchio/src/program.rs @@ -4,10 +4,10 @@ use core::{mem::MaybeUninit, ops::Deref}; use crate::{ account_info::AccountInfo, - entrypoint::ProgramResult, instruction::{Account, AccountMeta, Instruction, Signer}, program_error::ProgramError, pubkey::Pubkey, + ProgramResult, }; /// An `Instruction` as expected by `sol_invoke_signed_c`. diff --git a/sdk/pinocchio/src/pubkey.rs b/sdk/pinocchio/src/pubkey.rs index f81e247..532074c 100644 --- a/sdk/pinocchio/src/pubkey.rs +++ b/sdk/pinocchio/src/pubkey.rs @@ -131,7 +131,7 @@ pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option< ) }; match result { - crate::entrypoint::SUCCESS => Some((bytes, bump_seed)), + crate::SUCCESS => Some((bytes, bump_seed)), _ => None, } } @@ -183,7 +183,7 @@ pub fn create_program_address( ) }; match result { - crate::entrypoint::SUCCESS => Ok(bytes), + crate::SUCCESS => Ok(bytes), _ => Err(result.into()), } } diff --git a/sdk/pinocchio/src/sysvars/mod.rs b/sdk/pinocchio/src/sysvars/mod.rs index c571707..92ccd65 100644 --- a/sdk/pinocchio/src/sysvars/mod.rs +++ b/sdk/pinocchio/src/sysvars/mod.rs @@ -36,7 +36,7 @@ macro_rules! impl_sysvar_get { let result = core::hint::black_box(var_addr as *const _ as u64); match result { - $crate::entrypoint::SUCCESS => Ok(var), + $crate::SUCCESS => Ok(var), e => Err(e.into()), } } From 0681a0a548ca3b490323e763d62d7cf0b29e5c37 Mon Sep 17 00:00:00 2001 From: febo Date: Wed, 23 Oct 2024 11:51:47 +0100 Subject: [PATCH 03/12] Fix clippy --- sdk/pinocchio/src/log.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sdk/pinocchio/src/log.rs b/sdk/pinocchio/src/log.rs index ca36492..1281403 100644 --- a/sdk/pinocchio/src/log.rs +++ b/sdk/pinocchio/src/log.rs @@ -83,10 +83,10 @@ pub fn sol_log_slice(slice: &[u8]) { } } -/// Print the hexadecimal representation of the program's input parameters. -/// -/// - `accounts` - A slice of [`AccountInfo`]. -/// - `data` - The instruction data. +// Print the hexadecimal representation of the program's input parameters. +// +// - `accounts` - A slice of [`AccountInfo`]. +// - `data` - The instruction data. // TODO: This function is not yet implemented. /* pub fn sol_log_params(accounts: &[AccountInfo], data: &[u8]) { @@ -116,6 +116,4 @@ pub fn sol_log_compute_units() { unsafe { crate::syscalls::sol_log_compute_units_(); } - #[cfg(not(target_os = "solana"))] - core::hint::black_box(()); } From b788d6fde4281e520ecd506e01c3733f200645d3 Mon Sep 17 00:00:00 2001 From: febo Date: Wed, 23 Oct 2024 14:30:47 +0100 Subject: [PATCH 04/12] Add get account helper --- sdk/pinocchio/src/lazy_entrypoint.rs | 87 +++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/sdk/pinocchio/src/lazy_entrypoint.rs b/sdk/pinocchio/src/lazy_entrypoint.rs index 176e3c4..2fb2afb 100644 --- a/sdk/pinocchio/src/lazy_entrypoint.rs +++ b/sdk/pinocchio/src/lazy_entrypoint.rs @@ -77,10 +77,10 @@ pub struct InstructionContext { /// Pointer to the runtime input buffer for the instruction. input: *mut u8, - /// Number of available accounts. + /// Number of remaining accounts. /// /// This value is decremented each time [`next_account`] is called. - available: u64, + remaining: u64, /// Current memory offset on the input buffer. offset: usize, @@ -88,12 +88,11 @@ pub struct InstructionContext { impl InstructionContext { /// Creates a new [`InstructionContext`] for the input buffer. + #[inline(always)] pub fn new(input: *mut u8) -> Self { - let available = unsafe { *(input as *const u64) }; - Self { input, - available, + remaining: unsafe { *(input as *const u64) }, offset: core::mem::size_of::(), } } @@ -103,10 +102,15 @@ impl InstructionContext { /// The account is represented as a [`MaybeAccount`], since it can either /// represent and [`AccountInfo`] or the index of a duplicated account. It is up to the /// caller to handle the mapping back to the source account. + /// + /// # Error + /// + /// Returns a [`ProgramError::NotEnoughAccountKeys`] error if there are + /// no remaining accounts. #[inline(always)] pub fn next_account(&mut self) -> Result { - self.available = self - .available + self.remaining = self + .remaining .checked_sub(1) .ok_or(ProgramError::NotEnoughAccountKeys)?; @@ -115,24 +119,75 @@ impl InstructionContext { /// Returns the next account for the instruction. /// - /// Note that this method does *not* decrement the number of available accounts. + /// Note that this method does *not* decrement the number of remaining accounts, but moves + /// the offset forward. It is intended for use when the caller is certain on the number of + /// remaining accounts. /// /// # Safety /// - /// It is up to the caller to guarantee that there is an account available; calling this when - /// there are no more available accounts results in undefined behavior. Additionally, when the - /// next account is duplicated will result in a panic. + /// It is up to the caller to guarantee that there are remaining accounts; calling this when + /// there are no more remaining accounts results in undefined behavior. + #[inline(always)] + pub unsafe fn next_account_unchecked(&mut self) -> MaybeAccount { + read_account(self.input, &mut self.offset) + } + + /// Returns an account of the instruction given its index. + /// + /// This method operates independently to [`next_account`] and can be used at any point, + /// although it is recommended to use [`next_account`] when accessing the accounts + /// sequentially. #[inline(always)] - pub unsafe fn next_account_unchecked(&mut self) -> AccountInfo { - read_account(self.input, &mut self.offset).assume_account() + pub fn get_account(&self, index: u8) -> Result { + if index as u64 >= self.available() { + return Err(ProgramError::NotEnoughAccountKeys); + } + + Ok(unsafe { self.get_account_unchecked(index) }) + } + + /// Returns an account of the instruction given its index without validating whether + /// the index is within the available accounts. + /// + /// # Safety + /// + /// It is up to the caller to guarantee that here is an account available at the + /// given index; calling this with an invalid index results in undefined behavior. + #[inline(always)] + pub unsafe fn get_account_unchecked(&self, mut index: u8) -> MaybeAccount { + let mut offset = core::mem::size_of::(); + + while index > 0 { + let account: *mut Account = self.input.add(offset) as *mut _; + + if (*account).borrow_state == NON_DUP_MARKER { + offset += core::mem::size_of::(); + offset += (*account).data_len as usize; + offset += MAX_PERMITTED_DATA_INCREASE; + offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128); + offset += core::mem::size_of::(); + } else { + offset += core::mem::size_of::(); + } + + index -= 1; + } + + read_account(self.input, &mut offset) } /// Returns the number of available accounts. + #[inline(always)] + pub fn available(&self) -> u64 { + unsafe { *(self.input as *const u64) } + } + + /// Returns the number of remaining accounts. /// /// This value is decremented each time [`next_account`] is called. #[inline(always)] - pub fn available(&self) -> u64 { - self.available + pub fn remaining(&self) -> u64 { + self.remaining } /// Returns the instruction data for the instruction. @@ -141,7 +196,7 @@ impl InstructionContext { /// return a [`ProgramError::InvalidInstructionData`] error. #[inline(always)] pub fn instruction_data(&mut self) -> Result<(&[u8], &Pubkey), ProgramError> { - if self.available > 0 { + if self.remaining > 0 { return Err(ProgramError::InvalidInstructionData); } From 0d49b13444368bcd419a7c7c420c1f1edd18d64b Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 25 Oct 2024 10:22:10 +0100 Subject: [PATCH 05/12] Add entrypoint information --- README.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 41c320a..2eaa964 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,22 @@ From your project folder: cargo add pinocchio ``` +Pinocchio provides two different entrypoint macros: an `entrypoint` that looks similar to the "standard" one found in `solana-program` and a lightweight `lazy_entrypoint`. The main difference between them is how much work the entrypoint performs. While the `entrypoint` parsers the whole input and provide the `program_id`, `accounts` and `instruction_data` separately, the `lazy_entrypoint` only wraps the input at first. It then provides methods to parse the input on demand. The benefit in this case is that you have more control when the parsing is happening — even whether the parsing is needed or not. + +The `lazy_entrypoint` is suitable for program that have a single or very few instructions, since it requires the program to handle the parsing, which can become complex as the number of instructions increases. For "larger" programs, the `entrypoint` will likely be easier and more efficient to use. + +> ⚠️ **Note:** +> In both cases you should use the types from the `pinocchio` crate instead of `solana-program`. If you need to invoke a different program, you will need to redefine its instruction builder to create an equivalent instruction data using `pinocchio` types. + +### `entrypoint!` + On your entrypoint definition: ```rust use pinocchio::{ account_info::AccountInfo, entrypoint, - entrypoint::ProgramResult, msg, + ProgramResult pubkey::Pubkey }; @@ -73,8 +82,26 @@ pub fn process_instruction( } ``` -> ⚠️ **Note:** -> You should use the types from the `pinocchio` crate instead of `solana-program`. If you need to invoke a different program, you will need to redefine its instruction builder to create an equivalent instruction data using `pinocchio` types. +### `lazy_entrypoint!` + +On your entrypoint definition: +```rust +use pinocchio::{ + lazy_entrypoint, + lazy_entrypoint::InstructionContext, + msg, + ProgramResult +}; + +lazy_entrypoint!(process_instruction); + +pub fn process_instruction( + mut context: InstructionContext, +) -> ProgramResult { + msg!("Hello from my lazy program!"); + Ok(()) +} +``` ## License From e0b986c6dbe4fb353fcaadc2b519818a4334f03b Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 25 Oct 2024 10:35:46 +0100 Subject: [PATCH 06/12] Expand docs --- README.md | 22 +++++++++++++++++----- sdk/pinocchio/src/entrypoint.rs | 5 ++--- sdk/pinocchio/src/lazy_entrypoint.rs | 1 - 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2eaa964..e0ccb1e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The library defines: * core data types * logging macros * `syscall` functions -* access to system accounts (`sysvar`) +* access to system accounts (`sysvars`) * cross-program invocation ## Features @@ -58,9 +58,9 @@ The `lazy_entrypoint` is suitable for program that have a single or very few ins > ⚠️ **Note:** > In both cases you should use the types from the `pinocchio` crate instead of `solana-program`. If you need to invoke a different program, you will need to redefine its instruction builder to create an equivalent instruction data using `pinocchio` types. -### `entrypoint!` +### 🚪 `entrypoint!` -On your entrypoint definition: +To use the `entrypoint`, use the following in your entrypoint definition: ```rust use pinocchio::{ account_info::AccountInfo, @@ -82,9 +82,15 @@ pub fn process_instruction( } ``` -### `lazy_entrypoint!` +The information from the input is parsed into their own entities: -On your entrypoint definition: +* `program_id`: the `ID` of the program being called +* `accounts`: the accounts received +* `instruction_data`: data for the instruction + +### 🚪 `lazy_entrypoint!` + +To use the `lazy_entrypoint`, use the following in your entrypoint definition: ```rust use pinocchio::{ lazy_entrypoint, @@ -103,6 +109,12 @@ pub fn process_instruction( } ``` +The `InstructionContext` provides on-demand access to the information of the input: + +* `available()`: number of available accounts +* `next_account()`: parsers the next available account (can be used as many times as accounts available) +* `instruction_data()`: parsers the intruction data and program id + ## License The code is licensed under the [Apache License Version 2.0](LICENSE) diff --git a/sdk/pinocchio/src/entrypoint.rs b/sdk/pinocchio/src/entrypoint.rs index 1fd56bb..cee2db2 100644 --- a/sdk/pinocchio/src/entrypoint.rs +++ b/sdk/pinocchio/src/entrypoint.rs @@ -64,9 +64,9 @@ pub type ProgramResult = super::ProgramResult; /// use pinocchio::{ /// account_info::AccountInfo, /// entrypoint, -/// entrypoint::ProgramResult, /// msg, -/// pubkey::Pubkey +/// pubkey::Pubkey, +/// ProgramResult /// }; /// /// entrypoint!(process_instruction); @@ -77,7 +77,6 @@ pub type ProgramResult = super::ProgramResult; /// instruction_data: &[u8], /// ) -> ProgramResult { /// msg!("Hello from my program!"); -/// /// Ok(()) /// } /// diff --git a/sdk/pinocchio/src/lazy_entrypoint.rs b/sdk/pinocchio/src/lazy_entrypoint.rs index 2fb2afb..0cce8ac 100644 --- a/sdk/pinocchio/src/lazy_entrypoint.rs +++ b/sdk/pinocchio/src/lazy_entrypoint.rs @@ -49,7 +49,6 @@ use crate::{ /// mut context: InstructionContext, /// ) -> ProgramResult { /// msg!("Hello from my lazy program!"); -/// /// Ok(()) /// } /// From d6bedc361a91428ef65c3a815c234e57c76f6753 Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 25 Oct 2024 10:42:54 +0100 Subject: [PATCH 07/12] Cosmetics --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e0ccb1e..605eb60 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ The `lazy_entrypoint` is suitable for program that have a single or very few ins ### 🚪 `entrypoint!` -To use the `entrypoint`, use the following in your entrypoint definition: +To use the `entrypoint!` macro, use the following in your entrypoint definition: ```rust use pinocchio::{ account_info::AccountInfo, @@ -90,7 +90,7 @@ The information from the input is parsed into their own entities: ### 🚪 `lazy_entrypoint!` -To use the `lazy_entrypoint`, use the following in your entrypoint definition: +To use the `lazy_entrypoint!` macro, use the following in your entrypoint definition: ```rust use pinocchio::{ lazy_entrypoint, From 6a290923e1c6edc2404e8a93c132760659f268af Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 25 Oct 2024 10:44:21 +0100 Subject: [PATCH 08/12] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 605eb60..f9f668b 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ cargo add pinocchio Pinocchio provides two different entrypoint macros: an `entrypoint` that looks similar to the "standard" one found in `solana-program` and a lightweight `lazy_entrypoint`. The main difference between them is how much work the entrypoint performs. While the `entrypoint` parsers the whole input and provide the `program_id`, `accounts` and `instruction_data` separately, the `lazy_entrypoint` only wraps the input at first. It then provides methods to parse the input on demand. The benefit in this case is that you have more control when the parsing is happening — even whether the parsing is needed or not. -The `lazy_entrypoint` is suitable for program that have a single or very few instructions, since it requires the program to handle the parsing, which can become complex as the number of instructions increases. For "larger" programs, the `entrypoint` will likely be easier and more efficient to use. +The `lazy_entrypoint` is suitable for programs that have a single or very few instructions, since it requires the program to handle the parsing, which can become complex as the number of instructions increases. For "larger" programs, the `entrypoint` will likely be easier and more efficient to use. > ⚠️ **Note:** > In both cases you should use the types from the `pinocchio` crate instead of `solana-program`. If you need to invoke a different program, you will need to redefine its instruction builder to create an equivalent instruction data using `pinocchio` types. From 231606f3567331e283e8b3197c71f4392f69f591 Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 25 Oct 2024 11:31:22 +0100 Subject: [PATCH 09/12] Remove nested function call --- sdk/pinocchio/src/lazy_entrypoint.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/pinocchio/src/lazy_entrypoint.rs b/sdk/pinocchio/src/lazy_entrypoint.rs index 0cce8ac..499ed04 100644 --- a/sdk/pinocchio/src/lazy_entrypoint.rs +++ b/sdk/pinocchio/src/lazy_entrypoint.rs @@ -172,7 +172,9 @@ impl InstructionContext { index -= 1; } - read_account(self.input, &mut offset) + MaybeAccount::Account(AccountInfo { + raw: self.input.add(offset) as *mut _, + }) } /// Returns the number of available accounts. From f4b3002cac53ff31b9b37d1b9c2c188598e289ee Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 25 Oct 2024 14:48:31 +0100 Subject: [PATCH 10/12] Remove unsupported methods --- sdk/pinocchio/src/lazy_entrypoint.rs | 46 ---------------------------- 1 file changed, 46 deletions(-) diff --git a/sdk/pinocchio/src/lazy_entrypoint.rs b/sdk/pinocchio/src/lazy_entrypoint.rs index 499ed04..d33c746 100644 --- a/sdk/pinocchio/src/lazy_entrypoint.rs +++ b/sdk/pinocchio/src/lazy_entrypoint.rs @@ -131,52 +131,6 @@ impl InstructionContext { read_account(self.input, &mut self.offset) } - /// Returns an account of the instruction given its index. - /// - /// This method operates independently to [`next_account`] and can be used at any point, - /// although it is recommended to use [`next_account`] when accessing the accounts - /// sequentially. - #[inline(always)] - pub fn get_account(&self, index: u8) -> Result { - if index as u64 >= self.available() { - return Err(ProgramError::NotEnoughAccountKeys); - } - - Ok(unsafe { self.get_account_unchecked(index) }) - } - - /// Returns an account of the instruction given its index without validating whether - /// the index is within the available accounts. - /// - /// # Safety - /// - /// It is up to the caller to guarantee that here is an account available at the - /// given index; calling this with an invalid index results in undefined behavior. - #[inline(always)] - pub unsafe fn get_account_unchecked(&self, mut index: u8) -> MaybeAccount { - let mut offset = core::mem::size_of::(); - - while index > 0 { - let account: *mut Account = self.input.add(offset) as *mut _; - - if (*account).borrow_state == NON_DUP_MARKER { - offset += core::mem::size_of::(); - offset += (*account).data_len as usize; - offset += MAX_PERMITTED_DATA_INCREASE; - offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128); - offset += core::mem::size_of::(); - } else { - offset += core::mem::size_of::(); - } - - index -= 1; - } - - MaybeAccount::Account(AccountInfo { - raw: self.input.add(offset) as *mut _, - }) - } - /// Returns the number of available accounts. #[inline(always)] pub fn available(&self) -> u64 { From f8aa8232c7e3ec70302cd8e958f5404454174a8a Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 25 Oct 2024 14:50:26 +0100 Subject: [PATCH 11/12] Cleanup --- sdk/pinocchio/src/lazy_entrypoint.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sdk/pinocchio/src/lazy_entrypoint.rs b/sdk/pinocchio/src/lazy_entrypoint.rs index d33c746..86304de 100644 --- a/sdk/pinocchio/src/lazy_entrypoint.rs +++ b/sdk/pinocchio/src/lazy_entrypoint.rs @@ -166,14 +166,12 @@ impl InstructionContext { /// before reading all accounts will result in undefined behavior. #[inline(always)] pub unsafe fn instruction_data_unchecked(&mut self) -> (&[u8], &Pubkey) { - let data_len = unsafe { *(self.input.add(self.offset) as *const usize) }; + let data_len = *(self.input.add(self.offset) as *const usize); // shadowing the offset to avoid leaving it in an inconsistent state let offset = self.offset + core::mem::size_of::(); - let data = unsafe { core::slice::from_raw_parts(self.input.add(offset), data_len) }; + let data = core::slice::from_raw_parts(self.input.add(offset), data_len); - (data, unsafe { - &*(self.input.add(offset + data_len) as *const Pubkey) - }) + (data, &*(self.input.add(offset + data_len) as *const Pubkey)) } } From e4ac0814f8bfaaec3692145086401b324c39d5e3 Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 25 Oct 2024 23:48:02 +0100 Subject: [PATCH 12/12] Add reexport --- sdk/pinocchio/src/entrypoint.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk/pinocchio/src/entrypoint.rs b/sdk/pinocchio/src/entrypoint.rs index cee2db2..1629463 100644 --- a/sdk/pinocchio/src/entrypoint.rs +++ b/sdk/pinocchio/src/entrypoint.rs @@ -22,6 +22,10 @@ pub const HEAP_LENGTH: usize = 32 * 1024; /// The result of a program execution. pub type ProgramResult = super::ProgramResult; +#[deprecated(since = "0.6.0", note = "Use `SUCCESS` from the crate root instead")] +/// Return value for a successful program execution. +pub const SUCCESS: u64 = super::SUCCESS; + /// Declare the program entrypoint and set up global handlers. /// /// The main difference from the standard `entrypoint!` macro is that this macro represents an