mcpField
P
pinocchio-development

pinocchio-development

github.com/sendaifun開発130199 回インストール13 週間前に更新
リポジトリ

pinocchio-development について

pinocchio-development は、Pinocchio を使って Solana のオンチェーンプログラムを Rust で開発する人向けのガイドです。ゼロコピーのデータアクセスに加え、アカウント検証、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 は、重量級の solana-program クレートを使わずに Solana プログラムを記述できる、Anza の最小構成の Rust ライブラリです。トランザクションから受け取るデータを単一のバイトスライスとして扱い、ゼロコピー技術によってその場で読み取ります。

パフォーマンス比較
指標Anchorネイティブ(solana-program)Pinocchio
トークン転送の 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 には、トレードオフの異なる3つのエントリーポイントマクロがあります。

1. 標準エントリーポイント(ほとんどの場合に推奨)
use pinocchio::entrypoint;

entrypoint!(process_instruction);
  • ヒープアロケーターを設定します
  • パニックハンドラーを構成します
  • アカウントを自動的にデシリアライズします
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 削減効果を得られます
  • メモプログラムのベンチマークで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 トレイト
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. インストラクションとアカウントには、必ず1バイトの識別子を使用してください
  2. 固定サイズデータには、Borsh より bytemuck を優先してください
  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削減率
トークン転送~6,000~600-80088-95%
メモプログラム~650~10883%
カウンター~800~10487%

アセンブリ実装:104 CU、Pinocchio:108 CU、基本的な Anchor:649 CU

SDK のロードマップ(Anza の計画)

Anza チームは SDK v3 の計画を発表しました。

今後の改善
  • 統一された基本型:Anchor と Pinocchio で再利用できる型
  • 新しいシリアライズライブラリ:ゼロコピー、よりシンプルな列挙型、可変長型
  • ATA プログラムの最適化:Pinocchio に最適化された Associated Token Account
  • Token22 の最適化:CU 使用量を最小限に抑えた Token Extensions の完全サポート
統合の進捗
  • 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 のサポートは、現在活発に開発されています
  • クライアントを生成するには、Shank で生成した IDL とともに Codama を使用してください
  • Anza(Solana Agave クライアントの開発者)がメンテナンスしています

コメント