pinocchio

repository·main·Indexed 21 days ago

https://github.com/anza-xyz/pinocchio

A zero-dependency, high-efficiency Rust library for creating Solana programs, optimized for minimal compute unit consumption and small binary sizes. It provides no_std crates for performing Cross-Program Invocations (CPIs) to the System program, SPL Memo, SPL Token, SPL Token-2022, and SPL Associated Token Account programs, including support for efficient instruction batching.

Tokens
41K
Snippets
129
Records
155
Agent score
74%

What's inside pinocchio

  1. Overview of pinocchio-token

    main

    The pinocchio-token crate provides pinocchio helpers to perform Cross-Program Invocations (CPIs) for SPL Token instructions.

    Each instruction is represented by a struct containing the necessary accounts and parameters. Once configured, you can execute the instruction using .invoke() or .invoke_signed().

    Key Characteristics:

    • It is a no_std crate.
    • The API is subject to change.
    • It simplifies interacting with SPL Token programs by providing structured instruction builders.
  2. Perform SPL Token-2022 CPIs with pinocchio-token-2022

    main

    The pinocchio-token-2022 crate provides no_std helpers for performing Cross-Program Invocations (CPIs) with the SPL Token-2022 program.

    Each Token-2022 instruction is represented by a struct. To perform a CPI, you instantiate the instruction struct using its .new() method and then call .invoke() or .invoke_signed() to execute it.

    use pinocchio_token_2022::instructions::Transfer;
    
    // Perform a transfer of tokens
    Transfer::new(
        from,        // from account
        to,          // to account
        authority,   // authority
        10,          // amount
    ).invoke()?;
  3. Perform System Program CPIs with pinocchio-system

    main

    The pinocchio-system crate provides helpers to perform Cross-Program Invocations (CPIs) for System program instructions. It is a no_std crate designed to work with the pinocchio ecosystem.

    To perform a CPI, you define an instruction using a specific struct (e.g., CreateAccount or Transfer) and populate its fields. Once configured, you call .invoke() or .invoke_signed() to execute the instruction.

    // Example pattern for using pinocchio-system instructions
    InstructionStruct {
        field1: account1,
        field2: account2,
        // ...
    }.invoke()?;
  4. How the standard entrypoint! macro works

    main

    The entrypoint! macro is a convenience macro that sets up the three essential components required for a Solana program execution:

    1. program_entrypoint!: Declares the program entrypoint.
    2. default_allocator!: Declares the default (bump) global allocator.
    3. default_panic_handler!: Declares the default panic "hook" (works with std).

    If your program is strictly no_std, you should replace default_panic_handler! with nostd_panic_handler! to define a Rust runtime panic handler.

    When using entrypoint!, the instruction input is automatically split into:

    • program_id: The address of the program being called.
    • accounts: The accounts received by the instruction.
    • instruction_data: The data for the instruction.
    use pinocchio::{
      AccountView,
      Address,
      entrypoint,
      ProgramResult
    };
    use solana_program_log::log;
    
    entrypoint!(process_instruction);
    
    pub fn process_instruction(
      program_id: &Address,
      accounts: &mut [AccountView],
      instruction_data: &[u8],
    ) -> ProgramResult {
      log("Hello from my pinocchio program!");
      Ok(())
    }
  5. Invoke Token-2022 instructions on custom programs

    main

    The crate provides different invocation methods depending on whether you need to validate the target program address:

    • Verified (Standard Token-2022): Use invoke_with_program and invoke_signed_with_program. These accept an Address parameter and validate that it matches the official Token-2022 program address.
    • Unverified (Custom Token Programs): Use invoke_with_unverified_program and invoke_signed_with_unverified_program. These accept an Address parameter but skip the validation check, allowing you to interact with compatible custom token programs.
  6. How the Batch instruction works

    main

    The Batch instruction (discriminator 255) allows executing multiple Token instructions in a single CPI invocation.

    Benefits:

    • Efficiency: The base CPI invoke units (currently 1000 CUs) are consumed only once for the entire batch, rather than for every individual instruction. This significantly reduces the Compute Units (CUs) required for complex sequences of token operations.

    Workflow:

    1. Calculate the required total ACCOUNTS_LEN and DATA_LEN for all instructions in the batch.
    2. Initialize uninitialized arrays for data and accounts using MaybeUninit.
    3. Create a Batch instance using Batch::new().
    4. Add instructions to the batch using the .into_batch(&mut batch) method instead of calling .invoke() directly.
    5. Call batch.invoke() to execute the entire sequence.
    use {
      core::mem::MaybeUninit,
      pinocchio_token::instructions::{
        Batch, InitializeAccount, InitializeMint, IntoBatch, MintTo,
      },
    };
    
    // Determine the maximum number of accounts and data length required
    // for the batch instruction.
    const ACCOUNTS_LEN: usize = InitializeMint::ACCOUNTS_LEN
      + InitializeAccount::ACCOUNTS_LEN
      + MintTo::MAX_ACCOUNTS_LEN;
    
    const DATA_LEN: usize = Batch::header_data_len(3)
      + InitializeMint::MAX_DATA_LEN
      + InitializeAccount::DATA_LEN
      + MintTo::DATA_LEN;
    
    // Create uninitialized arrays for the batch instruction.
    let mut data = [const { MaybeUninit::uninit() }; DATA_LEN];
    let mut instruction_accounts = [const { MaybeUninit::uninit() }; ACCOUNTS_LEN];
    let mut accounts = [const { MaybeUninit::uninit() }; ACCOUNTS_LEN];
    
    // Create a new batch instruction with the uninitialized arrays.
    let mut batch = Batch::new(&mut data, &mut instruction_accounts, &mut accounts)?;
    
    InitializeMint::new(
      mint_account,
      rent_sysvar,
      9,
      authority,
      Some(authority),
    )
    .into_batch(&mut batch)?;
    
    InitializeAccount::new(
      token_account,
      mint_account,
      owner,
      rent_sysvar
    ).into_batch(&mut batch)?;
    
    MintTo::new(
      mint_account,
      token_account,
      authority_account,
      1000
    ).into_batch(&mut batch)?;
    
    // Invoke the batch instruction to execute all instructions in a single CPI.
    batch.invoke()?;
  7. Use lazy_program_entrypoint! for on-demand parsing

    main

    The lazy_program_entrypoint! macro is designed for programs where you want to control exactly when input parsing occurs to save compute units. Instead of parsing all inputs upfront, it provides an InstructionContext that allows you to parse data on demand.

    This is ideal for programs with very few instructions. For larger programs, the standard program_entrypoint! is generally easier and more efficient.

    Note: lazy_program_entrypoint! does not set up a global allocator or a panic handler. You must explicitly include default_allocator!, no_allocator!, or your own implementation, and a panic handler.

    use pinocchio::{
      default_allocator,
      default_panic_handler,
      entrypoint::InstructionContext,
      lazy_program_entrypoint,
      ProgramResult
    };
    
    lazy_program_entrypoint!(process_instruction);
    default_allocator!();
    default_panic_handler!();
    
    pub fn process_instruction(
      mut context: InstructionContext
    ) -> ProgramResult {
        Ok(())
    }
  8. Perform CPIs for SPL Associated Token Account instructions

    main

    This crate provides helpers to perform cross-program invocations (CPIs) for the SPL Associated Token Account program.

    Each instruction is represented by a struct containing the necessary accounts and parameters. To execute the instruction, you call .invoke() or .invoke_signed() on the instruction struct once all fields are populated.

    Note: This is a no_std crate.

  9. Use the Batch instruction for efficient Token-2022 CPIs

    main

    The Batch instruction (discriminator 255) allows executing multiple Token-2022 instructions in a single CPI invocation. This is significantly more compute-unit (CU) efficient because the base CPI invoke units (currently 1000 CUs) are consumed only once for the entire batch, rather than once per instruction.

    To use batching:

    1. Calculate the required ACCOUNTS_LEN and DATA_LEN by summing the requirements of all intended instructions.
    2. Create uninitialized arrays for data, instruction_accounts, and accounts using MaybeUninit.
    3. Initialize a Batch instance using Batch::new().
    4. Convert individual instructions into the batch using the .into_batch(&mut batch) method.
    5. Call batch.invoke() to execute the entire sequence.
    use {
      core::mem::MaybeUninit,
      pinocchio_token_2022::instructions::{
        Batch, InitializeAccount, InitializeMint, IntoBatch, MintTo,
      },
    };
    
    // 1. Determine maximum lengths
    const ACCOUNTS_LEN: usize = InitializeMint::ACCOUNTS_LEN
      + InitializeAccount::ACCOUNTS_LEN
      + MintTo::MAX_ACCOUNTS_LEN;
    
    const DATA_LEN: usize = Batch::header_data_len(3)
      + InitializeMint::MAX_DATA_LEN
      + InitializeAccount::DATA_LEN
      + MintTo::DATA_LEN;
    
    // 2. Create uninitialized arrays
    let mut data = [const { MaybeUninit::uninit() }; DATA_LEN];
    let mut instruction_accounts = [const { MaybeUninit::uninit() }; ACCOUNTS_LEN];
    let mut accounts = [const { MaybeUninit::uninit() }; ACCOUNTS_LEN];
    
    // 3. Create the batch
    let mut batch = Batch::new(&mut data, &mut instruction_accounts, &mut accounts)?;
    
    // 4. Add instructions to the batch
    InitializeMint::new(
      mint_account,
      rent_sysvar,
      9,
      &authority,
      Some(&authority),
    ).into_batch(&mut batch)?;
    
    InitializeAccount::new(
      token_account,
      mint_account,
      owner,
      rent_sysvar
    ).into_batch(&mut batch)?;
    
    MintTo::new(
      mint_account,
      token_account,
      authority_account,
      1000
    ).into_batch(&mut batch)?;
    
    // 5. Invoke the batch
    batch.invoke()?;
  10. Configure the program entrypoint for library compatibility

    main

    Because the entrypoint macro, global allocator, and panic handler can only be defined once globally, you should wrap your entrypoint logic in a conditional module if your crate is intended to be used as both a program and a library.

    The convention is to use a Cargo feature named bpf-entrypoint to wrap the entrypoint! macro and the process_instruction function.

    When building the final program binary, you must explicitly enable this feature using the --features flag.

    #[cfg(feature = "bpf-entrypoint")]
    mod entrypoint {
      use pinocchio::{
        AccountView,
        Address,
        entrypoint,
        ProgramResult
      };
    
      entrypoint!(process_instruction);
    
      pub fn process_instruction(
        program_id: &Address,
        accounts: &mut [AccountView],
        instruction_data: &[u8],
      ) -> ProgramResult {
        Ok(())
      }
    }
    
    # To build the binary:
    # cargo build-sbf --features bpf-entrypoint