(三)Solana 入门指南——Anchor框架
1. 什么是Anchor?
Anchor 是一个专为 Solana 区块链设计的开发框架,通过自动处理账户和指令数据的(反)序列化、内置安全检查、分离安全逻辑与业务逻辑、自动生成客户端库以及提供强大的测试工具,帮助开发者快速构建和测试安全可靠的智能合约和去中心化应用。
2. 安装 Anchor CLI
安装 Anchor 版本管理器 (avm)
首先,我们需要安装 Anchor 版本管理器 (avm),这可以通过 Cargo 来完成:
cargo install --git https://github.com/coral-xyz/anchor avm --force
使用 avm 安装最新版本的 CLI
安装完 avm 后,使用以下命令安装最新版本的 Anchor CLI:
avm install latest
验证安装
验证 Anchor CLI 是否安装成功:
anchor --version
你应该会看到类似如下的输出:
anchor-cli 0.30.1
查看已安装的 Anchor 列表
查看已安装的 Anchor 版本列表:
avm list
如果遇到任何问题,请参考 Anchor 安装指南。
安装 Node 和 Yarn
如果你还没有安装 Node 和 Yarn,可以通过以下链接进行安装:
3. 初始化项目
使用 Anchor CLI 快速初始化一个项目:
anchor init counter
这将创建一个名为 counter 的项目目录。进入该目录:
cd counter
使用 Visual Studio Code 打开该项目
如果在 lib.rs 文件中编辑器提示以下内容:
custom attribute panicked
message: Safety checks failed: Failed to get program path
在项目目录下新建文件 .vscode/settings.json,并添加以下内容:
{
"rust-analyzer.cargo.features": []
}
详细原因请参考 GitHub Issue #3042。
通过以上步骤,你已经成功安装了 Anchor CLI 并初始化了一个 Solana 项目。接下来,你可以开始编写和测试你的第一个 Solana 智能合约。
4. 编写程序
在文件 program/counter/src/lib.rs 中添加以下代码:
// 引入 Anchor 框架的预定义模块
use anchor_lang::prelude::*;
// 声明程序的唯一标识符,这是 Solana 程序的公钥(初始化的时候会生成)
declare_id!("7gioH5h8HSMv5pt5xdJrJz9ps7471ecD6mWaEm1kejFg");
// 定义一个名为 `counter` 的程序模块
#[program]
pub mod counter {
use super::*;
// 初始化计数器的函数
pub fn initialize(ctx: Context<InitializeCounter>) -> Result<()> {
// 打印程序的公钥
msg!("Greetings from: {:?}", ctx.program_id);
// 返回成功结果
Ok(())
}
// 增加计数器的函数
pub fn increment(ctx: Context<Increment>) -> Result<()> {
// 获取当前计数器的值,并将其增加 1
ctx.accounts.counter.count = ctx.accounts.counter.count.checked_add(1).unwrap();
// 返回成功结果
Ok(())
}
}
// 定义 `InitializeCounter` 结构体,用于初始化计数器时的上下文
#[derive(Accounts)]
pub struct InitializeCounter<'info> {
// 标记为可变的账户,表示该账户可以被修改
#[account(mut)]
pub payer: Signer<'info>, // 支付账户,用于支付创建新账户的费用
pub system_program: Program<'info, System>, // Solana 系统程序
// 创建一个新的计数器账户,空间大小为 `Counter::INIT_SPACE + 8`
#[account(init, payer=payer, space=Counter::INIT_SPACE+8)]
pub counter: Account<'info, Counter>, // 计数器账户
}
// 定义 `Increment` 结构体,用于增加计数器时的上下文
#[derive(Accounts)]
pub struct Increment<'info> {
// 标记为可变的账户,表示该账户可以被修改
#[account(mut)]
pub counter: Account<'info, Counter>, // 计数器账户
}
// 定义 `Counter` 账户结构体,用于存储计数器的数据
#[account]
#[derive(InitSpace)]
pub struct Counter {
count: u64, // 计数器的值
}
5. 测试程序
编译
在项目根目录下运行以下命令来编译智能合约:
anchor build
编写测试脚本
在文件tests/counter.ts中添加下面代码
// 导入Anchor库,这是一个用于构建Solana链上程序的框架
import * as anchor from "@coral-xyz/anchor";
// 导入Program类,用于与链上的智能合约进行交互
import { Program } from "@coral-xyz/anchor";
// 导入Counter类型,这是从编译后的合约类型中获取的
import { Counter } from "../target/types/counter";
// 导入断言库,用于测试验证
import { assert } from "chai";
// 导入Keypair类,用于生成密钥对
import { Keypair } from "@solana/web3.js";
// 定义一个测试套件,用于测试counter程序的功能
describe("counter", () => {
// 配置客户端使用本地集群(测试环境)
const provider = anchor.AnchorProvider.env();
// 设置全局provider,使Anchor知道如何与网络通信
anchor.setProvider(provider);
// 获取支付者的钱包信息,用于支付交易费用
const payer = provider.wallet;
// 从Anchor工作区加载Counter程序实例
const program = anchor.workspace.Counter as Program<Counter>;
// 为计数器账户生成一个新的密钥对
const counterKeypair = new Keypair();
// 测试用例:初始化计数器
it("初始化计数器", async () => {
// 调用智能合约的initialize方法来创建一个新的计数器账户
await program.methods
.initialize()
.accounts({
// 指定支付者和新创建的计数器账户
payer: payer.publicKey,
counter: counterKeypair.publicKey,
})
.signers([counterKeypair]) // 使用新生成的密钥对签名
.rpc(); // 发送并执行交易
// 从链上获取计数器账户的当前状态
const currentCount = await program.account.counter.fetch(
counterKeypair.publicKey
);
// 打印当前计数器的状态
console.log(currentCount);
// 断言计数器的初始值应该为0
assert(
currentCount.count.toNumber() === 0,
"Expected initialized count to be 0"
);
});
// 测试用例:递增计数器(1)
it("递增(1)", async () => {
// 调用智能合约的increment方法来增加计数器的值
await program.methods
.increment()
.accounts({ counter: counterKeypair.publicKey }) // 指定要操作的计数器账户
.rpc(); // 发送并执行交易
// 从链上获取计数器账户的当前状态
const currentCount = await program.account.counter.fetch(
counterKeypair.publicKey
);
// 打印当前计数器的状态
console.log(currentCount);
// 断言计数器的值应该为1
assert(currentCount.count.toNumber() === 1, "Expected count to be 1");
});
// 测试用例:递增计数器(2)
it("递增(2)", async () => {
// 再次调用increment方法,再次增加计数器的值
await program.methods
.increment()
.accounts({ counter: counterKeypair.publicKey }) // 指定要操作的计数器账户
.rpc(); // 发送并执行交易
// 从链上获取计数器账户的当前状态
const currentCount = await program.account.counter.fetch(
counterKeypair.publicKey
);
// 打印当前计数器的状态
console.log(currentCount);
// 断言计数器的值应该为2
assert(currentCount.count.toNumber() === 2, "Expected count to be 2");
});
});
运行测试
在项目根目录下运行以下命令来执行测试脚本:
anchor test
你应该会看到类似如下的输出:
恭喜您!现在您已经成功掌握了编写一个简单的Solana程序的方法。
最后
如果您还有其他疑问,比如什么是“账户”以及Anchor框架中的Accounts宏是什么,不用担心,这些问题在后续的教程中都会逐一解答。如果您觉得这个系列对您有帮助,请记得收藏并关注,以获得更多更新和支持。谢谢!