-
Notifications
You must be signed in to change notification settings - Fork 165
feat: middleware entrypoint #241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e5f42d0
fa62057
60379aa
57a94b0
d3d3c95
d07ad3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| /node_modules | ||
| /target | ||
| .DS_Store |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -172,6 +172,48 @@ pub fn process_instruction( | |||||
| > ⚠️ **Note:** | ||||||
| > The `no_allocator!` macro can also be used in combination with the `lazy_program_entrypoint!`. | ||||||
|
|
||||||
| 📌 [`middleware_program_entrypoint!`](https://docs.rs/pinocchio/latest/pinocchio/macro.middleware_program_entrypoint.html) | ||||||
|
|
||||||
| The `middleware_program_entrypoint!` macro defines a dual entrypoint implementation consisting of a `hot` path which bypasses entrypoint deserialization, and a `cold` path with behaves like a regular pinocchio entrypoint that has undergone `program_entrypoint!` deserialization. This gives the user flexibility to implement extreme CU optimizations against the most critical paths in their proggram with the convenience of being able to fallback to a regular pinocchio program to manage the fail case and other instructions that do not require such optimizations. | ||||||
|
|
||||||
| To use the `middleware_program_entrypoint!` macro, use the following in your entrypoint definition: | ||||||
|
|
||||||
| ```rust | ||||||
| #![cfg_attr(target_os = "solana", no_std)] | ||||||
| use pinocchio::{ | ||||||
| ProgramResult, | ||||||
| account_info::AccountInfo, | ||||||
| middleware_program_entrypoint, | ||||||
| msg, | ||||||
| no_allocator, | ||||||
| nostd_panic_handler, | ||||||
| pubkey::Pubkey | ||||||
| }; | ||||||
|
|
||||||
| nostd_panic_handler!(); | ||||||
| no_allocator!(); | ||||||
|
|
||||||
| middleware_program_entrypoint!(hot,cold); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| // This uses 4 CUs | ||||||
| #[inline(always)] | ||||||
| pub fn hot(input: *mut u8) -> u64 { | ||||||
| unsafe { *input as u64 } | ||||||
| } | ||||||
|
|
||||||
| // This uses 113 CUs | ||||||
| #[cold] | ||||||
| #[inline(always)] | ||||||
| pub fn cold( | ||||||
| _program_id: &Pubkey, | ||||||
| _accounts: &[AccountInfo], | ||||||
| _instruction_data: &[u8], | ||||||
| ) -> ProgramResult { | ||||||
| msg!("Hello from Pinocchio!"); | ||||||
| Ok(()) | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## Crate feature: `std` | ||||||
|
|
||||||
| By default, `pinocchio` is a `no_std` crate. This means that it does not use any code from the standard (`std`) library. While this does not affect how `pinocchio` is used, there is one particular apparent difference. In a `no_std` environment, the `msg!` macro does not provide any formatting options since the `format!` macro requires the `std` library. In order to use `msg!` with formatting, the `std` feature should be enabled when adding `pinocchio` as a dependency: | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,248 @@ | ||||||||||||||||||||||||||||
| //! Defines the middleware program entrypoint, enabling a hot path to bypass | ||||||||||||||||||||||||||||
| //! entrypoint deserialization, ejecting to the cold path on failure. | ||||||||||||||||||||||||||||
deanmlittle marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||
| /// Declare the middleware program entrypoint. | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// The macro expects a `hot` and `cold` path expressions. The `hot` is a function with | ||||||||||||||||||||||||||||
| /// the following type signature: | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ```ignore | ||||||||||||||||||||||||||||
| /// fn hot(input: mut *u8) -> u64; | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| /// The `input` argument represents the program input parameters serialized by the SVM | ||||||||||||||||||||||||||||
| /// loader. The `cold` is a function with the following type signature: | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ```ignore | ||||||||||||||||||||||||||||
| /// fn cold( | ||||||||||||||||||||||||||||
| /// program_id: &Pubkey, // Public key of the account the program was loaded into | ||||||||||||||||||||||||||||
| /// accounts: &[AccountInfo], // All accounts required to process the instruction | ||||||||||||||||||||||||||||
| /// instruction_data: &[u8], // Serialized instruction-specific data | ||||||||||||||||||||||||||||
| /// ) -> ProgramResult; | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| /// # Example | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// A middleware program entrypoint where an invocation with zero accounts will lead to | ||||||||||||||||||||||||||||
| /// the "hot" path, and anything else will fallback to the "cold" path: | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ```norun | ||||||||||||||||||||||||||||
| /// #![cfg_attr(target_os = "solana", no_std)] | ||||||||||||||||||||||||||||
| /// use pinocchio::{ | ||||||||||||||||||||||||||||
| /// ProgramResult, | ||||||||||||||||||||||||||||
| /// account_info::AccountInfo, | ||||||||||||||||||||||||||||
| /// middleware_program_entrypoint, | ||||||||||||||||||||||||||||
| /// msg, | ||||||||||||||||||||||||||||
| /// no_allocator, | ||||||||||||||||||||||||||||
| /// nostd_panic_handler, | ||||||||||||||||||||||||||||
| /// pubkey::Pubkey | ||||||||||||||||||||||||||||
| /// }; | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// nostd_panic_handler!(); | ||||||||||||||||||||||||||||
| /// no_allocator!(); | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// middleware_program_entrypoint!(hot,cold); | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// // This uses 4 CUs | ||||||||||||||||||||||||||||
| /// #[inline(always)] | ||||||||||||||||||||||||||||
| /// pub fn hot(input: *mut u8) -> u64 { | ||||||||||||||||||||||||||||
| /// unsafe { *input as u64 } | ||||||||||||||||||||||||||||
| /// } | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// // This uses 113 CUs | ||||||||||||||||||||||||||||
| /// #[cold] | ||||||||||||||||||||||||||||
| /// #[inline(always)] | ||||||||||||||||||||||||||||
| /// pub fn cold( | ||||||||||||||||||||||||||||
| /// _program_id: &Pubkey, | ||||||||||||||||||||||||||||
| /// _accounts: &[AccountInfo], | ||||||||||||||||||||||||||||
| /// _instruction_data: &[u8], | ||||||||||||||||||||||||||||
| /// ) -> ProgramResult { | ||||||||||||||||||||||||||||
| /// msg!("Hello from Pinocchio!"); | ||||||||||||||||||||||||||||
| /// Ok(()) | ||||||||||||||||||||||||||||
| /// } | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| #[macro_export] | ||||||||||||||||||||||||||||
| macro_rules! middleware_program_entrypoint { | ||||||||||||||||||||||||||||
| ($hot:expr, $cold:expr) => { | ||||||||||||||||||||||||||||
| $crate::middleware_program_entrypoint!($hot, $cold, { $crate::MAX_TX_ACCOUNTS }); | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| ($hot:expr, $cold:expr, $maximum:expr ) => { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #[no_mangle] | ||||||||||||||||||||||||||||
| pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { | ||||||||||||||||||||||||||||
| if $hot(input) == 0 { | ||||||||||||||||||||||||||||
| return $crate::SUCCESS | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const UNINIT: core::mem::MaybeUninit<$crate::account_info::AccountInfo> = core::mem::MaybeUninit::<$crate::account_info::AccountInfo>::uninit(); | ||||||||||||||||||||||||||||
| // Create an array of uninitialized account infos. | ||||||||||||||||||||||||||||
| let mut accounts = [UNINIT; $maximum]; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| let (program_id, count, instruction_data) = unsafe { | ||||||||||||||||||||||||||||
| $crate::entrypoint::deserialize::<$maximum>(input, &mut accounts) }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Call the program's entrypoint passing `count` account infos; we know that | ||||||||||||||||||||||||||||
| // they are initialized so we cast the pointer to a slice of `[AccountInfo]`. | ||||||||||||||||||||||||||||
| match $cold( | ||||||||||||||||||||||||||||
| &program_id, | ||||||||||||||||||||||||||||
| unsafe { core::slice::from_raw_parts(accounts.as_ptr() as _, count) }, | ||||||||||||||||||||||||||||
| &instruction_data, | ||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||
| Ok(()) => $crate::SUCCESS, | ||||||||||||||||||||||||||||
| Err(error) => error.into(), | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #[cfg(feature = "asm")] | ||||||||||||||||||||||||||||
| #[macro_export] | ||||||||||||||||||||||||||||
| macro_rules! asm_middleware_program_entrypoint { | ||||||||||||||||||||||||||||
| ($hot:expr, $cold:expr) => { | ||||||||||||||||||||||||||||
| $crate::asm_middleware_program_entrypoint!($hot, $cold, { $crate::MAX_TX_ACCOUNTS }); | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| ($hot:expr, $cold:expr, $maximum:expr ) => { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #[no_mangle] | ||||||||||||||||||||||||||||
| pub unsafe extern "C" fn entrypoint(input: *mut u8) { | ||||||||||||||||||||||||||||
| // Inject `hot()` directly. Assume conditional return is handled within this function. | ||||||||||||||||||||||||||||
| $hot(input); | ||||||||||||||||||||||||||||
| const UNINIT: core::mem::MaybeUninit<$crate::account_info::AccountInfo> = core::mem::MaybeUninit::<$crate::account_info::AccountInfo>::uninit(); | ||||||||||||||||||||||||||||
| // Create an array of uninitialized account infos. | ||||||||||||||||||||||||||||
| let mut accounts = [UNINIT; $maximum]; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| let (program_id, count, instruction_data) = unsafe { | ||||||||||||||||||||||||||||
| $crate::entrypoint::deserialize::<$maximum>(input, &mut accounts) }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Call the program's entrypoint passing `count` account infos; we know that | ||||||||||||||||||||||||||||
| // they are initialized so we cast the pointer to a slice of `[AccountInfo]`. | ||||||||||||||||||||||||||||
| match $cold( | ||||||||||||||||||||||||||||
| &program_id, | ||||||||||||||||||||||||||||
| unsafe { core::slice::from_raw_parts(accounts.as_ptr() as _, count) }, | ||||||||||||||||||||||||||||
| &instruction_data, | ||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||
| Ok(()) => { | ||||||||||||||||||||||||||||
| $crate::set_return_imm!($crate::SUCCESS) | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| Err(error) => { | ||||||||||||||||||||||||||||
| $crate::set_return!(Into::<u64>::into(error) as u32) | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// # Set Return | ||||||||||||||||||||||||||||
| /// Sets the return register `r0` to a stored value. For static return values, consider using `set_return_imm` to save 1 CU by avoiding an additional register allocation. | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### CU Cost | ||||||||||||||||||||||||||||
| /// 1 CU (+1 CU for register allocation) | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### ASM | ||||||||||||||||||||||||||||
| /// `mov32 r0, r1` | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### Parameters | ||||||||||||||||||||||||||||
| /// - `value`: The stored value to set the return register. | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### Example | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| /// let n = 1337; | ||||||||||||||||||||||||||||
| /// set_return(n); | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// This will assign `1337` to `n`, then set the register `r0` to `n`. | ||||||||||||||||||||||||||||
| #[cfg(feature = "asm")] | ||||||||||||||||||||||||||||
| #[macro_export] | ||||||||||||||||||||||||||||
| macro_rules! set_return { | ||||||||||||||||||||||||||||
| ($value:expr) => { | ||||||||||||||||||||||||||||
| #[cfg(target_os = "solana")] | ||||||||||||||||||||||||||||
| unsafe { | ||||||||||||||||||||||||||||
| core::arch::asm!( | ||||||||||||||||||||||||||||
| "mov32 r0, {0}", | ||||||||||||||||||||||||||||
| in(reg) $value | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+157
to
+162
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a compile-time check that
Suggested change
|
||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// # Set Return Register from Immediate | ||||||||||||||||||||||||||||
| /// Sets the return register `r0` to an immediate value. | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### CU Cost | ||||||||||||||||||||||||||||
| /// 1 CU | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### ASM | ||||||||||||||||||||||||||||
| /// `mov32 r0, r1` | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### Parameters | ||||||||||||||||||||||||||||
| /// - `value`: The stored value to set the return register. | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### Example | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| /// set_return_imm(1337); // Set return value to error code 1 | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// This will assign the return register `r0` to `1337`. | ||||||||||||||||||||||||||||
| #[cfg(feature = "asm")] | ||||||||||||||||||||||||||||
| #[macro_export] | ||||||||||||||||||||||||||||
| macro_rules! set_return_imm { | ||||||||||||||||||||||||||||
| ($value:expr) => { | ||||||||||||||||||||||||||||
| #[cfg(target_os = "solana")] | ||||||||||||||||||||||||||||
| unsafe { | ||||||||||||||||||||||||||||
| core::arch::asm!( | ||||||||||||||||||||||||||||
| "mov32 r0, {0}", | ||||||||||||||||||||||||||||
| const $value | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// # Exit | ||||||||||||||||||||||||||||
| /// Immediately exits the program | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### CU Cost | ||||||||||||||||||||||||||||
| /// 1 CU | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### ASM | ||||||||||||||||||||||||||||
| /// `exit` | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### Example | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| /// exit!(); // Set return value to error code 1 | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| #[cfg(feature = "asm")] | ||||||||||||||||||||||||||||
| #[macro_export] | ||||||||||||||||||||||||||||
| macro_rules! exit { | ||||||||||||||||||||||||||||
| () => { | ||||||||||||||||||||||||||||
| #[cfg(target_os = "solana")] | ||||||||||||||||||||||||||||
| unsafe { | ||||||||||||||||||||||||||||
| core::arch::asm!( | ||||||||||||||||||||||||||||
| "exit" | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// # Reset | ||||||||||||||||||||||||||||
| /// Resets r1 to the address of the serialized input region | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### CU Cost | ||||||||||||||||||||||||||||
| /// 1 CU | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### ASM | ||||||||||||||||||||||||||||
| /// `lddw r1, 0x400000000` | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ### Example | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| /// reset!(); // Set r1 to address of serialized input region | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| #[cfg(feature = "asm")] | ||||||||||||||||||||||||||||
| #[macro_export] | ||||||||||||||||||||||||||||
| macro_rules! reset { | ||||||||||||||||||||||||||||
| () => { | ||||||||||||||||||||||||||||
| #[cfg(target_os = "solana")] | ||||||||||||||||||||||||||||
| unsafe { | ||||||||||||||||||||||||||||
| core::arch::asm!( | ||||||||||||||||||||||||||||
| "lddw r1, 0x400000000" | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.