(三)Solana 入门指南——Anchor框架

1,946 阅读5分钟

(三)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

你应该会看到类似如下的输出:

image-20241101003210250.png

恭喜您!现在您已经成功掌握了编写一个简单的Solana程序的方法。

最后

如果您还有其他疑问,比如什么是“账户”以及Anchor框架中的Accounts宏是什么,不用担心,这些问题在后续的教程中都会逐一解答。如果您觉得这个系列对您有帮助,请记得收藏并关注,以获得更多更新和支持。谢谢!