mcpField
P
pinocchio-development

pinocchio-development

github.com/sendaifun开发130199 次安装13 周前更新
代码仓库

关于 pinocchio-development

pinocchio-development 是面向 Solana Rust 程序开发者的 Pinocchio 指南,介绍如何用轻量、零拷贝方式编写链上程序。内容涵盖账户验证、CPI、PDA 与数据解析等实现模式。

主要功能

  1. 01零拷贝账户数据读取
  2. 02标准、延迟及无分配入口点
  3. 03账户验证与 CPI 调用模式
  4. 04PDA 派生及签名
  5. 05固定与可变长度数据解析

适用场景

  1. 01构建对计算单元敏感的 Solana 程序
  2. 02实现代币、金库或托管程序
  3. 03编写自定义账户验证与 CPI 逻辑

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/pinocchio-development确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

docs/
edge-cases.md
migration-from-anchor.md
examples/counter/
README.md
examples/token-operations/
README.md
examples/vault/
README.md
resources/
account-patterns.md
cpi-reference.md
optimization-checklist.md
scripts/
scaffold-program.sh
templates/
program-template.rs

详细介绍

Pinocchio 开发指南

使用 Pinocchio 构建超高速 Solana 程序。Pinocchio 是一个零依赖、零拷贝框架,与传统方案相比,可减少 88-95% 的计算单元消耗,并使二进制文件缩小 40%。

概述

Pinocchio 是 Anza 推出的极简 Rust 库,可在不依赖庞大的 solana-program crate 的情况下编写 Solana 程序。它将传入的交易数据视为一个字节切片,并通过零拷贝技术直接读取数据。

性能对比
指标Anchor原生(solana-program)Pinocchio
Token 转账 CU~6,000~4,500~600-800
二进制文件大小大中小(-40%)
堆分配必需必需可选
依赖项很多若干零*

*链上执行时仅需 Solana SDK 类型

何时使用 Pinocchio

适合使用 Pinocchio 的情况:

  • 构建高吞吐量程序(DEX、订单簿、游戏)
  • 计算单元成为瓶颈
  • 二进制文件大小很重要(程序部署成本)
  • 需要对内存进行最大程度的控制
  • 构建基础设施(代币、金库、托管合约)

考虑改用 Anchor 的情况:

  • 快速原型开发 / MVP
  • 团队不熟悉底层 Rust
  • 账户关系复杂
  • 需要丰富的生态工具
  • 审计时间紧迫(更了解 Anchor 的审计人员更多)

快速入门

1. 项目设置
# 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. 基本程序结构
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. 使用 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]))
    }
}

指令

第 1 步:定义账户验证

创建一个结构体来保存已验证的账户:

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,
        })
    }
}
第 2 步:实现指令处理程序
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(())
}

入口点选项

Pinocchio 提供了三个入口点宏,各有不同的权衡:

1. 标准入口点(推荐用于大多数情况)
use pinocchio::entrypoint;

entrypoint!(process_instruction);
  • 设置堆分配器
  • 配置 panic 处理程序
  • 自动反序列化账户
2. 懒加载入口点(最适合单指令程序)
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(())
}
  • 按需解析
  • 简单程序可获得最佳 CU 节省效果
  • 在 memo 程序基准测试中,CU 减少 80-87%
3. 无分配器(最大程度优化)
use pinocchio::{entrypoint, no_allocator};

no_allocator!();
entrypoint!(process_instruction);
  • 完全禁用堆
  • 不能使用 String、Vec、Box
  • 最适合固定大小的操作

CPI 模式

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]]])?;
自定义 CPI(第三方程序)
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])?;

账户验证模式

模式 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)?;
模式 2:构建器模式
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();
模式 3:基于宏的验证
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 操作

派生 PDA
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,
)?;
用于 CPI 的 PDA 签名
// 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])?;

数据序列化

使用 Bytemuck 进行定长序列化(推荐)
#[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);
使用 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)?;
手动解析(最大程度的控制)
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()))
}

使用 Shank 生成 IDL

由于 Pinocchio 不会自动生成 IDL,请使用 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 },
}

生成 IDL:

shank idl -o idl.json -p src/lib.rs

指南

  1. 指令和账户始终使用单字节判别符
  2. 对于定长数据,优先使用 bytemuck 而非 Borsh
  3. 对于单指令程序,使用 lazy_entrypoint!
  4. 处理前验证所有账户
  5. 对 PDA 所有的账户进行操作时,使用 invoke_signed
  6. 添加填充,使结构体按 8 字节对齐
  7. 使用 solana-program-test 或 Bankrun 进行测试

此技能中的文件

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

性能基准测试(2025)

最新基准测试展现了 Pinocchio 的高效性:

程序Anchor CUPinocchio CU降幅
Token 转账~6,000~600-80088-95%
Memo 程序~650~10883%
Counter~800~10487%

汇编实现:104 CU,Pinocchio:108 CU,基础 Anchor:649 CU

SDK 路线图(Anza 计划)

Anza 团队已公布 SDK v3 的计划:

即将推出的改进
  • 统一基础类型:Anchor 和 Pinocchio 之间可复用的类型
  • 全新序列化库:零拷贝、更简单的枚举和变长类型
  • ATA 程序优化:针对 Pinocchio 优化的关联代币账户
  • Token22 优化:全面支持 Token 扩展,同时最大限度减少 CU 用量
集成进度
  • Pinocchio 类型正在集成到 Solana 核心 SDK 中
  • 改进 Anchor 和 Pinocchio 程序之间的互操作性

备注

  • Pinocchio 未经审计——在生产环境中使用时请谨慎
  • 当前版本为 0.10.x(最新版本:pinocchio = "0.10")
  • CPI 辅助工具使用 pinocchio-system = "0.4" 和 pinocchio-token = "0.4"
  • pinocchio-token 对 Token-2022 的支持正在积极开发中
  • 生成客户端时,请将 Codama 与 Shank 生成的 IDL 搭配使用
  • 由 Anza(Solana Agave 客户端开发者)维护

评论

支持 Markdown