Solana系列文章第3篇,在上一篇,我们使用了Sol在线IDEsolpg部署了一个简单的favorites程序,对于较大的项目,我们通常需要在本地进行工程化开发,在本地使用Anchor构建程序
Solana 系列文章第 3 篇,在上一篇,我们使用了 Sol 在线 IDE solpg 部署了一个简单的 favorites
程序,对于较大的项目,我们通常需要在本地进行工程化开发,在本地使用 Anchor 构建程序,这就是今天这篇文章的内容:
create-solana-dapp
新建工程 在开始之前,我们需要准备好开发 Solana DApp 所需的环境, 整个安装过程大体上根据 Anchor 安装说明: https://www.anchor-lang.com/docs/installation
Anchor 是使用 Rust 开发的,我们需要安装 Rust 来编译Anchor,后面也需要用 Rust 来编译 Solana 合约,因此我们先安装 Rust。
安装 Rust 推荐使用 rustup , 这样方便之后我们切换不同的rust 版本:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
在弹出的安装选项中,选择默认选项即可。
如果网络不好,可能等待会比较长。
安装完成后,可以用 rustup --version
看一下当前的版本:
rustup --version
rustup 1.27.1 (54dd3d00f 2024-04-24)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.79.0 (129f3b996 2024-06-10)`
可以看到我这里的版本是 rustup 1.27.1, rustc 为 1.79.0 , 此时说明已经正确安装了 rust.
Solana 命令行工具用于与 Solana 网络交互,包括部署和管理程序, 安装参考:https://solana.com/docs/intro/installation
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
stable
可以替换为指定的版本, 脚本执行后按提示添加环境变量,之后你可以这样验证一个安装:
> solana --version
solana-cli 1.18.18 (src:83047136; feat:4215500110, client:SolanaLabs)
可以看到我这里的 solana 版本是 1.18.18
DApp 需要在前端和 Solana 链,我们用 NVM 来安装管理不同的 Node.js 版本, 参考这里。
安装 Anchor 推荐使用 avm (Anchor version manager) 来安装,使用 cargo
来安装(cargo 是 rust 的包管理工具):
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
我在这步安装遇到了一点问题,直到我把 rust 切换到 nightly,并且设置 CARGO_NET_GIT_FETCH_WITH_CLI 才搞定:
> rustup install nightly
> export CARGO_NET_GIT_FETCH_WITH_CLI=true
由于 Solana 和 Anchor 都在不断的更新中,所依赖的 rust 相关工具链版本也在不断变化,这些版本问题确实有一点坑。
安装完成后,验证版本:
avm --version
然后用 avm 安装 Anchor :
> avm use latest
安装完成后,验证 anchor 版本:
> anchro --version
anchor-cli 0.30.1
这样所需要的环境就安装好了。
create-solana-dapp 是一个脚手架工具,为我们设置一个包含各种配置选项的 Solana Web 项目,包括 Next.js 客户端、Tailwind UI 库和一个简单的 Anchor 程序。
工程的完整代码,可以参考这里 。
现在我们来创建一个 favorites
工程:
npx create-solana-dapp
命令会提示输入项目名称、前端模版框架:
cd favorites
项目结构有两个主要的目录:
anchor/
:存放 Anchor 工程代码代码。src/
:存放前端应用代码。在项目创建完成后,我们可以 VS Code 打开工程,我们需要编写、编译并部署智能合约。
在 anchor/programs
目录下,你会看到默认生成的 Anchor 合约示例。以 favorites
为例,打开 anchor/programs/favorites/src/lib.rs
,用上一篇文章的 favorites
替换 lib.rs
。
在项目根目录下,运行以下命令编译合约:
anchor build
你可能会遇到错误:error: init_if_needed requires that anchor-lang be imported with the init-if-needed cargo feature
这是需要我们把 anchor/programs/favorites/cargo.toml
修改一下依赖的 feature :
...
[dependencies]
anchor-lang = { version = "0.30.1", features = ["init-if-needed"] }
...
Cargo.toml
文件是管理 Rust 项目依赖、编译配置、项目包管理及元信息的文件。决定“这个程序是如何编译、依赖什么库,以及采用哪些 Rust 特性”。
再次运行 anchor build
,执行成功后,在 target/
目录下生成一系列构建的文件,最重要的几个文件有:
target/deploy/<program-name>.so
),这是最终部署到 Solana 链上的可执行文件(ELF 文件)。target/deploy/<program-name>-keypair.json
), 记录程序的密钥和公钥。在 anchor deploy
时,Anchor CLI 会使用此 Keypair 将程序部署到链上,并将其 公钥 注册为 Program ID。target/idl/<program-name>.json
) ,里面包含了程序的程序 ID,以及所有方法、数据结构等描述,客户端就是通过这个文件来进行对智能合约的调用。部署合约前,我们需要确定部署的集群,以及部署程序的账号,这些是在 Anchor.toml
中配置的。
Anchor.toml
是 Anchor 框架 特有的项目配置文件,用来配置程序、网络和钱包选项。决定"部署到哪、用哪个钱包、项目中包含哪些程序。
Anchor.toml 包含的所有配置项,可以在这里 找到,当下我们使用到的有:
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
这里配置的使用本地网络的集群,这个集群是通过 solana-test-validator
启动的。
运行 solana-test-validator
启动本地网络集群:
> solana-test-validator
Ledger location: test-ledger
Log: test-ledger/validator.log
⠉ Initializing... Waiting for fees to stabilize 1...
Identity: Fzsq7WHnBVLCcwaeXnjYVYDsTuTT4izcPCHHFjbYasmK
Genesis Hash: 3fzCr16ffwszZDrTYX3z4775PC1hMpfpxfur6HE83LAh
Version: 1.18.18
Shred Version: 766
Gossip Address: 127.0.0.1:1024
TPU Address: 127.0.0.1:1027
JSON RPC URL: http://127.0.0.1:8899
WebSocket PubSub URL: ws://127.0.0.1:8900
现在创建一个账号用来部署合约:
> solana-keygen new -o /Users/emmett/.config/solana/id.json
Wrote new keypair to /Users/emmett/.config/solana/id.json
==========================================================================
pubkey: 6oLiQn73H8EWnbo5sSuFx1V4KNAasBgFP39puLR9Emaw
==========================================================================
Save this seed phrase and your BIP39 passphrase to recover your new keypair:
test test test test test test .....
==========================================================================
部署合约之前,需要确保本地有足够的 SOL 余额。你可以通过以下命令获取测试 SOL:
> solana airdrop 2 -u http://127.0.0.1:8899
Requesting airdrop of 2 SOL
...
-u
用来指定网络集群端点。
使用以下命令查询余额:
solana balance -u http://127.0.0.1:8899
也可以在浏览器上 https://explorer.solana.com/ ,链接本地集群查询账号信息:
使用 anchor deploy
部署合约
> anchor deploy
Deploying cluster: http://127.0.0.1:8899
Upgrade authority: /Users/emmett/.config/solana/id.json
Deploying program "favorites"...
Program path: /Users/emmett/SolanaProject/favorites/anchor/target/deploy/favorites.so...
Program Id: 2RGcHFUgJ4R8jxgQjLy1dF7p5kuUFK7aUKqyAxDcBnjH
部署成功后,控制台会打印出新部署的程序 ID。
小技巧,部署后,可以使用命令
anchor keys sync
把 Anchor.toml 和 lib.rs 中的program id
更新一下
Bankrun 是一个专门为 Solana 程序测试设计的、使用 NodeJS 进行测试框架, 提供了很多优秀的功能,例如:直接在内存中模拟 Solana 运行时环境,测试执行速度比传统方法快 10-100 倍,支持完整的账户状态控制(如设置账户余额、数据),丰富的测试 API(如模拟各种错误情况) 等。
在 anchor 目录下,通过以下命令安装:anchor-bankrun
:
npm install anchor-bankrun
create-solana-dapp
创建工程时, 在 anchor/tests/
目录下,已经包含了一个测试模版文件 favorites.spec.ts
,我们用以下代码替换 favorites.spec.ts
文件。
// 导入必要的依赖
import * as anchor from '@coral-xyz/anchor'
import { Program } from '@coral-xyz/anchor'
import { PublicKey, SystemProgram } from '@solana/web3.js'
import { BankrunProvider, startAnchor } from "anchor-bankrun";
// 导入程序类型定义,这是由 Anchor 自动生成的
import { Favorites } from '../target/types/favorites'
// 导入程序的 IDL (Interface Description Language) 文件
const IDL = require("../target/idl/favorites.json");
// 定义程序 ID,应该与你部署的程序地址匹配
const PROGRAM_ID = new PublicKey("coUnmi3oBUtwtd9fjeAvSsJssXh5A5xyPbhpewyzRVF");
describe('favorites', () => {
it('设置收藏', async () => {
// 初始化 Bankrun 测试环境
const context = await startAnchor("", [{name: "favorites", programId: PROGRAM_ID}], []);
// 创建 Bankrun Provider,用于与程序交互
const provider = new BankrunProvider(context);
// 使用 IDL 和 Provider 创建程序实例
const favoritesProgram = new Program<Favorites>(
IDL,
provider,
);
// 计算 PDA (Program Derived Address)
// seeds: ["favorites", 用户公钥]
const [favoritePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("favorites"),
provider.wallet.publicKey.toBuffer()
],
favoritesProgram.programId
)
// 准备测试数据
const favoriteNumber = new anchor.BN(42) // 使用 BN 处理大数
const favoriteColor = "red"
const hobbies = ["book", "sport", "programming"]
// 调用程序的 set_favorites 指令
// Anchor 会自动推导所需的账户,无需手动指定
await favoritesProgram.methods
.setFavorites(
favoriteNumber,
favoriteColor,
hobbies
)
.rpc()
// 从链上获取账户数据并验证
const favoriteData = await favoritesProgram.account.favorites.fetch(favoritePda)
// 验证存储的数据是否与输入匹配
expect(favoriteData.number.toString()).toEqual(favoriteNumber.toString())
expect(favoriteData.color).toEqual(favoriteColor)
expect(favoriteData.hobbies).toEqual(hobbies)
})
})
在 Anchor Bankrun 测试中不需要显式指定 accounts,Anchor 会根据指令(instruction)的定义自动推导所需的账户。
startAnchor 参数, 可参考相关的文档 。
在项目根目录下,运行以下命令:
anchor test
如果运行 anchor test
时出现错误:
Error: Your configured rpc port: 8899 is already in use
这是因为已经有一个 solana-test-validator
实例正在运行,而 Anchor 在运行测试时会尝试启动一个新的本地验证节点(也使用端口8899),所以会报错。
我们可以使用 --skip-local-validator
和 --skip-deploy
参数来跳过本地验证节点和合约部署:
> anchor test --skip-local-validator --skip-deploy
...
PASS tests/favorites.spec.ts (10.049 s)
test favorites
✓ 设置收藏 (202 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.097 s
这样就完成了合约的测试了。
我们在这篇文章中详细介绍了如何使用 Anchor 在本地搭建 Solana 开发环境, 主要是要注意 Rust 工具链的版本问题。
使用 create-solana-dapp
创建新的 Solana DApp 项目,并演示了通过 anchor 命令行工具来编译、部署、测试合约,主要的命令有:
anchor build
编译合约,会生成程序二进制文件、keypair、IDL文件。anchor deploy
部署合约,会使用 keypair 部署合约,并更新 Anchor.toml
和 lib.rs
中的 program id
。anchor test
进行合约测试,测试用例会使用前面的IDL 文件和程序 ID,并使用 bankrun
进行合约测试。如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!