pinocchio-development

安装量: 49
排名: #15107

安装

npx skills add https://github.com/sendaifun/skills --skill pinocchio-development

Pinocchio Development Guide 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 2. Basic Program Structure use pinocchio :: { account_info :: AccountInfo , entrypoint , program_error :: ProgramError , pubkey :: Pubkey , ProgramResult , } ; // Declare entrypoint entrypoint! ( process_instruction ) ; pub fn process_instruction ( program_id : & Pubkey , accounts : & [ AccountInfo ] , instruction_data : & [ u8 ] , ) -> ProgramResult { // Route instructions by discriminator (first byte) match instruction_data . first ( ) { Some ( 0 ) => initialize ( accounts , & instruction_data [ 1 .. ] ) , Some ( 1 ) => execute ( accounts , & instruction_data [ 1 .. ] ) , _ => Err ( ProgramError :: InvalidInstructionData ) , } } 3. Account Definition with Bytemuck use bytemuck :: { Pod , Zeroable } ; // Single-byte discriminator for account type pub const VAULT_DISCRIMINATOR : u8 = 1 ;

[repr(C)]

[derive(Clone, Copy, Pod, Zeroable)]

pub struct Vault { pub discriminator : u8 , pub owner : [ u8 ; 32 ] , // Pubkey as bytes pub balance : u64 , pub bump : u8 , pub _padding : [ u8 ; 6 ] , // Align to 8 bytes } impl Vault { pub const LEN : usize = std :: mem :: size_of :: < Self

( ) ; pub fn from_account ( account : & AccountInfo ) -> Result < & Self , ProgramError

{ let data = account . try_borrow_data ( ) ? ; if data . len ( ) < Self :: LEN { return Err ( ProgramError :: InvalidAccountData ) ; } if data [ 0 ] != VAULT_DISCRIMINATOR { return Err ( ProgramError :: InvalidAccountData ) ; } Ok ( bytemuck :: from_bytes ( & data [ .. Self :: LEN ] ) ) } pub fn from_account_mut ( account : & AccountInfo ) -> Result < & mut Self , ProgramError

{ let mut data = account . try_borrow_mut_data ( ) ? ; if data . len ( ) < Self :: LEN { return Err ( ProgramError :: InvalidAccountData ) ; } Ok ( bytemuck :: from_bytes_mut ( & mut data [ .. Self :: LEN ] ) ) } } Instructions Step 1: Define Account Validation Create a struct to hold validated accounts: pub struct InitializeAccounts < 'a

{ pub vault : & 'a AccountInfo , pub owner : & 'a AccountInfo , pub system_program : & 'a AccountInfo , } impl < 'a

InitializeAccounts < 'a

{ pub fn parse ( accounts : & 'a [ AccountInfo ] ) -> Result < Self , ProgramError

{ let [ vault , owner , system_program , .. ] = accounts else { return Err ( ProgramError :: NotEnoughAccountKeys ) ; } ; // Validate owner is signer if ! owner . is_signer ( ) { return Err ( ProgramError :: MissingRequiredSignature ) ; } // Validate system program if system_program . key ( ) != & pinocchio_system :: ID { return Err ( ProgramError :: IncorrectProgramId ) ; } Ok ( Self { vault , owner , system_program , } ) } } Step 2: Implement Instruction Handler 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 ] ) ? ; Account Validation Patterns Pattern 1: TryFrom Trait pub struct DepositAccounts < 'a

{ pub vault : & 'a AccountInfo , pub owner : & 'a AccountInfo , pub system_program : & 'a AccountInfo , } impl < 'a

TryFrom < & 'a [ AccountInfo ]

for DepositAccounts < 'a

{ type Error = ProgramError ; fn try_from ( accounts : & 'a [ AccountInfo ] ) -> Result < Self , Self :: Error

{ let [ vault , owner , system_program , .. ] = accounts else { return Err ( ProgramError :: NotEnoughAccountKeys ) ; } ; // Validations require! ( owner . is_signer ( ) , ProgramError :: MissingRequiredSignature ) ; require! ( vault . is_writable ( ) , ProgramError :: InvalidAccountData ) ; Ok ( Self { vault , owner , system_program } ) } } // Usage let ctx = DepositAccounts :: try_from ( accounts ) ? ; Pattern 2: Builder Pattern pub struct AccountValidator < 'a

{ account : & 'a AccountInfo , } impl < 'a

AccountValidator < 'a

{ pub fn new ( account : & 'a AccountInfo ) -> Self { Self { account } } pub fn is_signer ( self ) -> Result < Self , ProgramError

{ if ! self . account . is_signer ( ) { return Err ( ProgramError :: MissingRequiredSignature ) ; } Ok ( self ) } pub fn is_writable ( self ) -> Result < Self , ProgramError

{ if ! self . account . is_writable ( ) { return Err ( ProgramError :: InvalidAccountData ) ; } Ok ( self ) } pub fn has_owner ( self , owner : & Pubkey ) -> Result < Self , ProgramError

{ if self . account . owner ( ) != owner { return Err ( ProgramError :: IllegalOwner ) ; } Ok ( self ) } pub fn build ( self ) -> & 'a AccountInfo { self . account } } // Usage let owner = AccountValidator :: new ( & accounts [ 0 ] ) . is_signer ( ) ? . is_writable ( ) ? . build ( ) ; Pattern 3: Macro-based Validation macro_rules! require { ( $cond : expr , $err : expr ) => { if ! $cond { return Err ( $err ) ; } } ; } macro_rules! require_signer { ( $account : expr ) => { require! ( $account . is_signer ( ) , ProgramError :: MissingRequiredSignature ) } ; } macro_rules! require_writable { ( $account : expr ) => { require! ( $account . is_writable ( ) , ProgramError :: InvalidAccountData ) } ; } PDA Operations Deriving PDAs use pinocchio :: pubkey :: Pubkey ; // Find PDA with bump let ( pda , bump ) = Pubkey :: find_program_address ( & [ b"vault" , user . key ( ) . as_ref ( ) ] , program_id , ) ; // Create PDA with known bump (cheaper) let pda = Pubkey :: create_program_address ( & [ b"vault" , user . key ( ) . as_ref ( ) , & [ bump ] ] , program_id , ) ? ; PDA Signing for CPI // Single seed set let signer_seeds = & [ b"vault" , owner . as_ref ( ) , & [ bump ] ] ; Transfer { from : vault_pda , to : destination , lamports : amount , } . invoke_signed ( & [ signer_seeds ] ) ? ; // Multiple PDA signers let signer1 = & [ b"vault" , owner . as_ref ( ) , & [ bump1 ] ] ; let signer2 = & [ b"authority" , & [ bump2 ] ] ; invoke_signed ( & ix , & accounts , & [ signer1 , signer2 ] ) ? ; Data Serialization Fixed-Size with Bytemuck (Recommended)

[repr(C)]

[derive(Clone, Copy, Pod, Zeroable)]

pub struct GameState { pub discriminator : u8 , pub player : [ u8 ; 32 ] , pub score : u64 , pub level : u8 , pub _padding : [ u8 ; 6 ] , } // Zero-copy read let state : & GameState = bytemuck :: from_bytes ( & data ) ; // Zero-copy write let state : & mut GameState = bytemuck :: from_bytes_mut ( & mut data ) ; Variable-Size with Borsh use borsh :: { BorshDeserialize , BorshSerialize } ;

[derive(BorshSerialize, BorshDeserialize)]

pub struct Metadata { pub name : String , pub symbol : String , pub uri : String , } // Deserialize (allocates) let metadata = Metadata :: try_from_slice ( data ) ? ; // Serialize let mut buffer = Vec :: new ( ) ; metadata . serialize ( & mut buffer ) ? ; Manual Parsing (Maximum control) pub fn parse_u64 ( data : & [ u8 ] ) -> Result < u64 , ProgramError

{ if data . len ( ) < 8 { return Err ( ProgramError :: InvalidInstructionData ) ; } Ok ( u64 :: from_le_bytes ( data [ .. 8 ] . try_into ( ) . unwrap ( ) ) ) } pub fn parse_pubkey ( data : & [ u8 ] ) -> Result < Pubkey , ProgramError

{ if data . len ( ) < 32 { return Err ( ProgramError :: InvalidInstructionData ) ; } Ok ( Pubkey :: new_from_array ( data [ .. 32 ] . try_into ( ) . unwrap ( ) ) ) } IDL Generation with Shank Since Pinocchio doesn't auto-generate IDLs, use Shank: use shank :: { ShankAccount , ShankInstruction } ;

[derive(ShankAccount)]

pub struct Vault { pub owner : Pubkey , pub balance : u64 , }

[derive(ShankInstruction)]

pub enum ProgramInstruction {

[account(0, writable, signer, name =

"vault" )]

[account(1, signer, name =

"owner" )]

[account(2, name =

"system_program" )] Initialize ,

[account(0, writable, name =

"vault" )]

[account(1, signer, name =

"owner"
)]
Deposit
{
amount
:
u64
}
,
}
Generate IDL:
shank idl
-o
idl.json
-p
src/lib.rs
Guidelines
Always use single-byte discriminators
for instructions and accounts
Prefer bytemuck over Borsh
for fixed-size data
Use
lazy_entrypoint!
for single-instruction programs
Validate all accounts
before processing
Use
invoke_signed
for PDA-owned account operations
Add padding
to align structs to 8 bytes
Test with
solana-program-test
or Bankrun
Files in This Skill
pinocchio-development/
├── SKILL.md # This file
├── scripts/
│ ├── scaffold-program.sh # Project generator
│ └── benchmark-cu.sh # CU benchmarking
├── resources/
│ ├── account-patterns.md # Validation patterns
│ ├── cpi-reference.md # CPI quick reference
│ ├── optimization-checklist.md # Performance tips
│ └── anchor-comparison.md # Side-by-side comparison
├── examples/
│ ├── counter/ # Basic counter program
│ ├── vault/ # PDA vault with deposits
│ ├── token-operations/ # Token minting/transfers
│ └── transfer-hook/ # Token-2022 hook
├── templates/
│ └── program-template.rs # Starter template
└── docs/
├── migration-from-anchor.md # Anchor migration guide
└── edge-cases.md # Gotchas and solutions
Performance Benchmarks (2025)
Latest benchmarks demonstrate Pinocchio's efficiency:
Program
Anchor CU
Pinocchio CU
Reduction
Token Transfer
~6,000
~600-800
88-95%
Memo Program
~650
~108
83%
Counter
~800
~104
87%
Assembly implementation: 104 CU, Pinocchio: 108 CU, Basic Anchor: 649 CU
SDK Roadmap (Anza Plans)
The Anza team has announced plans for SDK v3:
Coming Improvements
Unified Base Types
Reusable types across Anchor and Pinocchio
New Serialization Library
Zero-copy, simpler enums, variable-length types
ATA Program Optimization
Pinocchio-optimized Associated Token Account
Token22 Optimization
Full Token Extensions support with minimal CU usage Integration Progress Pinocchio types are being integrated into the core Solana SDK Improved interoperability between Anchor and Pinocchio programs Notes Pinocchio is unaudited - use with caution in production Version 0.10.x is current (latest: pinocchio = "0.10" ) pinocchio-system = "0.4" and pinocchio-token = "0.4" for CPI helpers Token-2022 support via pinocchio-token is under active development For client generation, use Codama with your Shank-generated IDL Maintained by Anza (Solana Agave client developers)
返回排行榜