通过本教程,你将轻松上手 Anchor 框架,学习如何在 Solana 上创建、部署、测试智能合约,并通过实际示例掌握常用命令和调试技巧。
avm
(类似于 Node.js 的 nvm
)。我安装的相关版本如下:
rustc 1.75.0 (82e1608df 2023-12-21)
solana-cli 1.18.17 (src:c027cfc3; feat:4215500110, client:Agave)
anchor-cli 0.30.1
anchor help
帮助指令 anchor help
anchor help [subcommand]
anchor help deploy
显示 deploy
命令的用法和选项。创建新项目:
anchor init my_project
构建程序:
anchor build
target/deploy
目录下生成编译后的合约二进制文件。测试程序:
anchor test
部署程序:
anchor deploy
Anchor.toml
的 [provider]
配置项中修改 cluster
,例如:
cluster = "Devnet"
创建新的程序:
anchor new <program-name>
anchor init
初始化工作空间,然后使用 anchor new
创建程序。升级程序
anchor upgrade <target/deploy/my_project.so> --program-id <program-id>
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
Anchor.toml
:项目的配置文件。programs/
:包含程序的目录,每个程序有自己的子目录。Cargo.toml
:程序的 Rust 项目配置文件。src/
:包含程序代码文件,通常是 lib.rs
。tests/
:包含测试代码文件。target/
:包含构建和编译生成的文件。执行以下命令初始化项目:
anchor init counter_anchor
创建项目后,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
在 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<InitializeCounter>) -> Result<()> {
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
ctx.accounts.counter.count = ctx.accounts.counter.count.checked_add(1).unwrap();
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializeCounter<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
space = 8 + Counter::INIT_SPACE,
payer = payer
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}
#[account]
#[derive(InitSpace)]
pub struct Counter {
count: u64,
}
然后同步程序 ID:
anchor keys sync
同样,我们把测试脚本也替换成以下代码:
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<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)
与 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 中,查询账户数据并不依赖合约地址,而是直接通过账户公钥来访问。
修改 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 config set --url https://api.devnet.solana.com
为了全额退回已支付的租金,可以使用 solana program close <PROGRAM_ID>
命令来关闭 Solana 程序。例如:
solana program close 4HAbxhZ8twJfwbbGc8jpQUqLSr63cwScm8RsQv1wYkTy --bypass-warning
执行该命令时,Solana 可能会提示警告信息。若要跳过这些警告,可以加上 --bypass-warning
选项。
注意:一旦程序被关闭,其 Program ID 将无法再用于部署新的程序,请谨慎操作。
成功关闭程序后,终端将返回类似以下的消息,表示已回收租金:
Closed Program Id 4HAbxhZ8twJfwbbGc8jpQUqLSr63cwScm8RsQv1wYkTy, 1.4170908 SOL reclaimed
这样,程序占用的 SOL 资源将被释放并退回到你的账户。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!