这篇文章,我们从工程化的角度来带领大家使用 anchor 框架来实现一个 Bank 链上程序。
大家好,感谢你们点开这篇文章!如果有兴趣,欢迎关注我的 GitHub,里面有一些我的小项目和代码,水平有限,还请多多指教!
在 手把手带你写一个solana程序:计数器合约 这篇文章中,我们实现了一个简单的计数器合约,并从源码的层面对其中涉及到的一些概念做了梳理,想必大家已经掌握了基本的概念。深入理解账户的概念能帮助我们更好的掌握开发套路。
这篇文章,我们从工程化的角度来带领大家使用 anchor 框架来实现一个 Bank 链上程序。
在 一文说透,如何在solana上铸造spl-token 这篇文章中,我已经详细的介绍了开发环境的相关配置,这里不再赘述,有需要的朋友可以移步这里查看。
1、我们在本地查看环境配置:
➜ ~ solana config get
Config File: /Users/louis/.config/solana/cli/config.yml
RPC URL: http://localhost:8899
WebSocket URL: ws://localhost:8900/ (computed)
Keypair Path: /Users/louis/.config/solana/id.json
Commitment: confirmed
从上面的输出可以看到,我目前连接的是本地环境,这篇文章,我们就使用这个环境来给大家做演示。
1、找一个空的目录,使用 anchor init 命令进行项目初始化:
➜ anchor init solana_bank_demo
执行上面命令之后,会使用 yarn 命令初始化项目,项目初始化之后,我们用编辑器打开,项目长这个样子。
2、查看一些常用的配置文件:
programs/solana_bank_demo/Cargo.toml:
[package]
name = "solana_bank_demo"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
name = "solana_bank_demo"
[features]
default = []
cpi = ["no-entrypoint"]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
idl-build = ["anchor-lang/idl-build"]
[dependencies]
anchor-lang = "0.31.0"
我们来梳理下每个配置的作用,这里有一个坑点,我们稍后回来解决。
### [package] 部分
- `name`: 定义包的名称为 "solana_bank_demo"
- `version`: 包的版本号,遵循语义化版本规范
- `description`: 包的描述信息
- `edition`: Rust 版本,使用 2021 版本
### [lib] 部分
- `crate-type`: 指定生成的库类型
- `cdylib`: 生成动态链接库,用于与其他语言交互
- `lib`: 生成 Rust 标准库
- `name`: 指定库的名称
### [features] 部分(Solana/Anchor 特定功能)
- `default`: 默认启用的特性列表
- `cpi`: Cross-Program Invocation 特性,用于程序间调用
- `no-entrypoint`: 禁用程序入口点,通常在作为依赖被其他程序调用时使用
- `no-idl`: 禁用 IDL(Interface Description Language)生成
- `no-log-ix-name`: 禁用指令名称日志记录
- `idl-build`: 启用 Anchor IDL 构建功能
### [dependencies] 部分
- `anchor-lang`: 依赖 Anchor 框架,版本 0.31.0,这是 Solana 智能合约开发的主要框架
/solana_bank_demo/Anchor.toml:
[toolchain]
package_manager = "yarn"
[features]
resolution = true
skip-lint = false
[programs.localnet]
solana_bank_demo = "G3ABM8zJtyFrJF5e2D5CwvRxh7jDqHxFd9Rw6MF7uMpX"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
<!---->
### [toolchain] 部分
- `package_manager`: 指定使用 yarn 作为包管理器
### [features] 部分
- `resolution`: 启用依赖解析功能
- `skip-lint`: 设置为 false,表示不跳过代码检查
### [programs.localnet] 部分
- `solana_bank_demo`: 定义程序 ID,这是程序在 Solana 网络上的唯一标识符
- `"G3ABM8zJtyFrJF5e2D5CwvRxh7jDqHxFd9Rw6MF7uMpX"` 是程序的公钥地址
### [registry] 部分
- `url`: 指定 Anchor 包注册表的 URL,用于发布和下载 Anchor 包
### [provider] 部分
- `cluster`: 设置为 "localnet",表示使用本地测试网络
- `wallet`: 指定钱包密钥对的路径,这里使用的是默认的 Solana CLI 钱包路径
### [scripts] 部分
- `test`: 定义测试命令
- 使用 ts-mocha 运行测试
- `-p ./tsconfig.json`: 指定 TypeScript 配置文件
- `-t 1000000`: 设置测试超时时间为 1000000 毫秒
- `tests/**/*.ts`: 运行 tests 目录下所有的 TypeScript 测试文件
我们需要重点关注的是 programs.localnet 这个部分,这里显示的是程序的公钥地址。
3、程序的公钥地址是如何生成的呢?
我们在初始化项目的时候,anchor 框架自动的帮我们生成了这个公钥地址,并且在 target/deploy 目录中,还生成了一个 json 文件,格式就是 program_name-keypair.json,所以我这个项目当前的 文件名称就是:solana_bank_demo-keypair.json
需要注意的是,程序 ID 是确定性的,由程序的密钥对唯一决定,在本地开发时,每次重新生成密钥对都会得到新的程序 ID,在生产环境中,应该妥善保管程序的密钥对文件,因为它关系到程序的所有权和更新权限。
我们迫不及待想执行 build 操作,来构建我们的程序。
anchor build
<!---->
➜ solana_bank_demo git:(master) ✗ anchor build
error: rustc 1.79.0-dev is not supported by the following package:
Note that this is the rustc version that ships with Solana tools and not your system's rustc version. Use `solana-install update` or head over to https://docs.solanalabs.com/cli/install to install a newer version.
bytemuck_derive@1.9.2 requires rustc 1.84
Either upgrade rustc or select compatible dependency versions with
`cargo update <name>@<current-ver> --precise <compatible-ver>`
where `<compatible-ver>` is the latest version supporting rustc 1.79.0-dev
不出意外的话,肯定会出意外,直接报错,其实这个报错的原因就是 solana cli 中带有的 rustc 工具版本和我本地的安装的一个包不太兼容。bytemuck_derive\@1.9.2 requires rustc 1.84,我本地安装的 rust 版本比较新:
➜ solana_bank_demo git:(master) ✗ rustup --version
rustup 1.28.1 (f9edccde0 2025-03-05)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.85.0 (4d91de4e4 2025-02-17)`
如何解决呢?我尝试了一些方法,最终的解决方法是 对这个包进行降级:
➜ solana_bank_demo git:(master) ✗ cargo update bytemuck_derive@1.9.2 --precise 1.8.1
Updating crates.io index
Downgrading bytemuck_derive v1.9.2 -> v1.8.1
然后再次执行 anchor build,经过漫长的编译之后,就构建成功了。
在 tests 目录下,有一个测试文件,当我们指定 anchor test 的时候,就会执行这个测试文件。
➜ solana_bank_demo git:(master) ✗ anchor test
<!---->
solana_bank_demo
Your transaction signature jmSFrCk1LvaeB9P9CtZbLsQaSnvFn9VXrTbpVDKyN3Ta9yUfoCfyvAaQevYvebQqoebosNdqSKsXLTcuWaoV1uc
✔ Is initialized! (265ms)
1 passing (266ms)
✨ Done in 2.25s.
如果显示出上面的打印内容,说明测试的脚本成功执行了。
到这里,我们还没有看,脚手架给我们默认生成的程序结构呢:
use anchor_lang::prelude::*;
declare_id!("G3ABM8zJtyFrJF5e2D5CwvRxh7jDqHxFd9Rw6MF7uMpX");
#[program]
pub mod solana_bank_demo {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!("Greetings from: {:?}", ctx.program_id);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
其实很简单对吧,我们也并不需要关心它的内容,因为我们会把这部分内容全部删掉,然后写我们自己的逻辑。
在此之前,我们先测试一下部署到本地的测试环境:
➜ solana_bank_demo git:(master) ✗ solana-test-validator
Ledger location: test-ledger
Log: test-ledger/validator.log
⠈ Initializing... Waiting for fees to stabilize 1...
Identity: FNR8SCwvQwdcjdGhzZdnoEK77RRrMDGsG4XJJpUPht19
Genesis Hash: 2c8WcTPfKsSNTsxdae3h2tkWU3uQWTRVi3dnVF6j1VcV
Version: 2.1.17
Shred Version: 47570
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
⠐ 00:00:07 | Processed Slot: 16 | Confirmed Slot: 15 | Finalized Slot: 0 | Full Snapshot Slot: - | Incremental Snapshot Slot: - | Transactions: 15 | ◎499.999930000
我使用了 solana-test-validator 命令启动了测试环境,怎么验证这个环境有没有启动成功呢?我们可以打开浏览器链接本地的端口查看,看到出块信息说明我们的本地环境已经搭建好了。
执行 anchor deploy 命令,部署我们的程序:
➜ solana_bank_demo git:(master) ✗ anchor deploy
Deploying cluster: http://127.0.0.1:8899
Upgrade authority: /Users/louis/.config/solana/id.json
Deploying program "solana_bank_demo"...
Program path: /Users/louis/Documents/myProject/Solana_project/solana_bank_demo/target/deploy/solana_bank_demo.so...
Program Id: G3ABM8zJtyFrJF5e2D5CwvRxh7jDqHxFd9Rw6MF7uMpX
Signature: 4cygxMjzD18vuv5iZTDQYd6vMuvM3FmxPHLtPBKuipPiT6Lb2XyNS5AdEns7SMWyjAPFM3uXVMYpUw9YH4xPwFAT
Deploy success
浏览器能够看到相应的部署信息,说明我们的程序部署完成。
bank 合约的功能比较简单:
好了,上面就是我们合约的全部需求了。我这里贴上提前写好的程序:
use anchor_lang::prelude::*;
use anchor_lang::solana_program::system_instruction;
declare_id!("98Frhx1UD56goz5ns2vTR6WaqHLPsJE7Jux8iWgJr3Fv");
#[program]
pub mod solana_bank_demo {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!("Initializing bank contract");
let bank = &mut ctx.accounts.bank;
bank.owner = ctx.accounts.owner.key();
bank.total_balance = 0;
Ok(())
}
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
const MIN_DEPOSIT: u64 = 10_000_000; // 0.01 SOL
msg!(
"Processing deposit of {} lamports from user: {}",
amount,
ctx.accounts.user.key()
);
require!(amount >= MIN_DEPOSIT, BankError::DepositTooSmall);
let transfer_instruction = system_instruction::transfer(
&ctx.accounts.user.key(),
&ctx.accounts.bank.key(),
amount,
);
anchor_lang::solana_program::program::invoke(
&transfer_instruction,
&[
ctx.accounts.user.to_account_info(),
ctx.accounts.bank.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
)?;
let user_account = &mut ctx.accounts.user_account;
let old_balance = user_account.balance;
user_account.balance = user_account.balance.checked_add(amount).unwrap();
let bank =...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!