本文介绍了 Pinocchio,一个用于在 Rust 中创建 Solana 程序的零依赖库。
<h1 align="center"> <code>pinocchio</code> </h1> <p align="center"> <img width="400" alt="Limestone" src="https://github.com/user-attachments/assets/3a1894b4-403f-4c35-90aa-548e7672fe90" /> </p> <p align="center"> 创建没有附加依赖的 Solana 程序。 </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> </p>
<p align="right"> 我没有依赖<br /> 阻碍我<br /> 让我烦恼<br /> 或让我皱眉<br /> 我曾经有依赖<br /> 但现在我自由了<br /> 我没有任何依赖 </p>
Pinocchio 是一个零依赖库,用于在 Rust 中创建 Solana 程序。它利用了 SVM 加载器将程序输入参数序列化为字节数组的方式,然后将其传递给程序的入口点,以定义零拷贝类型来读取输入。由于程序和 SVM 加载器之间的通信——无论是在第一次调用程序时,还是当一个程序调用另一个程序的指令时——都是通过字节数组完成的,因此程序可以定义自己的类型。这完全消除了对 solana-program
crate 的依赖,从而通过专门设计用于创建链上程序的 crate 来缓解依赖问题。
因此,Pinocchio 可以用作 solana-program
的替代品来编写链上程序,从而在计算单元消耗和二进制文件大小方面进行优化。
该库定义了:
syscall
函数sysvars
)no_std
crateentrypoint!
宏 – 无需拷贝或分配从你的项目文件夹中:
cargo add pinocchio
这会将 pinocchio
作为依赖项添加到你的项目中。
Solana 程序需要定义一个入口点,该入口点将由运行时调用以开始程序执行。entrypoint!
宏会发出常见的样板代码来设置程序入口点。该宏还将使用 default_allocator! 和 default_panic_handler! 宏设置 全局分配器 和 自定义 panic hook。
entrypoint!
是一个便捷宏,它调用其他三个宏来设置程序执行所需的所有符号:
program_entrypoint!
:声明程序入口点default_allocator!
:声明默认的(bump)全局分配器default_panic_handler!
:声明默认的 panic handler如果所有依赖项都是 no_std
,则应追加 nostd_panic_handler!
以声明 rust 运行时 panic handler。如果任何依赖项是 std
,则无需执行此操作,因为 rust 编译器将发出 std panic handler。
要使用 entrypoint!
宏,请在你的入口点定义中使用以下内容:
use pinocchio::{
account_info::AccountInfo,
entrypoint,
msg,
ProgramResult,
pubkey::Pubkey
};
entrypoint!(process_instruction);
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
msg!("Hello from my program!"); // 来自我程序的问候!
Ok(())
}
来自输入的信息被解析为它们自己的实体:
program_id
:被调用程序的 ID
accounts
:接收到的账户instruction_data
:指令的数据pinocchio
还提供了程序入口点 (lazy_program_allocator
) 和全局分配器 (no_allocator
) 的变体。为了使用它们,程序需要单独指定程序入口点、全局分配器和 panic handler。entrypoint!
宏等效于编写:
program_entrypoint!(process_instruction);
default_allocator!();
default_panic_handler!();
这些宏中的任何一个都可以被其他实现替换,并且 pinocchio
为此提供了几个变体。
entrypoint!
宏看起来类似于 solana-program
中找到的“标准”宏。它解析整个输入并单独提供 program_id
、accounts
和 instruction_data
。这会在程序开始执行之前消耗计算单元。在某些情况下,程序可以更好地控制输入解析的发生时间,甚至控制是否需要解析——这就是 lazy_program_entrypoint!
宏的目的。此宏仅包装程序输入并提供按需解析输入的方法。
lazy_entrypoint
适用于具有单个或非常少的指令的程序,因为它需要程序处理解析,这可能会随着指令数量的增加而变得复杂。对于较大的程序,program_entrypoint!
可能会更容易且更有效。
要使用 lazy_program_entrypoint!
宏,请在你的入口点定义中使用以下内容:
use pinocchio::{
default_allocator,
default_panic_handler,
entrypoint::InstructionContext,
lazy_program_entrypoint,
msg,
ProgramResult
};
lazy_program_entrypoint!(process_instruction);
default_allocator!();
default_panic_handler!();
pub fn process_instruction(
mut context: InstructionContext
) -> ProgramResult {
msg!("Hello from my lazy program!"); // 来自我的 lazy 程序的问候!
Ok(())
}
InstructionContext
提供对输入信息的按需访问:
available()
:可用账户数量next_account()
:解析下一个可用账户(可以使用与可用账户一样多的次数)instruction_data()
:解析指令数据program_id()
:解析程序 id⚠️ 注意:
lazy_program_entrypoint!
不会设置全局分配器或 panic handler。程序应显式使用提供的宏之一来设置它们或包含其自己的实现。
在编写程序时,确保程序不尝试进行任何分配可能会很有用。对于这种情况,pinocchio
包含一个 no_allocator!
宏,该宏设置一个全局分配器,该分配器仅在任何尝试分配内存时发生 panic。
要使用 no_allocator!
宏,请在你的入口点定义中使用以下内容:
use pinocchio::{
account_info::AccountInfo,
default_panic_handler,
msg,
no_allocator,
program_entrypoint,
ProgramResult,
pubkey::Pubkey
};
program_entrypoint!(process_instruction);
default_panic_handler!();
no_allocator!();
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
msg!("Hello from `no_std` program!"); // 来自 `no_std` 程序的问候!
Ok(())
}
⚠️ 注意:
no_allocator!
宏也可以与lazy_program_entrypoint!
结合使用。
std
默认情况下,pinocchio
是一个 no_std
crate。这意味着它不使用标准 (std
) 库中的任何代码。虽然这不会影响 pinocchio
的使用方式,但有一个特别明显的区别。在 no_std
环境中,msg!
宏不提供任何格式化选项,因为 format!
宏需要 std
库。为了使用带有格式化的 msg!
,应在添加 pinocchio
作为依赖项时启用 std
功能:
pinocchio = { version = "0.7.0", features = ["std"] }
建议使用 pinocchio-log
crate,而不是启用 std
功能以使用 msg!
格式化日志消息。此 crate 提供了一个轻量级的 log!
宏,与标准 format!
宏相比,它具有更好的计算单元消耗,而无需 std
库。
入口点宏发出的符号——程序入口点、全局分配器和默认 panic handler——只能全局定义一次。如果程序 crate 也打算用作库,则通常的做法是在你的程序 crate 中定义一个 Cargo feature 以有条件地启用包含 entrypoint!
宏调用的模块。约定是将该 feature 命名为 bpf-entrypoint
。
#[cfg(feature = "bpf-entrypoint")]
mod entrypoint {
use pinocchio::{
account_info::AccountInfo,
entrypoint,
msg,
ProgramResult,
pubkey::Pubkey
};
entrypoint!(process_instruction);
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
msg!("Hello from my program!"); // 来自我程序的问候!
Ok(())
}
}
构建程序二进制文件时,必须启用 bpf-entrypoint
feature:
cargo build-sbf --features bpf-entrypoint
该代码已获得 Apache License Version 2.0 的许可
此存储库中的库基于/包括来自以下位置的代码:
- 原文链接: github.com/anza-xyz/pino...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!