Expand description
A no external dependencies library to create Solana programs in Rust. The only dependencies are types from the Solana SDK specifically designed for on-chain programs. This mitigates dependency issues and offers an efficient zero-copy library to write programs, optimized in terms of both compute units consumption and binary size.
§Defining the program entrypoint
A Solana program needs to define an entrypoint, which will be called by the
runtime to begin the program execution. The entrypoint! macro emits the
common boilerplate to set up the program entrypoint. The macro will also set
up global allocator
and panic handler using
the default_allocator! and default_panic_handler! macros.
The entrypoint!
is a convenience macro that invokes three other macros to set all components
required for a program execution:
program_entrypoint!: declares the program entrypointdefault_allocator!: declares the default (bump) global allocatordefault_panic_handler!: declares the default panic “hook” that works in combination with thestdpanic handler
When all dependencies are no_std, you should use nostd_panic_handler!
instead of default_panic_handler! to declare a rust runtime panic handler.
There’s no need to do this when any dependency is std since rust compiler
will emit a panic handler.
To use the entrypoint! macro, use the following in your entrypoint
definition:
use pinocchio::{
AccountView,
Address,
entrypoint,
ProgramResult
};
use solana_program_log::log;
entrypoint!(process_instruction);
pub fn process_instruction(
program_id: &Address,
accounts: &[AccountView],
instruction_data: &[u8],
) -> ProgramResult {
log!("Hello from my pinocchio program!");
Ok(())
}The information from the input is parsed into their own entities:
program_id: theIDof the program being calledaccounts: the accounts receivedinstruction_data: data for the instruction
pinocchio also offers variations of the program entrypoint
(lazy_program_entrypoint) and global allocator (no_allocator). In order
to use these, the program needs to specify the program entrypoint, global
allocator and panic handler individually. The entrypoint! macro is
equivalent to writing:
program_entrypoint!(process_instruction);
default_allocator!();
default_panic_handler!();Any of these macros can be replaced by alternative implementations.
§Custom entrypoints with crate::entrypoint::process_entrypoint
For programs that need maximum control over the entrypoint, pinocchio
exposes the crate::entrypoint::process_entrypoint function. This
function is the same deserialization logic used internally by the
program_entrypoint! macro, exposed as a public API and can be called
directly from a custom entrypoint, allowing you to implement fast-path
optimizations or custom pre-processing logic before falling back to standard
input parsing.
To use crate::entrypoint::process_entrypoint in a custom entrypoint:
use pinocchio::{
AccountView,
Address,
default_panic_handler,
entrypoint::process_entrypoint,
MAX_TX_ACCOUNTS,
no_allocator,
ProgramResult,
};
use solana_program_log::log;
no_allocator!();
default_panic_handler!();
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
// Fast path: check the number of accounts
let num_accounts = unsafe { *(input as *const u64) };
if num_accounts == 0 {
log("Fast path - no accounts!");
return 0;
}
// Standard path: delegate to `process_entrypoint`
unsafe { process_entrypoint::<MAX_TX_ACCOUNTS>(input, process_instruction) }
}
pub fn process_instruction(
program_id: &Address,
accounts: &[AccountView],
instruction_data: &[u8],
) -> ProgramResult {
log("Standard path");
Ok(())
}§lazy_program_entrypoint!
The entrypoint! macro looks similar to the “standard” one found in
solana-program-entrypoint.
It parses the whole input and provides the program_id, accounts and
instruction_data separately. This consumes compute units before the
program begins its execution. In some cases, it is beneficial for a program
to have more control when the input parsing is happening, even whether the
parsing is needed or not - this is the purpose of the
lazy_program_entrypoint! macro. This macro only wraps the program input
and provides methods to parse the input on-demand.
The lazy_program_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 program_entrypoint! will likely be easier and
more efficient to use.
To use the lazy_program_entrypoint! macro, use the following in your
entrypoint definition:
use pinocchio::{
default_allocator,
default_panic_handler,
entrypoint::InstructionContext,
lazy_program_entrypoint,
ProgramResult
};
lazy_program_entrypoint!(process_instruction);
default_allocator!();
default_panic_handler!();
pub fn process_instruction(
mut context: InstructionContext
) -> ProgramResult {
Ok(())
}The InstructionContext provides
on-demand access to the information of the input:
remaining(): number of available accounts to parse; this number is decremented as the program parses accounts.next_account(): parses the next available account (can be used as many times as accounts available).instruction_data(): parses the instruction data.program_id(): parses the program id.
💡 The lazy_program_entrypoint! does not set up a global allocator nor a
panic handler. A program should explicitly use one of the provided macros to
set them up or include its own implementation.
§no_allocator!
When writing programs, it can be useful to make sure the program does not
attempt to make any allocations. For this cases, Pinocchio includes a
no_allocator! macro that set a global allocator just panics at any
attempt to allocate memory.
To use the no_allocator! macro, use the following in your entrypoint
definition:
use pinocchio::{
AccountView,
Address,
default_panic_handler,
no_allocator,
program_entrypoint,
ProgramResult
};
program_entrypoint!(process_instruction);
default_panic_handler!();
no_allocator!();
pub fn process_instruction(
program_id: &Address,
accounts: &[AccountView],
instruction_data: &[u8],
) -> ProgramResult {
Ok(())
}💡 The no_allocator! macro can also be used in combination with the
lazy_program_entrypoint!.
Since the no_allocator! macro does not allocate memory, the 32kb memory
region reserved for the heap remains unused. To take advantage of this, the
no_allocator! macro emits an allocate_unchecked helper function that
allows you to manually reserve memory for a type at compile time.
// static allocation:
// - 0 is the offset when the type will be allocated
// - `allocate_unchecked` returns a mutable reference to the allocated
type let lamports = allocate_unchecked::<u64>(0);
*lamports = 1_000_000_000;Note that it is the developer’s responsibility to ensure that types do not
overlap in memory - the offset + <size of type> of different types must
not overlap.
§nostd_panic_handler!
When writing no_std programs, it is necessary to declare a panic handler
using the nostd_panic_handler! macro. This macro sets up a default panic
handler that logs the location (file, line and column) where the panic
occurred and then calls the abort() syscall.
💡 The default_panic_handler! macro only works in an std context.
§Crate features
§alloc
The alloc feature is enabled by default and it uses the alloc
crate. This provides access to dynamic memory allocation in combination with
the default_allocator!, e.g., required to use String and Vec in a
program. Helpers that need to allocate memory, such as fetching
crate::sysvars::slot_hashes::SlotHashes::fetch sysvar data, are also
available.
When no allocation is needed or desired, the feature can be disabled:
pinocchio = { version = "0.10.0", default-features = false }§copy
The copy feature enables the derivation of the Copy trait for types. It
also enables the copy feature on the solana-account-view and
solana-address re-exports.
§cpi
The cpi feature enables the cross-program invocation helpers, as well as
types to define instructions and signer information.
pinocchio = { version = "0.10.0", features = ["cpi"] }§Advanced entrypoint configuration
The symbols emitted by the entrypoint macros - program entrypoint, global
allocator and default panic handler - can only be defined once globally. If
the program crate is also intended to be used as a library, it is common
practice to define a Cargo feature
in your program crate to conditionally enable the module that includes the
entrypoint! macro invocation. The convention is to name the feature
bpf-entrypoint.
#[cfg(feature = "bpf-entrypoint")]
mod entrypoint {
use pinocchio::{
AccountView,
Address,
entrypoint,
ProgramResult
};
entrypoint!(process_instruction);
pub fn process_instruction(
program_id: &Address,
accounts: &[AccountView],
instruction_data: &[u8],
) -> ProgramResult {
Ok(())
}
}When building the program binary, you must enable the bpf-entrypoint
feature:
cargo build-sbf --features bpf-entrypoint§Upstream BPF compatibility
Pinocchio is compatible with upstream BPF target (target_arch = bpf). When
using syscalls (e.g., cross-program invocations), it is necessary to
explicitly enable static syscalls in your program’s Cargo.toml:
[dependencies]
# Enable static syscalls for BPF target
solana-define-syscall = { version = "4.0.1", features = ["unstable-static-syscalls"] }When compiling your program with the upstream BPF target, the std library
is not available. Therefore, the program crate must include the #![no_std]
crate-level attribute and use the nostd_panic_handler! macro. An
allocator may be used as long as alloc is used.
Re-exports§
pub use solana_account_view as account;pub use solana_address as address;pub use solana_program_error as error;pub use solana_instruction_view as instruction;cpi
Modules§
- cpi
cpi - Cross-program invocation helpers.
- entrypoint
- Macros and functions for defining the program entrypoint and setting up global handlers.
- hint
- Module with functions to provide hints to the compiler about how code should be optimized.
- sysvars
- Provides access to cluster system accounts.
Macros§
- default_
allocator alloc - Default global allocator.
- default_
panic_ handler - Default panic hook.
- entrypoint
alloc - Declare the program entrypoint and set up global handlers.
- impl_
sysvar_ get - Implements the
Sysvar::getmethod for both SBF and host targets. - lazy_
program_ entrypoint - Declare the lazy program entrypoint.
- no_
allocator - A global allocator that does not dynamically allocate memory.
- nostd_
panic_ handler - A global
#[panic_handler]forno_stdprograms. - program_
entrypoint - Declare the program entrypoint.
Structs§
- Account
View - Wrapper struct for a
RuntimeAccount. - Address
- The address of a Solana account.
Constants§
- MAX_
TX_ ACCOUNTS - Maximum number of accounts that a transaction may process.
- SUCCESS
- Return value for a successful program execution.