通过示例学习 Anchor 框架:轻松上手 Solana 开发

  • 0xE
  • 更新于 9小时前
  • 阅读 131

通过本教程,你将轻松上手 Anchor 框架,学习如何在 Solana 上创建、部署、测试智能合约,并通过实际示例掌握常用命令和调试技巧。

Anchor CLI

1. Anchor CLI 简介

  • Anchor 是一个基于 Rust 的 Solana 开发框架,支持智能合约的开发,并提供了完整的工具链来简化开发流程。
  • 依赖安装:需要安装 Rust、Solana、Yarn 等依赖,然后安装 Anchor 版本管理工具 avm(类似于 Node.js 的 nvm)。
  • 安装指南Anchor 安装指南

我安装的相关版本如下:

rustc 1.75.0 (82e1608df 2023-12-21)
solana-cli 1.18.17 (src:c027cfc3; feat:4215500110, client:Agave)
anchor-cli 0.30.1

2. anchor help 帮助指令

  • 打印所有可用命令
     anchor help
  • 获取子命令帮助信息
     anchor help [subcommand]
    • 例如:anchor help deploy 显示 deploy 命令的用法和选项。

3. 常用指令

  • 创建新项目

     anchor init my_project
    • 创建一个包含示例代码的新 Anchor 项目。
    • 注意项目名需要使用蛇形命名。
  • 构建程序

     anchor build
    • target/deploy 目录下生成编译后的合约二进制文件。
    • 常用选项:
      • --skip-lint: 跳过代码静态检查。
      • --features <features>: 指定编译特性。
  • 测试程序

     anchor test
    • 常用选项:
      • --skip-build: 跳过构建步骤,仅运行测试。
      • --bpf: 使用 BPF 测试,更接近实际部署环境。
      • --features <features>: 指定编译特性。
  • 部署程序

     anchor deploy
    • 将程序部署到指定的 Solana 网络。
    • Anchor.toml[provider] 配置项中修改 cluster,例如:
      cluster = "Devnet"
  • 创建新的程序:

     anchor new &lt;program-name>
    • 在工作空间中创建一个新的程序(智能合约)。
    • 通常先使用 anchor init 初始化工作空间,然后使用 anchor new 创建程序。
  • 升级程序

     anchor upgrade &lt;target/deploy/my_project.so> --program-id &lt;program-id>
    • 升级步骤
      1. 部署最新版本的程序到 Solana 区块链。
      2. 初始化接口定义(IDL),使客户端能够正确解析合约数据结构和方法。
      3. 执行所有必要的迁移脚本,确保链上合约状态的平滑过渡。
    • 账户空间不足错误: 如果升级的时候遇到 account data too small for instruction 报错,则需要调整空间后再升级
      solana program extend PROGRAM_ID 60000 -u d -k KEYPAIR_FILE_PATH
      # 例如调整到 60000 (60 KB)
      solana program extend FwtCTkoxTtWRYZZMpeTfsvHwHwU6YmH5KfvknA2bdqfL 60000 -u d -k ~/.config/solana/id.json

4. 项目目录结构

  • Anchor.toml:项目的配置文件。
  • programs/:包含程序的目录,每个程序有自己的子目录。
  • Cargo.toml:程序的 Rust 项目配置文件。
  • src/:包含程序代码文件,通常是 lib.rs
  • tests/:包含测试代码文件。
  • target/:包含构建和编译生成的文件。

示例:计数器合约

  1. 创建新项目

执行以下命令初始化项目:

anchor init counter_anchor
  1. 获取程序 ID

创建项目后,Anchor 会自动生成一个程序 ID,可以在 Anchor.toml 和 lib.rs 的宏中找到。

我们可以使用 solana-keygen 指令查看当前的程序 ID:

solana-keygen pubkey target/deploy/counter_anchor-keypair.json

如果需要生成新的程序 ID,可以执行以下命令:

solana-keygen new -o target/deploy/counter_anchor-keypair.json --force
  1. 编写计数器合约

在 programs/counter_anchor/src/lib.rs 中替换为以下代码:

#![allow(clippy::result_large_err)]

use anchor_lang::prelude::*;

declare_id!("BmDHboaj1kBUoinJKKSRqKfMeRKJqQqEbUj1VgzeQe4A");

#[program]
pub mod counter_anchor {
    use super::*;

    pub fn initialize_counter(_ctx: Context&lt;InitializeCounter>) -> Result&lt;()> {
        Ok(())
    }

    pub fn increment(ctx: Context&lt;Increment>) -> Result&lt;()> {
        ctx.accounts.counter.count = ctx.accounts.counter.count.checked_add(1).unwrap();
        Ok(())
    }
}

#[derive(Accounts)]
pub struct InitializeCounter&lt;'info> {
    #[account(mut)]
    pub payer: Signer&lt;'info>,

    #[account(
        init,
        space = 8 + Counter::INIT_SPACE,
        payer = payer
    )]
    pub counter: Account&lt;'info, Counter>,
    pub system_program: Program&lt;'info, System>,
}

#[derive(Accounts)]
pub struct Increment&lt;'info> {
    #[account(mut)]
    pub counter: Account&lt;'info, Counter>,
}

#[account]
#[derive(InitSpace)]
pub struct Counter {
    count: u64,
}

然后同步程序 ID:

anchor keys sync
  1. 编写测试脚本

同样,我们把测试脚本也替换成以下代码:

import * as anchor from '@coral-xyz/anchor';
import type { Program } from '@coral-xyz/anchor';
import { Keypair } from '@solana/web3.js';
import { assert } from 'chai';
import type { CounterAnchor } from '../target/types/counter_anchor';

describe('counter_anchor', () => {
  // Configure the client to use the local cluster.
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);
  const payer = provider.wallet as anchor.Wallet;

  const program = anchor.workspace.CounterAnchor as Program&lt;CounterAnchor>;

  // Generate a new keypair for the counter account
  const counterKeypair = new Keypair();

  it('Initialize Counter', async () => {
    await program.methods
      .initializeCounter()
      .accounts({
        counter: counterKeypair.publicKey,
        payer: payer.publicKey,
      })
      .signers([counterKeypair])
      .rpc();

    const currentCount = await program.account.counter.fetch(counterKeypair.publicKey);

    assert(currentCount.count.toNumber() === 0, 'Expected initialized count to be 0');
  });

  it('Increment Counter', async () => {
    await program.methods.increment().accounts({ counter: counterKeypair.publicKey }).rpc();

    const currentCount = await program.account.counter.fetch(counterKeypair.publicKey);

    assert(currentCount.count.toNumber() === 1, 'Expected  count to be 1');
  });

  it('Increment Counter Again', async () => {
    await program.methods.increment().accounts({ counter: counterKeypair.publicKey }).rpc();

    const currentCount = await program.account.counter.fetch(counterKeypair.publicKey);

    assert(currentCount.count.toNumber() === 2, 'Expected  count to be 2');
  });
});

直接运行 anchor test,会先编译再测试。因为我们使用的默认 Anchor.toml,配置为:

[provider]
cluster = "Localnet"

所以,在测试时,Anchor 会使用本地 solana-test-validator,这个本地网络只在测试运行期间有效,测试完成后会被销毁。

正常情况下,可以看到测试通过。

  counter_anchor
    ✔ Initialize Counter (601ms)
    ✔ Increment Counter (465ms)
    ✔ Increment Counter Again (465ms)

在本地部署并且使用 CLI 查询

与 EVM 不同,Solana 采用程序与数据分离的设计。在我们的程序中,我们会新建一个密钥对,并将其交给程序来初始化账户数据。因此,想要查询计数器中的具体数据时,我们不能直接通过程序 ID 进行查询,而是需要使用账户的公钥。

通过以下示例,你将更直观地理解这个过程!

首先,打开一个新的终端,手动启动本地 Solana 节点:

solana-test-validator

然后,在测试脚本中添加一行代码,打印出输入给程序的账户地址:

console.log('Counter Keypair:', counterKeypair.publicKey.toBase58());

接着,运行测试,同时使用 --skip-local-validator 标志,避免自动启动本地 Solana 验证节点:

anchor test --skip-local-validator

测试运行后,程序将部署到本地节点,并在终端输出随机生成的公钥,例如:

Counter Keypair: 714hf7qcXREvtvWEKYSn4w9ujxD3c5JR5oUCJoQ7NGha

这个地址就是用于初始化计数器的账户地址。测试完成后,该账户的 count 值应为 2。

此时,我们可以使用 anchor account 命令来查询该账户的信息:

anchor account counter_anchor.Counter 714hf7qcXREvtvWEKYSn4w9ujxD3c5JR5oUCJoQ7NGha   

返回的结果如下:

{
  "count": 2
}

这里,counter_anchor.Counter 读取了 IDL 文件,而后面的账户地址用于查询数据。可以看到,在 Solana 中,查询账户数据并不依赖合约地址,而是直接通过账户公钥来访问。

在 Dev 网络上部署测试

修改 Anchor.toml 配置文件,将 Localnet 改为 Devnet。或者 "Devnet" 直接放私有的 RPC 链接。

[provider]
cluster = "Devnet"
wallet = "~/.config/solana/id.json"

确保 "~/.config/solana/id.json" 对应的账户在 Dev 网络上有测试代币。然后运行 anchor test

如果遇到网络问题,比如:

Error: AccountNotFound: pubkey=4Ak8RNHDqWQCyjxVMk1W95DDDW4ntaM92Qq6kEBKAHvV: error decoding response body: unexpected end of file
There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.

那么就像之前文章说过的,可以试试 proxychains4 代理以及 Helius 申请私有 RPC。

结果如下:

Counter Keypair: 6NFQPBVMnoHDc3ruiw52sxPDAZAziAoqGBSnWcJr35kH

  counter_anchor
    ✔ Initialize Counter (5579ms)
    ✔ Increment Counter (4108ms)
    ✔ Increment Counter Again (3290ms)

然后我们来试试 anchor account 指令来在 Dev 网络上查询,能得到正确结果:

anchor account counter_anchor.Counter 6NFQPBVMnoHDc3ruiw52sxPDAZAziAoqGBSnWcJr35kH

{
  "count": 2
}

关闭 Solana 程序

首先,我们需要配置 Solana 命令行工具(solana)的网络:

solana config set --url https://api.devnet.solana.com

为了全额退回已支付的租金,可以使用 solana program close &lt;PROGRAM_ID> 命令来关闭 Solana 程序。例如:

solana program close 4HAbxhZ8twJfwbbGc8jpQUqLSr63cwScm8RsQv1wYkTy --bypass-warning

执行该命令时,Solana 可能会提示警告信息。若要跳过这些警告,可以加上 --bypass-warning 选项。

注意:一旦程序被关闭,其 Program ID 将无法再用于部署新的程序,请谨慎操作。

成功关闭程序后,终端将返回类似以下的消息,表示已回收租金:

Closed Program Id 4HAbxhZ8twJfwbbGc8jpQUqLSr63cwScm8RsQv1wYkTy, 1.4170908 SOL reclaimed

这样,程序占用的 SOL 资源将被释放并退回到你的账户。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
0xE
0xE
0x59f6...a17e
17年进入币圈,Web3 开发者。刨根问底探链上真相,品味坎坷悟 Web3 人生。工作机会可加v:__0xE__