如何编写 Solana 智能合约而不会被样板代码逼疯
如果你曾经尝试过用纯 Rust 为 Solana 编写程序,你可能记得那种感觉:五行业务逻辑需要写五十行账户验证检查、通过 Borsh 手动反序列化,以及签名验证。交易中密钥顺序搞错了——你就会得到一个运行时错误,有时需要数小时才能调试出来。
在以太坊世界中,开发者早已习惯了 Solidity 和 Hardhat 或 Foundry 等现成的封装库。在 Solana 中,类似的标杆变成了 Anchor——一个处理数据解析、访问控制检查和客户端代码生成等日常工作的框架。
Anchor 为你处理的工作
本质上,Anchor 是构建在 Rust 之上的 DSL(领域特定语言)。它不会改变 Solana 内部的 BPF 执行模型,而是将底层调用打包成易于理解的声明式宏。
当你使用 Anchor 编写程序时,框架解决四个主要任务:
- 账户数据的序列化和反序列化,无需手动调用解包方法。
- 通过属性直接在结构体签名中进行账户约束验证(所有者检查、签名验证、内存初始化)。
- IDL(接口描述语言)规范组装——以太坊 ABI 的等价物。
- 创建可直接用于前端或测试的现成 TypeScript 和 Rust 客户端,用于与合约交互。
实际代码长什么样
让我们看一个经典的计数器示例。在纯 Solana SDK 中,你需要手动解析字节数组 InstructionData,提取账户切片,检查调用者地址的 is_signer,并验证系统程序地址。
以下是同一合约在 Anchor 中的样子:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, start: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = *ctx.accounts.authority.key;
counter.count = start;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}
差异立竿见影。所有验证逻辑都移到了 Initialize 和 Increment 结构体中。
#[account(init, payer = authority, space = 48)] 属性告诉运行时:创建一个新账户,为其分配 48 字节空间,并向 authority 钱包收取租金。
#[account(mut, has_one = authority)] 结构会自动检查 Counter 结构内的 authority 字段是否与传入的账户 authority 匹配,以及该账户是否实际签署了交易。如果没有签名或传入了不同的钱包,执行将在进入函数体之前中止 increment。
IDL 和前端,不再头疼
Anchor 捆绑包中最方便的是 JSON 格式的 IDL 文件。编译器在构建项目时会自动组装它。
IDL 描述了程序的所有指令、数据结构和可能的自定义错误类型。基于这个文件,@anchor-lang/core 库为 JavaScript 或 TypeScript 生成类型化接口。
在客户端组装交易时,你不再需要记住字节偏移量。从前端调用方法变成了一次常规的函数调用:
await program.methods
.increment()
.accounts({
counter: counterPubkey,
authority: wallet.publicKey,
})
.rpc();
如果你忘记传递必需的账户或指定了错误的参数类型,TypeScript 会高亮显示错误。
内置模糊测试,查找漏洞
在智能合约中,犯错的代价太高,这就是测试扮演特殊角色的原因。CLI 包含与 Crucible 工具的集成,用于覆盖率引导的模糊测试。
anchor fuzz init 命令生成测试工具,anchor fuzz run 用随机输入数据运行它,尝试找出导致 panic 或无效账户状态的边界情况。这有助于在部署到测试网之前捕获不明显的溢出或遗漏的检查。
# Инициализация фазз-тестов
anchor fuzz init program_name
# Запуск тестов в release-сборке
anchor fuzz run program_name test_name --release
安装和入门
为了管理工具链版本,开发者创建了一个名为 AVM(Anchor 版本管理器)的实用工具。这可以避免 Solana 编译器与框架本身之间的版本冲突,当不同项目需要不同的子版本时。
该工具只需一行命令即可安装:
curl -sSfL https://raw.githubusercontent.com/otter-sec/anchor/master/avm/install | sh
安装后,你可以切换到夜间构建版本或为工作区固定特定版本:
avm nightly
avm nightly --disable
谁将从 Anchor 中受益
如果你刚开始学习 Solana 开发,不使用 Anchor 几乎毫无意义。你会花数周时间重新发明账户解析和鉴别符验证的轮子。
该框架涵盖了主要需求:
- 为 Rust 后端开发者提供严格类型检查和针对常见验证漏洞模式的保护。
- 为前端开发者提供现成的 TypeScript 类型和发送交易的便捷方法。
唯一可能觉得 Anchor 过于冗余的情况是编写需要极致二进制大小优化(计算预算限制)的微型程序,在这种情况下,每个字节的指令都至关重要。在所有其他场景中,它都是事实上的标准,节省了数百小时的工作时间。
相关项目