Solana Anchor 框架指南
Learn to build Solana programs with Anchor Framework. Complete guide covering setup, core concepts, security, testing and deployment to devnet.
Solana Anchor 框架通过结构化的账户验证、指令定义、生成的接口和可重复使用的安全检查,帮助开发者构建程序。
版本说明: Anchor、Rust、Solana CLI 和 JavaScript 客户端版本独立变化。在安装或部署前,请查阅官方 Anchor 文档以验证当前的兼容性。
本文仅用于教育目的,不构成财务或投资建议。加密货币投资具有重大风险。在做出投资决策前,请务必进行独立研究。
📌 注意: 本指南使用 Anchor v0.29.x 和 Solana CLI v1.18.x+,截至 2024 年为最新版本。Anchor 框架演进迅速。在遵循这些说明之前,请运行 anchor --version 验证您的安装版本。
目录
- 什么是 Solana Anchor 框架?
- Solana 的账户模型
- Solana Anchor 框架的核心组件
- 先决条件
- 如何安装 Anchor 框架
- 构建并部署您的第一个 Anchor 程序
- 测试 Anchor 程序
- Anchor 与其他替代方案
- Anchor 框架安全性
- 常见 Anchor 错误排查
- Anchor 框架与 Solana 的开发者生态系统
- 常见问题解答
- 结论
什么是 Solana Anchor 框架?
Solana Anchor 框架是一个开源开发框架,它消除了在原始 Rust 中编写 Solana 程序(智能合约的链上等价物)时产生的样板代码和账户验证的复杂性。
Solana 是一个高性能的 Layer 1 区块链,在理想条件下(截至 2024 年)每秒可处理约 65,000 笔交易,具有亚秒级的最终确定性,且费用通常低于 0.001 美元。Solana 能够实现这种吞吐量,部分归功于一种名为历史证明 (PoH) 的独特共识机制,它创建了一个加密时钟,可以在没有传统共识协议通信开销的情况下对交易进行排序。
如果在没有框架的情况下在 Solana 上进行构建,则意味着需要为每个程序编写冗长、低级的 Rust 代码。开发者必须手动验证账户所有权、序列化数据、处理错误,并管理每笔交易的账户关系。对于来自 Web2 后端或以太坊开发的工程师来说,这一学习门槛极高,足以让大多数项目在开始之前就止步不前。
Anchor 改变了这一点。可以将 Anchor 看作 Solana 开发中的 Ruby on Rails:它是一个固执己见的框架,负责处理底层工作,让您可以专注于程序的逻辑,而不是其下的基础设施。Anchor 由 Armani Ferrante 创建,并由 Coral XYZ 维护。您可以在 Anchor 的 GitHub 仓库 (coral-xyz/anchor).) 中找到源代码和版本发布信息。当前稳定版本为 v0.29.x。
Anchor 使开发者能够构建去中心化应用程序 (dApp)。dApp 是一种应用程序,其后端逻辑通过 Solana 程序运行在区块链上,而不是在中心化服务器上。程序是 dApp 的后端;Anchor 生成的 TypeScript 客户端是您的前端调用它的方式。使用 Anchor 构建的 dApp 类别包括去中心化金融 (DeFi) 协议、非同质化代币 (NFT) 平台、去中心化自治组织 (DAO) 和 Web3 游戏。
阅读完本指南后,您将了解 Anchor 的核心架构,拥有一个可运行的开发环境,并将程序部署到 Solana 的 devnet(一个提供免费且无价值 SOL 的测试网络),并有足够的背景知识来评估 Anchor 是否适合您的项目。要直观了解 Anchor 与以太坊工具及原生 Rust 开发的对比,请参阅 Anchor 与以太坊 Hardhat 以及原生 Rust 开发的比较。
您将学到:
- 什么是 Solana 以及为什么它的架构使得 Anchor 成为必要
- IDL、宏和 PDA 各自的作用,以及程序如何相互调用
- 如何安装完整的 Anchor 开发环境
- 如何编写并部署您的第一个 Anchor 程序
- 在上线主网之前需要避免的安全漏洞
- 如何排查最常见的 Anchor 错误
Solana 的账户模型:您需要先理解的基础
Solana 的账户模型是使 Anchor 成为必要的架构基础。它与您在以太坊或 Web2 开发中遇到的任何模型都完全不同。在理解 Anchor 的抽象概念之前,您需要先了解它们所解决的问题。
什么是 Solana
Solana 是一个专为高吞吐量和低交易成本设计的 Layer 1 区块链网络。与以太坊主网每秒处理约 15 到 30 笔交易不同,Solana 在理想条件下(截至 2024 年)每秒处理约 65,000 笔交易。交易费用通常低于 0.001 美元。这种速度和成本的结合使 Solana 非常适合需要大量小额、频繁交易的应用:DeFi 交易系统、NFT 铸造平台和链上游戏。
SOL 是 Solana 的原生代币。它用于支付交易费用以及链上账户为在网络上保持活动而必须维持的租金。您部署的每个程序以及程序创建的每个账户都需要 SOL 才能存在于链上。在 devnet(Solana 的免费测试网络)上,您可以免费获得 SOL 空投。
速度优势的存在是因为架构决策使得底层 Solana 开发比以太坊开发更难。Anchor 正是针对这些难点而设计的。
Solana 如何存储程序和数据
在 Solana 上,“账户”不是日常意义上的用户账户或钱包账户。账户是区块链上的存储单元:可以保存数据、余额或可执行程序代码的独立容器。
核心架构区别:在 Solana 上,程序(可执行代码)和数据(状态)存储在不同的账户中。程序是无状态的。程序账户仅包含编译后的字节码。数据存储在程序读取和写入的独立数据账户中。
在以太坊中,智能合约就像一个自给自足的公寓:代码和家具住在同一个空间。在 Solana 中,程序更像是一家拥有独立部门的公司:可执行逻辑住在一个账户中,而它读取和写入的数据完全住在其他账户中。
| 属性 | 以太坊智能合约 | Solana 程序 + 账户 |
|---|---|---|
| 代码存储 | 在合约内 | 独立的程序账户 |
| 状态存储 | 在合约内 | 独立的(数据)账户 |
| 开发者管理账户? | 否 | 是(Anchor 自动化处理) |
| 语言 | Solidity | Rust(通过 Anchor) |
| 有状态? | 是 | 程序是无状态的;状态存在于账户中 |
每个 Solana 账户有三个属性:所有者(控制它的程序)、公钥地址(其链上标识符)以及 lamport 余额(SOL 的最小单位,1 SOL 等于 10 亿 lamport)。数据账户也包含序列化的程序数据。
实际后果是:当你调用一个 Solana 程序时,你必须明确传递程序将读取或写入的每个账户。你必须证明所有权。你必须声明哪些账户是可变的。你必须验证哪些账户是签名者。在原生 Rust 中,这种账户管理需要为每个程序编写数百行验证代码。
这种明确的账户管理正是 Anchor 的约束系统旨在解决的问题。
Solana Anchor 框架的核心组件:IDL、宏、PDA 和 CPI
Anchor 提供了四种相互关联的机制,它们共同消除了原生 Solana 开发中的大部分复杂性。按顺序理解它们是编写第一个程序的前提。
IDL(接口定义语言):Anchor 的通信契约
当你运行 anchor build 时,Anchor 会自动生成一个名为接口定义语言 (IDL) 的 JSON 文件。将其视为你的链上程序的 OpenAPI 规范:一个机器可读的描述,说明程序接受的每个指令、这些指令所需的每个账户以及涉及的每种数据类型。
IDL 之于 Anchor,正如 ABI(应用程序二进制接口)之于以太坊上的 Solidity 合约。如果你在以太坊上开发过,你已经知道 ABI 如何让前端代码安全地调用合约函数。Anchor 的 IDL 对 Solana 程序也做同样的事情,并且它会自动从你的 Rust 代码中生成。你永远不需要手动编写它。
IDL 文件在 anchor build 后会保存在 target/idl/your_program_name.json。与此伴随的是,Anchor 会生成 target/types/your_program_name.ts,一个 TypeScript 类型文件,你的前端或测试脚本可以导入它,以便获得对程序指令的完全类型化访问。
缩减版的 IDL 如下所示:
{
"version": "0.1.0",
"name": "balance_tracker",
"instructions": [
{
"name": "initialize",
"accounts": [
{ "name": "balanceAccount", "isMut": true, "isSigner": false },
{ "name": "user", "isMut": true, "isSigner": true },
{ "name": "systemProgram", "isMut": false, "isSigner": false }
],
"args": []
},
{
"name": "deposit",
"accounts": [
{ "name": "balanceAccount", "isMut": true, "isSigner": false },
{ "name": "user", "isMut": false, "isSigner": true }
],
"args": [{ "name": "amount", "type": "u64" }]
}
]
}TypeScript 客户端读取此 IDL,并为你提供完全类型的方法调用(program.methods.initialize() 和 program.methods.deposit(amount)),无需手动编写接口。你的 TypeScript 技能在这里可以直接应用。
Anchor 提供了一组 Rust 宏,这些宏是代码生成器,可以为你编写重复的验证逻辑。Rust 中的宏是在编译时扩展为代码的注解。Anchor 的宏生成了你原本需要手动编写的账户验证、序列化和错误处理代码。
三个宏构成了每个 Anchor 程序的核心:
#[program] 标记包含指令处理函数所在的模块。Anchor 将此模块中的每个公共函数视为可调用的指令。
#[derive(Accounts)] 根据 Rust 结构体定义生成账户验证代码。你声明一个指令需要哪些账户,Anchor 就会自动生成代码来验证所有权和可变性,以及每种账户的类型。
#[account] 标记一个结构体,将其作为 Anchor 管理的账户数据类型,并添加 8 字节的鉴别器,让 Anchor 可以区分你的账户类型与其他类型。
以下是这些宏在上下文中的样子:
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init, // Anchor 创建此账户(验证它不存在)
payer = user, // 'user' 账户为租金豁免存款提供资金
space = 8 + 8 // 8 字节鉴别器 + 8 字节用于 u64 total 字段
)]
pub balance_account: Account<'info, BalanceAccount>,
#[account(mut)] // 声明此账户的 lamport 余额将发生变化
pub user: Signer<'info>, // Signer 类型:Anchor 验证此账户已签名交易
pub system_program: Program<'info, System>,
}#[account(...)] 中的每个属性都是一个 Anchor 约束。约束是在指令逻辑执行前自动运行的验证规则。它们是 Anchor 程序需要远少于原生 Rust 程序手动验证代码的主要原因。
最常见的约束是:
#[derive(Accounts)]
pub struct ExampleAccounts<'info> {
#[account(init, payer = user, space = 8 + 8)]
// init: 创建账户;如果已存在则回滚
pub new_account: Account<'info, MyData>,
#[account(mut)]
// mut: 声明此账户的数据或 lamport 余额将发生变化
pub existing_account: Account<'info, MyData>,
#[account(seeds = [b"vault", user.key().as_ref()], bump)]
// seeds + bump: 派生 PDA 地址并验证规范的 bump
pub pda_account: Account<'info, VaultData>,
#[account(constraint = user_account.authority == user.key())]
// constraint: 计算布尔表达式;如果为 false 则失败
pub user_account: Account<'info, UserData>,
pub user: Signer<'info>,
// Signer: 验证账户已签名交易
}没有约束的话,你需要在指令体内手动编写所有这些检查。有了约束,Anchor 会在你的指令逻辑运行前强制执行它们。这既提高了生产力,又增加了安全性,因为约束比隐藏在指令代码中的手动检查更不容易被遗忘。
程序派生地址 (PDA):确定性的链上存储
程序派生地址 (PDA) 是 Solana 开发中较难理解的概念之一。计划将本节阅读两次。PDA 实现了 Solana 最强大的模式之一,并且 Anchor 使其比原生 Rust 更易于管理。
PDA 是从一组种子和一个程序 ID 确定性派生的账户地址。与普通钱包地址不同,PDA 没有对应的私钥。没有单个用户可以代表 PDA 签名。只有拥有该 PDA 的程序才能授权涉及它的交易。
将 PDA 视为一个只有银行(程序)才能打开的保险箱。没有客户拥有钥匙;银行通过程序控制访问。这使得 PDA 非常适合程序控制的保险库、托管账户和用户特定存储,其中程序(而非用户)应拥有权限。
在原生 Rust 中,派生 PDA 需要调用 Pubkey::find_program_address(&seeds, &program_id) 并手动验证结果。Anchor 通过 seeds 约束来处理这个问题:
#[derive(Accounts)]
pub struct CreateVault<'info> {
#[account(
init,
payer = authority,
space = 8 + 32,
seeds = [b"vault", authority.key().as_ref()], // Seeds 决定 PDA 地址
bump // Anchor 查找并存储规范的 bump seed
)]
pub vault: Account<'info, Vault>, // PDA 账户,无私钥
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}seeds 约束告诉 Anchor 如何派生 PDA 地址。bump 字段存储规范的 bump 种子,这是一个确保派生地址落在椭圆曲线之外且没有私钥的小整数。在 Anchor v0.29.x 中,规范的 bump 会被自动派生并验证。
PDA 是确定性的:给定相同的种子和程序 ID,它们总是产生相同的地址。此属性允许客户端在链下计算 PDA 地址以获取数据,而程序则控制写入访问。
跨程序调用 (CPI):程序间如何对话
跨程序调用 (CPI) 是 Solana 的一种机制,允许一个程序调用另一个程序的指令。它是链上等同于调用共享库函数的操作。CPI 实现了可组合性:您的 DeFi 协议可以调用 SPL Token 程序来转移代币,您的治理合约可以调用质押程序,等等。
Solana 程序库 (SPL) 是 Solana 链上程序的标准库。SPL Token 之于 Solana 就像 ERC-20 之于 Ethereum,但它是作为一个共享的链上程序实现的,而不是每个项目独立部署的合约模板。当您的 Anchor 程序需要铸造或转移代币时,它会通过 CPI 调用 SPL Token。
Anchor 使用 anchor-spl crate 和 CpiContext 简化了 CPI 调用:
use anchor_spl::token::{self, Transfer, Token};
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.vault_token_account.to_account_info(), // 源代币账户
to: ctx.accounts.user_token_account.to_account_info(), // 目标代币账户
authority: ctx.accounts.vault.to_account_info(), // PDA 为保险库签名
};
let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts);
token::transfer(cpi_ctx, amount)?; // 调用 SPL Token 的转移指令
Ok(())
}如果没有 Anchor,组装账户信息、构建指令并调用 invoke_signed 大约需要 30 行代码。Anchor 的 CPI 助手将此简化为一个类型化的、易读的函数调用。
您将在分步程序教程中将这些概念付诸实践。
前提条件:安装 Anchor 框架前需要准备什么
在 Solana 上使用 Anchor 框架构建之前,需要准备四种工具。
所需工具:
- Rust 和 Cargo(通过 rustup),建议使用 1.70.0 或更高版本
- Solana CLI(v1.18.x 或更高版本),用于与 Solana 网络交互的官方命令行工具;处理钱包管理、空投请求和集群配置
- Node.js(v18 或更高版本)以及 Yarn 或 npm,TypeScript 客户端和 Anchor 测试所需
- Anchor Version Manager (avm),用于安装和管理 Anchor CLI 版本;请始终通过 avm 安装 Anchor,而不是直接通过 Cargo
关于 Rust: Anchor 程序是用 Rust 编写的。必须安装 Rust,这是无法规避的。即便如此,Anchor 的宏抽象了最痛苦的 Rust 模式:账户验证和序列化,以及错误传播。您不需要精通 Rust 也能构建您的第一个程序。
开始时需要了解:结构体 (structs)、函数 (functions)、基本所有权 (basic ownership) 和浅层次的 trait bounds。可以推迟学习的部分:生命周期 (lifetimes)、异步 Rust 以及借用检查器 (borrow checker) 的细节。您的 TypeScript 或 JavaScript 技能可以直接应用于 Anchor 的客户端代码。测试、前端交互和 IDL 生成的客户端调用都使用 TypeScript。您不需要成为 Rust 专家才能使用 Anchor 构建,就像您不需要成为 Ruby 专家才能使用 Ruby on Rails 构建一样。但您会遇到 Rust,基本的熟悉程度会有所帮助。
如果您对 Anchor 抽象 Rust 模式的方式感兴趣,核心组件部分详细解释了 Anchor 从 Rust 中抽象出了哪些内容。
关于 Solana CLI 与 Anchor CLI: 这是两个具有不同职责的独立工具。solana CLI 处理网络交互:查询余额、请求空投以及在开发网 (devnet) 和主网之间切换。anchor CLI 处理程序的生命周期:初始化项目、构建程序、运行测试和部署。两者都是必需的。
如何安装 Anchor 框架:分步设置指南
📌 注意: 以下所有命令均已在 2024 年针对 Anchor v0.29.x 和 Solana CLI v1.18.x 进行了验证。安装后运行 anchor --version 以确认您的版本。如果在发布后阅读本文,请在 Anchor 的 GitHub 仓库 (coral-xyz/anchor) 验证当前版本。
步骤 1:通过 rustup 安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 按照屏幕提示选择默认安装
source "$HOME/.cargo/env"
rustc --version
# 应打印:rustc 1.70.0 或更高版本步骤 2:安装 Solana CLI
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
# 按照安装程序输出的 PATH 指令进行操作
solana --version
# 应打印:solana-cli 1.18.x 或更高版本当您将 Anchor 程序部署到 Solana 时,它会存储在链上,并在交易调用它时由网络的验证节点执行。Solana CLI 是您在终端与这些验证节点交互的方式。
步骤 3:将 Solana CLI 配置为开发网并创建钱包
solana config set --url devnet
solana-keygen new
# 创建一个本地密钥对文件(您的开发钱包)
# 保存输出路径。Anchor 使用此密钥对签署部署💡 提示: 保存您的密钥对文件路径。Anchor 从您的 Anchor.toml 配置中读取此密钥对,以签署开发网上的部署交易。
步骤 4:请求开发网 SOL 空投
solana airdrop 2
solana balance
# 确认余额显示为 2 SOL步骤 5:安装 avm (Anchor Version Manager) 和 Anchor
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
avm install latest
avm use latest
anchor --version
# 应打印:anchor-cli 0.29.x📌 注意: 请务必通过 avm 安装 Anchor,而不是直接通过 cargo install anchor-cli。avm 让您可以在不重新安装的情况下切换 Anchor 版本。当项目固定到特定版本或升级现有项目时,这一点非常重要。
步骤 6:初始化您的第一个项目
anchor init my-first-program
cd my-first-programanchor init 后的项目结构:
my-first-program/
├── Anchor.toml
├── programs/
│ └── my-first-program/
│ └── src/
│ └── lib.rs
├── tests/
│ └── my-first-program.ts
└── app/💡 提示: anchor init 命令在 lib.rs 中构建了一个工作示例程序框架。在编写您自己的代码之前,请立即运行 anchor test 以确认您的环境运行正常。
构建并部署您的第一个 Anchor 程序:完整演练
本节将带您从一个空项目过渡到在 Solana 开发网上部署程序。该示例是一个余额追踪器:一个将存储余额初始化为零,然后接受充值指令以增加余额的程序。这演示了完整的账户生命周期,功能上与计数器程序相同,但使用了原始变量名称。
编写余额追踪器程序
您的 programs/my-first-program/src/lib.rs 文件已经包含来自 anchor init 的框架。将其内容替换为以下内容:
// Anchor v0.29.x 余额追踪器程序
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkgHH5WfZCnQ");
#[program]
pub mod balance_tracker {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.balance_account.total = 0; // 初始余额为零
Ok(())
}
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
ctx.accounts.balance_account.total += amount; // 增加存储的余额
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = user,
space = 8 + 8 // 8 字节 Anchor 判别器 + 8 字节用于 u64
)]
pub balance_account: Account<'info, BalanceAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut)]
pub balance_account: Account<'info, BalanceAccount>,
pub user: Signer<'info>,
}
#[account]
pub struct BalanceAccount {
pub total: u64,
}在继续下一步之前,这段代码中的三个模式非常重要。首先,#[derive(Accounts)] 完全生成了账户验证逻辑。Anchor 会读取您的结构体定义并编写所有权检查、可变性检查和签名者验证。您声明意图;Anchor 执行意图。其次,space = 8 + 8 准确分配了账户所需的字节数:8 字节用于 Anchor 的判别器(一个唯一标签,用于识别账户类型并防止账户混淆攻击),再加上 8 字节用于 u64 类型的 total。第三,Signer<'info> 是 Anchor 的内置类型,用于验证账户是否签署了交易。如果没有它,任何账户都可以调用您的充值指令。
使用 Mocha 和 TypeScript 测试您的程序
Mocha 是一个 JavaScript 测试运行器;Chai 提供断言语法。当您运行 anchor init 时,两者都会预先安装。打开 tests/my-first-program.ts 并替换其内容:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { MyFirstProgram } from "../target/types/my_first_program";
import { assert } from "chai";
describe("balance-tracker", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.MyFirstProgram as Program<MyFirstProgram>;
const balanceAccount = anchor.web3.Keypair.generate();
it("Initializes a balance account with total = 0", async () => {
await program.methods
.initialize()
.accounts({
balanceAccount: balanceAccount.publicKey,
user: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([balanceAccount])
.rpc();
const account = await program.account.balanceAccount.fetch(
balanceAccount.publicKey
);
assert.equal(account.total.toNumber(), 0, "初始余额应为 0");
});
it("Deposits 500 and updates the stored balance", async () => {
await program.methods
.deposit(new anchor.BN(500))
.accounts({
balanceAccount: balanceAccount.publicKey,
user: provider.wallet.publicKey,
})
.rpc();
const account = await program.account.balanceAccount.fetch(
balanceAccount.publicKey
);
assert.equal(account.total.toNumber(), 500, "充值后余额应为 500");
});
});@coral-xyz/anchor npm 包是 Anchor 的 TypeScript 客户端库。Program<MyFirstProgram> 类型是在运行 anchor build 时从 IDL 自动生成的。这就是 IDL 的实际价值所在:您的 TypeScript 代码可以获得对 Rust 程序接口的完全类型安全性,而无需手动编写任何接口。
此程序也是 dApp 的后端。上面显示的 TypeScript 客户端就是前端应用程序调用它的方式。在这些方法调用之上添加一个 React 或 Next.js UI,您就拥有了一个运行在 Solana 上的完整去中心化应用程序。
构建并部署到 Devnet
从项目根目录按顺序运行这三个命令:
anchor build
# 输出:target/deploy/my_first_program.so, target/idl/my_first_program.json, target/types/my_first_program.tsanchor test
# 启动本地验证器,进行部署,运行 Mocha 测试,然后关闭anchor deploy --provider.cluster devnet
# 输出包含您的 Program Id: ABC123...xyz789💡 提示: 从部署输出中复制您的 Program ID。用它更新 lib.rs 中的 declare_id!("...") 值,然后再次运行 anchor build 以在编译后的程序中嵌入正确的地址。同时更新 Anchor.toml 中的 [programs.devnet] 部分。
📌 注意: 您正在部署到 devnet,即 Solana 的测试网络。这不需要花费真实的 SOL。您在设置章节获得的 devnet 空投将支付部署费用。
您现在已经编写并测试了一个 Solana 程序,然后使用 Anchor 框架将其部署。该程序以您的 Program ID 存在于 devnet 上,并在每次交易调用它时执行。
测试 Anchor 程序:anchor test 工作流
anchor test 是一个单一命令,它会启动本地 Solana 验证器,将您的程序部署到其中,运行完整的 Mocha 测试套件,并在完成后关闭验证器。您不需要手动管理验证器。
测试工作流顺序如下:
anchor test读取您的Anchor.toml配置- 它在后台启动一个本地验证器进程
- 它构建您的程序并部署到该验证器
- 它使用 Mocha 运行
tests/目录中的所有 TypeScript 测试文件 - 它打印通过/失败结果并关闭验证器
测试文件结构: 每个测试文件都从 @coral-xyz/anchor 导入,设置一个 Provider(连接到本地验证器),并通过 IDL 生成的 workspace 对象引用您的程序。describe 和 it 块是标准的 Mocha 语法。
获取账户数据: 调用指令后,获取链上账户以断言其状态:
const account = await program.account.balanceAccount.fetch(
balanceAccount.publicKey
);
assert.equal(account.total.toNumber(), 0);测试错误案例: 测试无效输入是否能正确报错。一个经过良好测试的程序包含负面测试:
it("Rejects deposit from an unsigned account", async () => {
const badActor = anchor.web3.Keypair.generate();
try {
await program.methods
.deposit(new anchor.BN(100))
.accounts({
balance_account: balanceAccount.publicKey,
user: badActor.publicKey,
})
.rpc();
assert.fail("交易本应被拒绝");
} catch (err) {
assert.ok(err, "Anchor 拒绝了未签名的交易");
}
});本地网络 (Localnet) vs. 测试网络 (Devnet): 在所有活跃开发阶段都使用 anchor test (localnet)。本地网络是瞬时的且免费的,并在每次运行之间重置。仅当您想分享持久的 URL 或针对实际网络条件进行测试时,才部署到 devnet。
Mocha 是测试运行器(describe 和 it 块结构)。Chai 提供断言语法(assert.equal, assert.ok, expect)。这两者在每个 anchor init 项目中都是预先配置好的,无需单独安装。
Anchor 与替代方案:选择您的 Solana 开发路径
在 Solana 上构建存在三种开发路径:Anchor 框架、不带框架的原生 Rust,以及 Seahorse。Solana 本身是值得为您的项目进行评估的几个区块链之一。下面的表格展示了关键区别,不作任何倾向性推荐。
表 1:Solana 开发方法
| Anchor | 原生 Rust (无框架) | Seahorse | |
|---|---|---|---|
| 语言 | Rust + TypeScript 客户端 | 仅 Rust | Python (编译为 Rust) |
| 所需样板代码 | 低 (宏处理验证) | 高 (手动验证代码) | 低 |
| 生产就绪度 | 生产就绪 | 生产就绪 | 实验性 |
| 社区规模 | 大 (事实标准) | 中等 | 小 |
| 学习曲线 | 中等 (需要 Rust 基础) | 陡峭 (需要完整 Rust) | 低 (Python 语法) |
| IDL/客户端生成 | 自动 | 手动 | 通过 Anchor (底层) |
| 最适合 | 大多数 Solana 应用开发 | 需要完全控制的系统级程序 | Python 开发者原型设计 |
表格 2:Solana 与 Ethereum 工具链对比
| Anchor (Solana) | Hardhat (Ethereum) | Foundry (Ethereum) | |
|---|---|---|---|
| 区块链 | Solana | Ethereum | Ethereum |
| 主要语言 | Rust | Solidity + JavaScript | Solidity |
| 测试语言 | TypeScript (Mocha) | JavaScript/TypeScript | Solidity (原生) |
| 程序模型 | 无状态程序 + 独立账户 | 有状态合约 | 有状态合约 |
| 吞吐量 | ~65,000 TPS (最佳,截至 2024 年) | ~15–30 TPS (主网,截至 2024 年) | ~15–30 TPS (主网,截至 2024 年) |
| 交易费用 | 通常低于 0.001 美元 | 可变 (每笔交易通常 1-50 美元以上) | 可变 (每笔交易通常 1-50 美元以上) |
| 生态系统就绪度 | 增长中;年轻 | 最大智能合约生态系统 | 最大智能合约生态系统 |
| 安全审计池 | 较小,正在增长 | 较大,成熟 | 较大,成熟 |
| 最适合 | 高频、低费用应用 | 最广泛的生态系统;最大安全记录 | 偏好 Solidity 原生测试的 Ethereum 高级用户 |
何时选择 Anchor 而非原生 Rust: 对于许多 Solana 应用开发用例,包括 DeFi 协议、NFT 平台、代币门控系统、DAO 和链上游戏,Anchor 是常见的起点。样板代码的减少非常可观:一段原始 Rust 代码可能需要 300 行,而使用 Anchor 可能只需要 80 行。原生 Rust 开发在程序需要精确控制计算单元消耗、Anchor 的抽象与非标准架构冲突,或者团队拥有深厚的 Rust 专业知识并需要最大灵活性时更有意义。
Seahorse 框架: Seahorse 允许开发者用 Python 编写 Solana 程序,Seahorse 然后在底层将其编译为兼容 Anchor 的 Rust。这降低了希望在不学习 Rust 的情况下尝试 Solana 的 Python 开发者语言门槛。权衡在于就绪度:Seahorse 是一个社区项目,用户基础比 Anchor 小得多,截至 2024 年不推荐用于生产应用。对于原型设计,这是一个合理选项;对于生产环境,Anchor 仍然是事实标准。
Ethereum 与 Solana 对比: Ethereum 拥有最大的智能合约开发者生态系统、最深厚安全审计历史以及最广泛的工具支持。Solana 提供更高的吞吐量和更低的费用,更适合交易频率和成本是主要限制因素的应用。这些平台适合不同的应用画像。构建高频交易基础设施或微交易游戏的开发者可能会偏爱 Solana;构建治理系统或需要最大生态系统兼容性的代币标准的开发者可能会偏爱 Ethereum。对于大多数生产 Solana 应用,Anchor 是常见的起点,主要协议(包括 Raydium、Marinade Finance 和 Metaplex)选择 Anchor 作为其基础,也证实了这一点。
Anchor 框架安全:每个 Solana 开发者必须知道的五种漏洞
⚠️ 警告: Solana 程序中的安全漏洞可能导致用户资金永久、不可逆的损失。在将任何程序部署到主网之前,请运行静态分析工具(如 Soteria)并考虑进行专业安全审计。Solana 生态系统已因以下描述的漏洞而遭受重大损失。对于生产程序,安全审查不是可选项。如需更深入的阅读,请查阅 Anchor 官方文档.
Anchor 的约束系统 (#[account(...)] 属性在您的账户结构体中) 是您的第一道安全防线。它会自动强制执行您原本需要在原始 Rust 中手动编写的所有权检查、可变性权限和类型验证。但约束仅保护您声明的内容。以下漏洞源于开发者忘记声明的内容。
缺失签名者检查
如果调用者未被要求签署交易,任何账户都可以调用您的指令。这使得任何钱包都能调用本应受限的指令。
// 易受攻击:AccountInfo 未验证调用者是否签署了交易
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut)]
pub balance_account: Account<'info, BalanceAccount>,
pub user: AccountInfo<'info>, // 未进行签名验证
}// 安全:Signer<'info> 如果账户未签名,将导致交易失败
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut)]
pub balance_account: Account<'info, BalanceAccount>,
pub user: Signer<'info>, // 如果用户未签名,交易将回滚
}对于应授权指令的任何账户,请使用 Signer<'info>。
缺失所有权检查 (账户替换)
攻击者可以替换一个精心构造的账户,该账户具有与您的预期账户相同的数据布局,但由不同的程序拥有。
// 易受攻击:AccountInfo 未验证账户的程序所有者
pub balance_account: AccountInfo<'info>, // 接受任何账户// 安全:Account<'info, T> 验证账户是否由您的程序拥有
pub balance_account: Account<'info, BalanceAccount>, // 所有权检查是自动的对于包含程序控制数据的账户,始终使用 Anchor 的类型化 Account<'info, T> 包装器,而不是原始的 AccountInfo<'info>。
重初始化攻击
如果 initialize 指令可以在同一账户上运行多次,攻击者就可以重置本应永久的状态。
// 易受攻击:init_if_needed 会静默接受已初始化的账户
#[account(init_if_needed, payer = user, space = 8 + 8)]
pub balance_account: Account<'info, BalanceAccount>,// 安全:init 会在账户已存在时回滚
#[account(init, payer = user, space = 8 + 8)]
pub balance_account: Account<'info, BalanceAccount>,对于只能创建一次的账户,请使用 init(而不是 init_if_needed)。如果 init_if_needed 确实有必要,请添加一个显式的初始化标志并在您的指令逻辑中对其进行检查。
整数溢出和下溢
Rust 的算术在调试版本中溢出时会 panic,但在发布版本中会静默包装,这可能导致金融余额损坏而不会引发错误。
// 易受攻击:直接算术在发布模式下会静默包装
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<() {
ctx.accounts.balance_account.total += amount;
Ok(())
}// 安全:checked_add 在溢出时返回 Err 而不是包装
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<() {
ctx.accounts.balance_account.total = ctx.accounts.balance_account.total
.checked_add(amount)
.ok_or(error!(ErrorCode::Overflow))?;
Ok(())
}对金融数值的所有算术运算,请使用 checked_add、checked_sub 和 checked_mul。定义一个具有 Overflow 变体的自定义 ErrorCode 枚举,以获得清晰的错误消息。
PDA Bump 规范化
使用非规范的 bump seed 会生成一个有效但意外的 PDA 地址,从而可能导致地址欺骗攻击。
// 易受攻击:手动 bump 不保证是规范的
#[account(seeds = [b"vault", user.key().as_ref()], bump = ctx.accounts.vault.bump)]
pub vault: Account<'info, Vault>,// 安全:省略显式的 bump 值。Anchor v0.29.x 会自动推导并验证
// 规范的 bump
#[account(
seeds = [b"vault", user.key().as_ref()],
bump,
)]
pub vault: Account<'info, Vault>,在 Anchor v0.29.x 中,省略显式的 bump 值,让 Anchor 推导规范的 bump。如果您将 bump 存储在账户数据中,请在 init 期间使用 ctx.bumps.vault 进行填充。
在实现这些安全模式时遇到的错误消息,请参阅故障排除部分。
故障排除:常见的 Anchor 错误
下方的错误代表了开发者在使用 Anchor Framework 构建时遇到的最常见问题。每个条目包括了确切的错误消息、原因和修复方法。
📌 注意: 版本不匹配是导致大多数 anchor build 失败的原因。在调查其他原因之前,请先查看此表格。
| Anchor 版本 | 所需 Solana CLI | 所需 Rust |
|---|---|---|
| v0.29.x | v1.18.x 或更高版本 | 1.70.0 或更高版本 |
| v0.28.x | v1.16.x 或更高版本 | 1.69.0 或更高版本 |
错误 1:AccountNotInitialized
AnchorError caused by account: balance_account.
Error Code: AccountNotInitialized.
Error Number: 3012.原因: 您的程序尝试读取一个尚不存在于链上的数据账户。initialize 指令尚未被调用。
修复: 在任何读取该账户的指令之前,请调用您的 initialize 指令。在测试中,请确保初始化 it 块在充值测试块之前运行。
错误 2:anchor build 时的类型不匹配
error[E0308]: mismatched types
expected struct `anchor_lang::prelude::AccountInfo`
found struct `anchor_lang::accounts::signer::Signer`原因: 您的代码使用了 Anchor v0.26.x 或更早版本的账户约束语法,该语法在 v0.29.x 中已更改。
修复: 查阅 Anchor 官方文档) 获取 v0.29.x 的约束语法。运行 avm use 0.29.0 以确认您的 CLI 版本与 Cargo.toml 中的依赖版本匹配。
错误 3:Anchor/Solana CLI 版本不兼容
Error: RPC response error -32002: Transaction simulation failed原因: 您的 Anchor CLI 版本和 Solana CLI 版本不兼容。
修复:
avm use 0.29.0
solana --version # 确认 Solana CLI 是 1.18.x 或更高版本错误 4:重新部署后 declare_id! 不匹配
Error: Program Id mismatch原因: 您已重新部署到新的地址,但 lib.rs 中的 declare_id!() 仍然保留旧的程序 ID。
修复:
anchor keys sync
anchor build错误 5:Devnet 余额不足
Error: Transaction simulation failed: insufficient funds原因: 您的 devnet 钱包没有足够的 SOL 来支付部署或交易费用。
修复:
solana airdrop 2
solana balance错误 6:程序二进制文件超出大小限制
Error: account data too small for instruction原因: 您编译后的程序二进制文件超出了 Solana 对可升级程序的大小限制。
修复: 在您的 Cargo.toml 中添加发布优化:
[profile.release]
opt-level = "z"
lto = true从您的 Cargo.toml 和 Anchor.toml 中移除未使用的依赖项和功能标志。
如果您遇到版本错误,请返回安装指南,并验证您的工具版本与上表中的兼容性相符。
Anchor Framework 和 Solana 的开发者生态系统
Anchor Framework 支持 Solana 主网上部署的大多数生产级程序,包括去中心化金融 (DeFi)、NFT 基础设施和链上治理中的协议。
使用 Anchor 构建的生产协议:
- Raydium:自动化做市商 (AMM) 和流动性协议
- Marinade Finance:SOL 的流动性质押协议
- Metaplex:NFT 标准、代币元数据和工具套件
- Drift Protocol:去中心化永续合约交易所
- Kamino Finance:流动性和借贷协议
- Jito:MEV 感知质押和验证者基础设施
DeFi 协议代表了 Anchor 部署程序的主流类别。去中心化交易所、借贷平台和收益聚合器都依赖 Solana 的吞吐量(截至 2024 年,在最佳条件下约为每秒 65,000 笔交易)和低费用(通常低于每笔交易 0.001 美元)来进行核心操作。链上金融工具和现实世界资产代币化的增长反映了这种基础设施的成熟度。
根据 Electric Capital 开发者报告(最新数据请访问 electriccapital.com),Solana 的活跃开发者基数在 2023 年至 2024 年期间同比增长。不断增长的开发者采用率通常有助于区块链生态系统的健康发展,但这不应被视为投资建议。
SOL 是 Solana 网络的手续费货币。每一次调用链上程序的交易都需要花费少量 SOL(可在 Coinbase 和 Binance 等主要加密货币交易所交易)。有关当前的 SOL 市场数据,请参阅CoinGecko 上的当前 SOL 价格和市场数据.)
越多开发者使用 Anchor 构建,部署的程序就越多,Solana 网络的使用也就越活跃。这种联系是对生态系统动态的观察,而非投资信号。
关于 Solana Anchor Framework 的常见问题解答
Solana 使用什么编程语言?
Solana 程序使用 Rust 编写。运行时也支持 C 和 C++,但 Rust 是生产开发的标准。对于客户端代码,包括测试、前端交互和调用您程序的脚本,则通过 @coral-xyz/anchor npm 包使用 TypeScript 和 JavaScript。Anchor Framework 同时覆盖了这两个层面:Rust 用于程序逻辑,TypeScript 用于与之交互的所有内容。
使用 Anchor Framework 是否必须掌握 Rust?
是的。Anchor 程序用 Rust 编写,这是必须的。也就是说,Anchor 的宏抽象了 Solana 开发中最具 Rust 特色的部分:账户验证和错误传播。一个具备基本 Rust 知识(结构体、函数、trait、基本所有权)的开发者就可以构建并部署一个可工作的 Anchor 程序。对于入门来说,不需要深入掌握 Rust 的生命周期、异步模式和高级泛型。您的 TypeScript 或 JavaScript 技能可以直接应用于每个 Anchor 项目的客户端部分。
简单来说,什么是程序派生地址 (PDA)?
程序派生地址 (PDA) 是一个由程序控制而非用户通过私钥控制的账户地址。由于 PDA 没有私钥,只有拥有它的程序才能授权涉及它们的交易。给定相同的 seeds 和程序 ID,PDA 始终解析为相同的地址,这允许客户端在不单独存储地址的情况下查找程序控制的账户。Anchor 通过账户结构体中的 seeds 约束自动处理 PDA 推导。PDA 常用于程序控制的钱包、托管账户和每个用户的存储。
您能用 Anchor Framework 构建 NFT 吗?
是的。Anchor 由 Coral XYZ 维护,是截至 2024 年 Solana 程序开发的实际标准。当前稳定版本是 v0.29.x。Anchor GitHub 存储库 (coral-xyz/anchor) 会定期发布更新,跟踪 Solana 的运行时更新。包括 Raydium、Marinade Finance 和 Metaplex 在内的主要生产协议继续在 Anchor 上构建,证实了其生产就绪性。
Anchor 与以太坊的 Hardhat 相比如何?
Anchor 和 Hardhat 在各自的区块链上扮演着类似的角色:它们都提供项目脚手架、测试环境和部署工具。区别始于底层架构。Hardhat 针对的是以太坊的有状态合约模型,智能合约既包含逻辑也包含状态。Anchor 针对的是 Solana 的无状态程序模型,程序和数据生活在单独的账户中。语言也不同:Hardhat 项目使用 Solidity 编写合约,使用 JavaScript 或 TypeScript 进行测试;Anchor 项目使用 Rust 编写程序,使用 TypeScript 进行测试。交易成本也大不相同:Solana 交易通常成本低于 $0.001,而以太坊 Gas 费用各不相同,每笔交易可能高达 $1 到 $50 或更多。如需完整的并排比较,请参阅 Anchor 与替代方案的比较表。
学习 Anchor 框架需要多长时间?
拥有 TypeScript 或 JavaScript 经验且没有 Rust 知识的开发人员,通常可以在结构化指南的帮助下,在一到两天内构建一个基本的 Anchor 程序并部署到 devnet。从基本程序到生产 DeFi 协议需要数周到数月,具体取决于程序的复杂性和 Rust 的掌握程度。Rust 的学习曲线是主要的可变因素。在学习 Anchor 的同时投入时间学习 Rust 基础知识的开发人员,在开发非简单程序方面会取得更快的进展。
什么是 Solana 程序库 (SPL)?
Solana 程序库 (SPL) 是 Solana 上用于常见操作的预部署、已审计程序集合。SPL Token 程序是 Solana 对应 ERC-20 标准的版本,它被实现为一个共享的链上程序,而不是每个项目独立部署的合约模板。Anchor 通过 anchor-spl crate 集成 SPL,该 crate 为 SPL Token 指令(包括 transfer、mint_to 和 burn)提供了类型安全的封装。SPL 也是 Metaplex 程序通过 NFT 代币元数据的基础。
Solana 中的历史证明 (PoH) 是什么?
历史证明 (PoH) 是 Solana 在将交易提交给验证者达成共识之前,用来对交易进行排序的一种加密时间戳机制。PoH 不是要求验证者互相通信来就交易顺序达成一致,而是将可验证的时间戳序列直接嵌入区块链。这减少了传统共识协议的协调开销,也是 Solana 在 2024 年最佳条件下能达到每秒约 65,000 笔交易的原因之一。
Solana 适合智能合约开发吗?
Solana 非常适合交易吞吐量和成本是主要限制因素的应用:高频 DeFi 协议、涉及大量小额交易的 NFT 市场以及链上游戏。它的账户模型比以太坊需要更多的前期学习,但 Anchor 框架极大地减少了这种摩擦。Solana 是否是正确的选择取决于您的应用程序类型。以太坊提供了更大的生态系统和更成熟的安全审计历史,而 Solana 则为两者皆有益的用例提供了更低的费用和更高的吞吐量。
结论:Anchor 框架适合您的 Solana 项目吗?
对于许多 Solana 应用程序开发工作流程,Anchor 框架是一个常见的起点,而当需要更底层的控制时,原生 Rust 仍然具有相关性。
Anchor 大大减少了原生 Rust 开发的样板代码。在原生 Solana 代码中需要数百行才能完成的程序,使用 Anchor 的宏系统只需一小部分代码。它通过其约束系统强制执行账户安全,在编译时或运行时捕获所有权和授权错误,而不是在生产环境中捕获。它还会根据 IDL 自动生成 TypeScript 客户端,为 JavaScript 开发人员提供一个类型化、熟悉的程序接口,而无需在客户端编写 Rust。
只有当您的程序需要精确控制计算单元消耗,或者 Anchor 的抽象与非典型架构真正冲突时,才值得考虑原生 Rust 开发。对于绝大多数程序,包括 DeFi 协议、NFT 平台、DAO 和代币门控系统,Anchor 都是您应该开始的地方。
下一步:
- 使用
anchor deploy --provider.cluster devnet命令,通过本指南将余额跟踪器程序部署到 devnet - 访问 anchor-lang.com 查看 Anchor 官方文档,了解完整的 API 参考和高级功能
- 浏览 Anchor GitHub 示例存储库,查找生产模式的程序
- 当遇到问题时,加入 Anchor Discord 或 Solana Stack Exchange 获取社区支持
本指南中部署的程序是真实的:它存在于 Solana 的 devnet 上,接受交易,并在链上存储状态。这是每个生产 Solana 应用程序的基础。
相关 Solana 技术指南
跟踪 SOL 以及生态系统工具的开发人员可以参考 SOL 价格页面),该页面提供独立的市场参考;它不验证程序的安全性或部署就绪性。