Comprehensive guide for building high-performance Solana programs using Pinocchio - the zero-dependency, zero-copy framework. Covers account validation, CPI patterns, optimization techniques, and migration from Anchor.
Build blazing-fast Solana programs with Pinocchio - a zero-dependency, zero-copy framework that delivers 88-95% compute unit reduction and 40% smaller binaries compared to traditional approaches.
Overview
Pinocchio is Anza's minimalist Rust library for writing Solana programs without the heavyweight solana-program crate. It treats incoming transaction data as a single byte slice, reading it in-place via zero-copy techniques.
Performance Comparison
Metric
Anchor
Native (solana-program)
Pinocchio
Token Transfer CU
~6,000
~4,500
~600-800
Binary Size
Large
Medium
Small (-40%)
Heap Allocation
Required
Required
Optional
Dependencies
Many
Several
Zero*
*Only Solana SDK types for on-chain execution
When to Use Pinocchio
Use Pinocchio When:
Building high-throughput programs (DEXs, orderbooks, games)
Compute units are a bottleneck
Binary size matters (program deployment costs)
You need maximum control over memory
Building infrastructure (tokens, vaults, escrows)
Consider Anchor Instead When:
Rapid prototyping / MVPs
Team unfamiliar with low-level Rust
Complex account relationships
Need extensive ecosystem tooling
Audit timeline is tight (more auditors know Anchor)
Quick Start
1. Project Setup
# Cargo.toml
[package]
name = "my-program"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
[features]
default = []
bpf-entrypoint = []
[dependencies]
pinocchio = "0.10"
pinocchio-system = "0.4" # System Program CPI helpers
pinocchio-token = "0.4" # Token Program CPI helpers
bytemuck = { version = "1.14", features = ["derive"] }
[profile.release]
overflow-checks = true
lto = "fat"
codegen-units = 1
opt-level = 3
use pinocchio_system::instructions::CreateAccount;
pub fn initialize(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
let ctx = InitializeAccounts::parse(accounts)?;
// Derive PDA
let (pda, bump) = Pubkey::find_program_address(
&[b"vault", ctx.owner.key().as_ref()],
&crate::ID,
);
// Verify PDA matches
if ctx.vault.key() != &pda {
return Err(ProgramError::InvalidSeeds);
}
// Create account via CPI
let space = Vault::LEN as u64;
let rent = pinocchio::sysvar::rent::Rent::get()?;
let lamports = rent.minimum_balance(space as usize);
CreateAccount {
from: ctx.owner,
to: ctx.vault,
lamports,
space,
owner: &crate::ID,
}
.invoke_signed(&[&[b"vault", ctx.owner.key().as_ref(), &[bump]]])?;
// Initialize account data
let vault = Vault::from_account_mut(ctx.vault)?;
vault.discriminator = VAULT_DISCRIMINATOR;
vault.owner = ctx.owner.key().to_bytes();
vault.balance = 0;
vault.bump = bump;
Ok(())
}
Entrypoint Options
Pinocchio provides three entrypoint macros with different trade-offs:
1. Standard Entrypoint (Recommended for most cases)
use pinocchio::entrypoint;
entrypoint!(process_instruction);
Sets up heap allocator
Configures panic handler
Deserializes accounts automatically
2. Lazy Entrypoint (Best for single-instruction programs)
use pinocchio::lazy_entrypoint;
lazy_entrypoint!(process_instruction);
pub fn process_instruction(mut context: InstructionContext) -> ProgramResult {
// Accounts parsed on-demand
let account = context.next_account()?;
let data = context.instruction_data();
Ok(())
}
Defers parsing until needed
Best CU savings for simple programs
80-87% CU reduction in memo program benchmarks
3. No Allocator (Maximum optimization)
use pinocchio::{entrypoint, no_allocator};
no_allocator!();
entrypoint!(process_instruction);
Disables heap entirely
Cannot use String, Vec, Box
Best for statically-sized operations
CPI Patterns
System Program CPI
use pinocchio_system::instructions::{CreateAccount, Transfer};
// Create account
CreateAccount {
from: payer,
to: new_account,
lamports: rent_lamports,
space: account_size,
owner: &program_id,
}.invoke()?;
// Transfer SOL
Transfer {
from: source,
to: destination,
lamports: amount,
}.invoke()?;
// Transfer with PDA signer
Transfer {
from: pda_account,
to: destination,
lamports: amount,
}.invoke_signed(&[&[b"vault", owner.as_ref(), &[bump]]])?;
Token Program CPI
use pinocchio_token::instructions::{Transfer, MintTo, Burn};
// Transfer tokens
Transfer {
source: from_token_account,
destination: to_token_account,
authority: owner,
amount: token_amount,
}.invoke()?;
// Mint tokens (with PDA authority)
MintTo {
mint: mint_account,
token_account: destination,
authority: mint_authority_pda,
amount: mint_amount,
}.invoke_signed(&[&[b"mint_auth", &[bump]]])?;
Custom CPI (Third-party programs)
use pinocchio::{
instruction::{AccountMeta, Instruction},
program::invoke,
};
// Build instruction manually
let accounts = vec![
AccountMeta::new(*account1.key(), false),
AccountMeta::new_readonly(*account2.key(), true),
];
let ix = Instruction {
program_id: &external_program_id,
accounts: &accounts,
data: &instruction_data,
};
invoke(&ix, &[account1, account2])?;