Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 42 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
<img width="400" alt="Limestone" src="https://github.com/user-attachments/assets/3a1894b4-403f-4c35-90aa-548e7672fe90" />
</p>
<p align="center">
Create Solana programs with no dependencies attached.
Create Solana programs with no dependencies<sup>*</sup> attached.
</p>

<p align="center">
<a href="https://github.com/anza-xyz/pinocchio/actions/workflows/main.yml"><img src="https://img.shields.io/github/actions/workflow/status/anza-xyz/pinocchio/main.yml?logo=GitHub" /></a>
<a href="https://crates.io/crates/pinocchio"><img src="https://img.shields.io/crates/v/pinocchio?logo=rust" /></a>
<a href="https://docs.rs/pinocchio"><img src="https://img.shields.io/docsrs/pinocchio?logo=docsdotrs" /></a>
</p>

<p align="right">
Expand All @@ -23,25 +24,20 @@
<i>There are no dependencies on me</i>
</p>

## Overview

Pinocchio is a zero-dependency library to create Solana programs in Rust. It takes advantage of the way SVM loaders serialize the program input parameters into a byte array that is then passed to the program's entrypoint to define zero-copy types to read the input. Since the communication between a program and SVM loader &mdash; either at the first time the program is called or when one program invokes the instructions of another program &mdash; is done via a byte array, a program can define its own types. This completely eliminates the dependency on the `solana-program` crate, which in turn mitigates dependency issues by having a crate specifically designed to create on-chain programs.
<p style="font-size: 12px; color: #999999">
<strong><sup>*</sup></strong> It includes only dependencies to Solana SDK types &mdash; i.e., no <i>external</i> dependencies.
</p>

As a result, Pinocchio can be used as a replacement for [`solana-program`](https://crates.io/crates/solana-program) to write on-chain programs, which are optimized in terms of both compute units consumption and binary size.
## Overview

The library defines:
* program entrypoint
* core data types
* logging macros
* `syscall` functions
* access to system accounts (`sysvars`)
* cross-program invocation
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

* Zero dependencies and `no_std` crate
* Efficient `entrypoint!` macro – no copies or allocations
* Improved CU consumption of cross-program invocations
* `no_std` crate
* no-external dependencies
* efficient `program_entrypoint!` macro – no copies or allocations
* lightweight `lazy_program_entrypoint` providing more control over how the input is parsed

## Getting started

Expand All @@ -57,21 +53,21 @@ This will add `pinocchio` as a dependency to your project.

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](https://doc.rust-lang.org/stable/core/alloc/trait.GlobalAlloc.html) and [custom panic hook](https://github.com/anza-xyz/rust/blob/2830febbc59d44bdd7ad2c3b81731f1d08b96eba/library/std/src/sys/pal/sbf/mod.rs#L49) using the [default_allocator!](https://docs.rs/pinocchio/latest/pinocchio/macro.default_allocator.html) and [default_panic_handler!](https://docs.rs/pinocchio/latest/pinocchio/macro.default_panic_handler.html) macros.

The [`entrypoint!`](https://docs.rs/pinocchio/latest/pinocchio/macro.entrypoint.html) is a convenience macro that invokes three other macros to set all symbols required for a program execution:
The [`entrypoint!`](https://docs.rs/pinocchio/latest/pinocchio/macro.entrypoint.html) is a convenience macro that invokes three other macros to set all components required for a program execution:

* [`program_entrypoint!`](https://docs.rs/pinocchio/latest/pinocchio/macro.program_entrypoint.html): declares the program entrypoint
* [`default_allocator!`](https://docs.rs/pinocchio/latest/pinocchio/macro.default_allocator.html): declares the default (bump) global allocator
* [`default_panic_handler!`](https://docs.rs/pinocchio/latest/pinocchio/macro.default_panic_handler.html): declares the default panic handler

If all dependencies are `no_std`, you should append [`nostd_panic_handler!`](https://docs.rs/pinocchio/latest/pinocchio/macro.nostd_panic_handler.html) to declare a rust runtime panic handler. There's no need to do this if any dependency is `std` since rust compiler will emit std panic handler.
When all dependencies are `no_std`, you should use [`nostd_panic_handler!`](https://docs.rs/pinocchio/latest/pinocchio/macro.nostd_panic_handler.html) 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:
```rust
use pinocchio::{
account::AccountView,
Address,
entrypoint,
ProgramResult,
Address
ProgramResult
};

entrypoint!(process_instruction);
Expand All @@ -91,17 +87,17 @@ The information from the input is parsed into their own entities:
* `accounts`: the accounts received
* `instruction_data`: data for the instruction

`pinocchio` also offers variations of the program entrypoint (`lazy_program_allocator`) 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:
`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:
```rust
program_entrypoint!(process_instruction);
default_allocator!();
default_panic_handler!();
```
Any of these macros can be replaced by other implementations and `pinocchio` offers a couple of variants for this.
Any of these macros can be replaced by alternative implementations.

📌 [`lazy_program_entrypoint!`](https://docs.rs/pinocchio/latest/pinocchio/macro.lazy_program_entrypoint.html)

The `entrypoint!` macro looks similar to the "standard" one found in `solana-program`. 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 &mdash; this is the purpose of the [`lazy_program_entrypoint!`](https://docs.rs/pinocchio/latest/pinocchio/macro.lazy_program_entrypoint.html) macro. This macro only wraps the program input and provides methods to parse the input on demand.
The `entrypoint!` macro looks similar to the "standard" one found in [`solana-program-entrypoint`](https://docs.rs/solana-program-entrypoint/latest/solana_program_entrypoint/macro.entrypoint.html). 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 &mdash; this is the purpose of the [`lazy_program_entrypoint!`](https://docs.rs/pinocchio/latest/pinocchio/macro.lazy_program_entrypoint.html) 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!`](https://docs.rs/pinocchio/latest/pinocchio/macro.program_entrypoint.html) will likely be easier and more efficient to use.

Expand Down Expand Up @@ -166,7 +162,21 @@ pub fn process_instruction(
> ⚠️ **Note:**
> The `no_allocator!` macro can also be used in combination with the `lazy_program_entrypoint!`.

## Crate feature: `alloc`
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.

```rust
/// 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 &mdash; the `offset + <size of type>` of different types must not overlap.

## Crate features

### `alloc`

The `alloc` feature is enabled by default and it uses the [`alloc`](https://doc.rust-lang.org/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.

Expand All @@ -179,9 +189,17 @@ pinocchio = { version = "0.10.0", default-features = false }
> ⚠️ **Note:**
> The `default_allocator` macro is not available when disabling the `alloc` feature.

### `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"] }
```

## Advance entrypoint configuration

The symbols emitted by the entrypoint macros &mdash; program entrypoint, global allocator and default panic handler &mdash; 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](https://doc.rust-lang.org/cargo/reference/features.html) in your program crate to conditionally enable the module that includes the `entrypoint!` macro invocation. The convention is to name the feature `bpf-entrypoint`.
The components emitted by the entrypoint macros &mdash; program entrypoint, global allocator and default panic handler &mdash; 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](https://doc.rust-lang.org/cargo/reference/features.html) in your program crate to conditionally enable the module that includes the `entrypoint!` macro invocation. The convention is to name the feature `bpf-entrypoint`.

```rust
#[cfg(feature = "bpf-entrypoint")]
Expand Down
6 changes: 5 additions & 1 deletion sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ readme = "./README.md"
repository = { workspace = true }
rust-version = { workspace = true }

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
all-features = true
rustdoc-args = ["--cfg=docsrs"]

[lib]
crate-type = ["rlib"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(target_os, values("solana"))',
'cfg(target_feature, values("static-syscalls"))',
] }

[features]
Expand Down
90 changes: 57 additions & 33 deletions sdk/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
//! # Pinocchio
//! # A library to create Solana programs in Rust
//!
//! Pinocchio is a "no-external" dependencies library to create Solana programs
//! in Rust, which means that the only dependencies are from the Solana SDK. This
//! reduces the chance of dependency conflicts when compiling the program.
//!
//! It takes advantage of the way SVM loaders serialize the program input
//! parameters into a byte array that is then passed to the program's entrypoint
//! to use zero-copy types to read the input - these types are defined in an efficient
//! way taking into consideration that they will be used in on-chain programs.
//!
//! It is intended to be used by on-chain programs only; for off-chain programs,
//! use instead [`solana-sdk`] crates.
//!
//! [`solana-sdk`]: https://docs.rs/solana-sdk/latest/solana_sdk/
//! 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.
//!
//! ## Defining the program entrypoint
//!
Expand All @@ -23,17 +15,23 @@
//! and [panic handler](https://doc.rust-lang.org/nomicon/panic-handler.html) using
//! the [`default_allocator!`] and [`default_panic_handler!`] macros.
//!
//! The [`entrypoint!`] is a convenience macro that invokes three other macros to set
//! all symbols required for a program execution:
//! The [`entrypoint!`](https://docs.rs/pinocchio/latest/pinocchio/macro.entrypoint.html)
//! is a convenience macro that invokes three other macros to set all components
//! required for a program execution:
//!
//! * [`program_entrypoint!`]: declares the program entrypoint
//! * [`default_allocator!`]: declares the default (bump) global allocator
//! * [`default_panic_handler!`]: declares the default panic handler
//!
//! When all dependencies are `no_std`, you should use [`nostd_panic_handler!`](https://docs.rs/pinocchio/latest/pinocchio/macro.nostd_panic_handler.html)
//! 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:
//! ```ignore
//! use pinocchio::{
//! AccountView,
//! account::AccountView,
//! Address,
//! entrypoint,
//! ProgramResult
Expand All @@ -56,18 +54,16 @@
//! * `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:
//! `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:
//! ```ignore
//! program_entrypoint!(process_instruction);
//! default_allocator!();
//! default_panic_handler!();
//! ```
//! Any of these macros can be replaced by other implementations and Pinocchio
//! offers a couple of variants for this.
//! Any of these macros can be replaced by alternative implementations.
//!
//! ### [`lazy_program_entrypoint!`]
//!
Expand Down Expand Up @@ -160,16 +156,44 @@
//! 💡 The [`no_allocator!`] macro can also be used in combination with the
//! [`lazy_program_entrypoint!`].
//!
//! ## `std` crate feature
//! 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.
//! ```ignore
//! /// 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.
//!
//! ## Crate features
//!
//! ### `alloc`
//!
//! The `alloc` feature is enabled by default and it uses the [`alloc`](https://doc.rust-lang.org/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 [`crate::sysvars::slot_hashes::SlotHashes::fetch`]
//! sysvar data, are also available.
//!
//! When no allocation is needed or desired, the feature can be disabled:
//!
//! ```ignore
//! pinocchio = { version = "0.10.0", default-features = false }
//! ```
//!
//! ### `cpi`
//!
//! The `cpi` feature enables the cross-program invocation helpers, as well as types
//! to define instructions and signer information.
//!
//! By default, Pinocchio is a `no_std` crate. This means that it does not use any
//! code from the standard (`std`) library. While this does not affect how Pinocchio
//! is used, there is a one particular apparent difference. Helpers that need to
//! allocate memory, such as fetching `SlotHashes` sysvar, are not available. To
//! enable these helpers, the `alloc` feature must be enabled when adding Pinocchio
//! as a dependency:
//! ```ignore
//! pinocchio = { version = "0.10.0", features = ["alloc"] }
//! pinocchio = { version = "0.10.0", features = ["cpi"] }
//! ```
//!
//! ## Advanced entrypoint configuration
Expand All @@ -185,7 +209,7 @@
//! #[cfg(feature = "bpf-entrypoint")]
//! mod entrypoint {
//! use pinocchio::{
//! AccountView,
//! account::AccountView,
//! Address,
//! entrypoint,
//! ProgramResult
Expand Down