Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
The `middleware_program_entrypoint!` macro defines a dual entrypoint implementation consisting of a `hot` path which bypasses entrypoint deserialization, and a `cold` path which 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 program 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
middleware_program_entrypoint!(hot,cold);
middleware_program_entrypoint!(hot, cold);


// 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:
Expand Down
94 changes: 94 additions & 0 deletions sdk/pinocchio/src/entrypoint/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! Defines the middleware program entrypoint, enabling a hot path to bypass
//! entrypoint deserialization, ejecting to the cold path on failure.
/// 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// middleware_program_entrypoint!(hot,cold);
/// middleware_program_entrypoint!(hot, cold);

///
/// // 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(),
}
}
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}

2 changes: 2 additions & 0 deletions sdk/pinocchio/src/entrypoint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub mod lazy;

pub use lazy::{InstructionContext, MaybeAccount};

pub mod middleware;

#[cfg(not(feature = "std"))]
use core::alloc::{GlobalAlloc, Layout};

Expand Down
Loading