SkillAgentSearch skills...

Peephole

No description available

Install / Use

/learn @ergonia/Peephole
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

peephole

Zero-copy account parsing for Solana programs.

Why?

solana_program::entrypoint::deserialize allocates AccountInfo structs and copies account data. peephole skips that with a zero-alloc and zero-copy view over the account and instruction data.

Real-world: 51 CU oracle update, 67 CUs for 3 oracle updates, 12 CU trading contract, and many others.

Installation

[dependencies]
peephole = { version = "1.0", features = ["solana-sdk"] }

Feature flags: solana-sdk or pinocchio-sdk (exactly one required).

Basic Iteration

use peephole::account_iterator::{AccountIterator, NextHeader};

let mut iter = unsafe { AccountIterator::new_from_instruction(input) };

loop {
    match iter.next_header() {
        NextHeader::Header(cursor) => {
            // Inspect header before parsing data
            let key = &cursor.static_data.key;
            let data_len = cursor.data_len();
            // Parse the full account when ready
            let (acc, next) = unsafe { cursor.parse_data() };
            let lamports = acc.static_data.lamports;
            let data: &[u8] = acc.data();
            iter = next;
        }
        NextHeader::Dup(idx, next) => {
            // Duplicate of account `idx`—runtime only serializes full data once
            iter = next;
        }
        NextHeader::Data(instruction_data, program_id) => {
            // instruction_data: &mut [u8], program_id: &Pubkey
            break;
        }
    }
}

Typed Slurping

Cast account data directly to your struct:

#[repr(C)]
#[derive(Pod, Zeroable, Copy, Clone)]
struct TokenAccount {
    mint: [u8; 32],
    owner: [u8; 32],
    amount: u64,
}

let (account, iter) = unsafe { iter.static_slurp_typed_account::<TokenAccount>() };

let amount = account.data.amount;
account.data.amount = new_amount;

Batch Slurping

Slurp N accounts of the same type—single pointer cast:

let (accounts, iter) = unsafe { iter.static_slurp_typed_accounts::<TokenAccount, 3>() };
for acc in accounts.iter() {
    process(acc.data.amount);
}

Requirements

  • T: Pod + Zeroable (bytemuck)
  • size_of::<T>() % 8 == 0 — 8-byte alignment
  • data_len == size_of::<T>() for each account
  • No duplicate markers in the range

Safety Model

The unsafe methods assume you know the account layout. Slurp wrong type = valid pointer to garbage.

Typical pattern—verify authority first, then trust layout:

let mut iter = unsafe { AccountIterator::new_from_instruction(input) };

let (authority, iter) = unsafe { iter.known_next_full_account() };
if !(is_program_authority(&authority.static_data.key) && authority.static_data.is_signer()) {
    return Err(ProgramError::Unauthorized);
}

// Authority verified, trust the rest
let (token, iter) = unsafe { iter.static_slurp_typed_account::<TokenAccount>() };
let (vault, iter) = unsafe { iter.static_slurp_typed_account::<VaultAccount>() };

Debug builds verify invariants (counts, no unexpected dups, sizes). Release builds skip checks.

Helper Methods

NonDupAccountStatic provides convenience methods:

acc.static_data.is_signer()    // -> bool
acc.static_data.is_writable()  // -> bool
acc.static_data.is_executable() // -> bool

Buffer Layout

┌─────────────────────────────────────────────┐
│ num_accounts: u64                           │
├─────────────────────────────────────────────┤
│ Account 0                                   │
├─────────────────────────────────────────────┤
│ ...                                         │
├─────────────────────────────────────────────┤
│ instruction_data_len: u64                   │
├─────────────────────────────────────────────┤
│ instruction_data: [u8]                      │
├─────────────────────────────────────────────┤
│ program_id: Pubkey (32 bytes)               │
└─────────────────────────────────────────────┘

Each account starts with a marker byte: 0xFF = real account, 0x00-0xFE = duplicate (index of original).

Real account layout:

┌──────────────────────────────────────────────┐
│ NonDupAccountStatic (128 bytes)              │
│   is_dup, is_signer, is_writable, executable │
│   original_data_len, key, owner              │
│   lamports, data_len                         │
├──────────────────────────────────────────────┤
│ data: [u8; data_len]                         │
├──────────────────────────────────────────────┤
│ _buffer: [u8; 10240] (realloc reserve)       │
├──────────────────────────────────────────────┤
│ padding to 8-byte alignment                  │
├──────────────────────────────────────────────┤
│ rent_epoch: u64                              │
└──────────────────────────────────────────────┘

Types

pub enum NextHeader {
    Header(AccountHeaderCursor<'static>),      // Real account header
    Dup(usize, AccountIterator),               // Duplicate marker + next iterator
    Data(&'static mut [u8], &'static Pubkey),  // (instruction_data, program_id)
}

pub struct AccountHeaderCursor<'a> {
    pub static_data: &'a NonDupAccountStatic,
    // ... remaining_accounts_after (internal)
}

pub struct NonDupAccount<'a> {
    pub static_data: &'a NonDupAccountStatic,
    pub all_data: &'a mut [u8],
    pub rent_epoch: &'a u64,
}

pub struct TypedNonDupAccount<T: Pod + Zeroable> {
    pub static_data: NonDupAccountStatic,
    pub data: T,
    // ... 10KB buffer, rent_epoch
}

pubkey_byte_map

O(1) pubkey lookup for known key sets. Indexes by first byte, so all keys must have unique first bytes.

use peephole::pubkey_byte_map::{pubkey_byte_map, PubkeyMap};

const AUTHORITIES: PubkeyMap = pubkey_byte_map(&[ADMIN_KEY, OPERATOR_KEY]);

if !AUTHORITIES.contains(&signer_key) {
    return Err(Unauthorized);
}

assume!

debug_assert! in debug builds, unreachable_unchecked in release. Use when you know a condition is true but want debug-time verification:

use peephole::assume;

unsafe {
    assume!(account_count > 0, "expected at least one account");
}

Related Skills

View on GitHub
GitHub Stars52
CategoryDevelopment
Updated21d ago
Forks7

Languages

Rust

Security Score

90/100

Audited on Mar 13, 2026

No findings