Thanks to visit codestin.com
Credit goes to github.com

Skip to content

anza-xyz/pinocchio

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

pinocchio

Limestone

Create Solana programs with no external dependencies attached.

I've got no dependencies
To hold me down
To make me fret
Or make me frown
I had dependencies
But now I'm free
There are no dependencies on me

Overview

Pinocchio is a no external dependencies library to create Solana programs in Rust. The only dependencies are types from the Solana SDK specifically designed for on-chain programs. This mitigates dependency issues and offers an efficient zero-copy library to write programs, optimized in terms of both compute units consumption and binary size.

Features

  • no_std crate
  • only dependencies to Solana SDK types
  • efficient program_entrypoint! macro with no copies or allocations
  • lightweight lazy_program_entrypoint providing more control over how the input is parsed

Getting started

From your project folder:

cargo add pinocchio

This will add pinocchio as a dependency to your project.

Defining the program entrypoint

A Solana program needs to define an entrypoint, which will be called by the runtime to begin the program execution. The entrypoint! macro emits the common boilerplate to set up the program entrypoint. The macro will also set up global allocator and custom panic hook using the default_allocator! and default_panic_handler! macros.

The entrypoint! is a convenience macro that invokes three other macros to set all components required for a program execution:

When all dependencies are no_std, you should use nostd_panic_handler! instead of default_panic_handler! to declare a rust runtime panic handler. There's no need to do this when any dependency is std since rust compiler will emit a panic handler.

To use the entrypoint! macro, use the following in your entrypoint definition:

use pinocchio::{
  AccountView,
  Address,
  entrypoint,
  ProgramResult
};
use solana_program_log::log;

entrypoint!(process_instruction);

pub fn process_instruction(
  program_id: &Address,
  accounts: &[AccountView],
  instruction_data: &[u8],
) -> ProgramResult {
  log("Hello from my pinocchio program!");
  Ok(())
}

The information from the input is parsed into their own entities:

  • program_id: the ID of the program being called
  • accounts: the accounts received
  • instruction_data: data for the instruction

pinocchio also offers variations of the program entrypoint (lazy_program_entrypoint) and global allocator (no_allocator). In order to use these, the program needs to specify the program entrypoint, global allocator and panic handler individually. The entrypoint! macro is equivalent to writing:

program_entrypoint!(process_instruction);
default_allocator!();
default_panic_handler!();

Any of these macros can be replaced by alternative implementations.

πŸ“Œ Custom entrypoints with process_entrypoint

For programs that need maximum control over the entrypoint, pinocchio exposes the process_entrypoint function. This function is the same deserialization logic used internally by the program_entrypoint! macro, exposed as a public API and can be called directly from a custom entrypoint, allowing you to implement fast-path optimizations or custom pre-processing logic before falling back to standard input parsing.

To use process_entrypoint in a custom entrypoint:

use pinocchio::{
  AccountView,
  Address,
  default_panic_handler,
  entrypoint::process_entrypoint,
  MAX_TX_ACCOUNTS,
  no_allocator,
  ProgramResult,
};
use solana_program_log::log;

no_allocator!();
default_panic_handler!();

#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
    // Fast path: check the number of accounts
    let num_accounts = unsafe { *(input as *const u64) };
    if num_accounts == 0 {
        log("Fast path - no accounts!");
        return 0;
    }

    // Standard path: delegate to `process_entrypoint`
    unsafe { process_entrypoint::<MAX_TX_ACCOUNTS>(input, process_instruction) }
}

pub fn process_instruction(
  program_id: &Address,
  accounts: &[AccountView],
  instruction_data: &[u8],
) -> ProgramResult {
  log("Standard path");
  Ok(())
}

πŸ“Œ lazy_program_entrypoint!

The entrypoint! macro looks similar to the "standard" one found in solana-program-entrypoint. It parses the whole input and provides the program_id, accounts and instruction_data separately. This consumes compute units before the program begins its execution. In some cases, it is beneficial for a program to have more control when the input parsing is happening, even whether the parsing is needed or not β€” this is the purpose of the lazy_program_entrypoint! macro. This macro only wraps the program input and provides methods to parse the input on demand.

The lazy_entrypoint is suitable for programs that have a single or very few instructions, since it requires the program to handle the parsing, which can become complex as the number of instructions increases. For larger programs, the program_entrypoint! will likely be easier and more efficient to use.

To use the lazy_program_entrypoint! macro, use the following in your entrypoint definition:

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(())
}

The InstructionContext provides on-demand access to the information of the input:

  • remaining(): number of remaining accounts to be parsed
  • next_account(): parsers the next available account (can be used as many times as accounts remaining)
  • instruction_data(): parsers the instruction data
  • program_id(): parsers the program id

⚠️ Note: The lazy_program_entrypoint! does not set up a global allocator nor a panic handler. A program should explicitly use one of the provided macros to set them up or include its own implementation.

πŸ“Œ no_allocator!

When writing programs, it can be useful to make sure the program does not attempt to make any allocations. For this cases, pinocchio includes a no_allocator! macro that sets a global allocator just panics at any attempt to allocate memory.

To use the no_allocator! macro, use the following in your entrypoint definition:

use pinocchio::{
  AccountView,
  default_panic_handler,
  no_allocator,
  program_entrypoint,
  ProgramResult,
  Address
};

program_entrypoint!(process_instruction);
default_panic_handler!();
no_allocator!();

pub fn process_instruction(
  program_id: &Address,
  accounts: &[AccountView],
  instruction_data: &[u8],
) -> ProgramResult {
  Ok(())
}

⚠️ Note: The no_allocator! macro can also be used in combination with the lazy_program_entrypoint!.

Since the no_allocator! macro does not allocate memory, the 32kb memory region reserved for the heap remains unused. To take advantage of this, the no_allocator! macro emits an allocate_unchecked helper function that allows you to manually reserve memory for a type at compile time.

// static allocation:
//    - 0 is the offset when the type will be allocated
//    - `allocate_unchecked` returns a mutable reference to the allocated type
let lamports = allocate_unchecked::<u64>(0);
*lamports = 1_000_000_000;

Note that it is the developer's responsibility to ensure that types do not overlap in memory β€” the offset + <size of type> of different types must not overlap.

πŸ“Œ nostd_panic_handler!

When writing no_std programs, it is necessary to declare a panic handler using the nostd_panic_handler! macro. This macro sets up a default panic handler that logs the location (file, line and column) where the panic occurred and then calls the abort() syscall.

⚠️ Note: The default_panic_handler! macro only works in an std context.

Crate features

alloc

The alloc feature is enabled by default and it uses the alloc crate. This provides access to dynamic memory allocation in combination with the default_allocator, e.g., required to use String and Vec in a program. Helpers that need to allocate memory, such as fetching SlotHashes sysvar data, are also available.

When no allocation is needed or desired, the feature can be disabled:

pinocchio = { version = "0.10.0", default-features = false }

⚠️ Note: The default_allocator macro is not available when disabling the alloc feature.

copy

The copy feature enables the derivation of the Copy trait for types. It also enables the copy feature on the solana-account-view and solana-address re-exports.

cpi

The cpi feature enables the cross-program invocation helpers, as well as types to define instructions and signer information.

pinocchio = { version = "0.10.0", features = ["cpi"] }

Advanced entrypoint configuration

The components emitted by the entrypoint macros β€” program entrypoint, global allocator and default panic handler β€” can only be defined once globally. If the program crate is also intended to be used as a library, it is common practice to define a Cargo feature in your program crate to conditionally enable the module that includes the entrypoint! macro invocation. The convention is to name the feature bpf-entrypoint.

#[cfg(feature = "bpf-entrypoint")]
mod entrypoint {
  use pinocchio::{
    AccountView,
    entrypoint,
    ProgramResult,
    Address
  };

  entrypoint!(process_instruction);

  pub fn process_instruction(
    program_id: &Address,
    accounts: &[AccountView],
    instruction_data: &[u8],
  ) -> ProgramResult {
    Ok(())
  }
}

When building the program binary, you must enable the bpf-entrypoint feature:

cargo build-sbf --features bpf-entrypoint

Upstream BPF compatibility

Pinocchio is compatible with upstream BPF target (target_arch = bpf). When using syscalls (e.g., cross-program invocations), it is necessary to explicitly enable static syscalls in your program's Cargo.toml:

[dependencies]
# Enable static syscalls for BPF target
solana-define-syscall = { version = "4.0.1", features = ["unstable-static-syscalls"] }

When compiling your program with the upstream BPF target, the std library is not available. Therefore, the program crate must include the #![no_std] crate-level attribute and use the nostd_panic_handler! macro. An allocator may be used as long as alloc is used.

License

The code is licensed under the Apache License Version 2.0