大家好,感谢你们点开这篇文章!如果有兴趣,欢迎关注我的 GitHub,里面有一些我的小项目和代码,水平有限,还请多多指教!
相关背景:
在 手把手带你写一个solana程序:计数器合约 这篇文章中,我们实现了一个简单的计数器合约,并从源码的层面对其中涉及到的一些概念做了梳理,想必大家已经掌握了基本的概念。深入理解账户的概念能帮助我们更好的掌握开发套路。
这篇文章,我们从工程化的角度来带领大家使用 anchor 框架来实现一个 Bank 链上程序。
环境准备:
在 一文说透,如何在solana上铸造spl-token 这篇文章中,我已经详细的介绍了开发环境的相关配置,这里不再赘述,有需要的朋友可以移步这里查看。
1、我们在本地查看环境配置:
arduino
➜ ~ 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 命令进行项目初始化:
csharp
➜ anchor init solana_bank_demo
执行上面命令之后,会使用 yarn 命令初始化项目,项目初始化之后,我们用编辑器打开,项目长这个样子。
2、查看一些常用的配置文件:
programs/solana_bank_demo/Cargo.toml:
ini
[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"
我们来梳理下每个配置的作用,这里有一个坑点,我们稍后回来解决。
markdown
### [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:
ini
[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"
markdown
### [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
vbnet
➜ 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.
[email protected] 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 工具版本和我本地的安装的一个包不太兼容。[email protected] requires rustc 1.84,我本地安装的 rust 版本比较新:
vbnet
➜ 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)`
如何解决呢?我尝试了一些方法,最终的解决方法是 对这个包进行降级:
sql
➜ solana_bank_demo git:(master) ✗ cargo update [email protected] --precise 1.8.1
Updating crates.io index
Downgrading bytemuck_derive v1.9.2 -> v1.8.1
然后再次执行 anchor build,经过漫长的编译之后,就构建成功了。
执行测试操作:
在 tests 目录下,有一个测试文件,当我们指定 anchor test 的时候,就会执行这个测试文件。
bash
➜ solana_bank_demo git:(master) ✗ anchor test
scss
solana_bank_demo
Your transaction signature jmSFrCk1LvaeB9P9CtZbLsQaSnvFn9VXrTbpVDKyN3Ta9yUfoCfyvAaQevYvebQqoebosNdqSKsXLTcuWaoV1uc
✔ Is initialized! (265ms)
1 passing (266ms)
✨ Done in 2.25s.
如果显示出上面的打印内容,说明测试的脚本成功执行了。
执行部署操作:
到这里,我们还没有看,脚手架给我们默认生成的程序结构呢:
rust
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 {}
其实很简单对吧,我们也并不需要关心它的内容,因为我们会把这部分内容全部删掉,然后写我们自己的逻辑。
在此之前,我们先测试一下部署到本地的测试环境:
yaml
➜ 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 命令,部署我们的程序:
javascript
➜ 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 合约:
bank 合约的功能比较简单:
- 1、bank 合约目前只支持原生代币 sol 的存款和取款的功能;
- 2、任何人都可以向程序中存入不小于0.01个sol代币;
- 3、用户任何时候可以从中提取自己的资金;
- 4、程序中需要维护每个人的在bank中的余额,可以供外部查看每个人资金的情况;
- 5、需要维护bank智能合约中总的余额;
- 6、合约的部署人员也没有权限动用别人的资金;
好了,上面就是我们合约的全部需求了。我这里贴上提前写好的程序:
rust
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 = &mut ctx.accounts.bank;
bank.total_balance = bank.total_balance.checked_add(amount).unwrap();
msg!(
"Deposit successful. User balance: {} -> {}, Bank total: {}",
old_balance,
user_account.balance,
bank.total_balance
);
Ok(())
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let user_account = &mut ctx.accounts.user_account;
msg!(
"Processing withdrawal of {} lamports for user: {}",
amount,
ctx.accounts.user.key()
);
require!(user_account.balance >= amount, BankError::InsufficientFunds);
let old_balance = user_account.balance;
let old_bank_balance = ctx.accounts.bank.total_balance;
**ctx
.accounts
.bank
.to_account_info()
.try_borrow_mut_lamports()? -= amount;
**ctx
.accounts
.user
.to_account_info()
.try_borrow_mut_lamports()? += amount;
user_account.balance = user_account.balance.checked_sub(amount).unwrap();
let bank = &mut ctx.accounts.bank;
bank.total_balance = bank.total_balance.checked_sub(amount).unwrap();
msg!(
"Withdrawal successful. User balance: {} -> {}, Bank total: {} -> {}",
old_balance,
user_account.balance,
old_bank_balance,
bank.total_balance
);
Ok(())
}
pub fn get_balance(ctx: Context<GetBalance>) -> Result<u64> {
let balance = ctx.accounts.user_account.balance;
msg!(
"Queried balance for user {}: {} lamports",
ctx.accounts.user.key(),
balance
);
Ok(balance)
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = owner,
space = 8 + 32 + 8,
seeds = [b"bank"],
bump
)]
pub bank: Account<'info, Bank>,
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut, seeds = [b"bank"], bump)]
pub bank: Account<'info, Bank>,
#[account(
init_if_needed,
payer = user,
space = 8 + 8,
seeds = [b"user", user.key().as_ref()],
bump
)]
pub user_account: Account<'info, UserAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut, seeds = [b"bank"], bump)]
pub bank: Account<'info, Bank>,
#[account(mut, seeds = [b"user", user.key().as_ref()], bump)]
pub user_account: Account<'info, UserAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct GetBalance<'info> {
#[account(seeds = [b"user", user.key().as_ref()], bump)]
pub user_account: Account<'info, UserAccount>,
pub user: Signer<'info>,
}
#[account]
pub struct Bank {
pub owner: Pubkey,
pub total_balance: u64,
}
#[account]
pub struct UserAccount {
pub balance: u64,
}
#[error_code]
pub enum BankError {
#[msg("Deposit amount must be at least 0.01 SOL")]
DepositTooSmall,
#[msg("Insufficient funds for withdrawal")]
InsufficientFunds,
}
当我把这个代码贴到 lib.rs 中的时候,程序就直接报错了,提示我们当前我们的配置中不支持 init_if_needed 这个特性,还记得上面我们说的一个坑点吗,就是这里,我们修改下 Cargo.toml 相关配置:
programs/solana_bank_demo/Cargo.toml
ini
[dependencies]
anchor-lang = { version = "0.31.0", features = ["init-if-needed"] }
添加完毕之后,发现我们的程序不报错了。
编译 bank 合约:
我们先清除掉之前的 build 的程序,然后再重新构建
bash
anchor clean
anchor build
......
Finished `test` profile [unoptimized + debuginfo] target(s) in 17.19s
Running unittests src/lib.rs (/Users/louis/Documents/myProject/Solana_project/solana_bank_demo/target/debug/deps/solana_bank_demo-829da3616c2d2786)
经过漫长的编译之后,我们开始部署自己的程序,在这之前我们还需要做一下 id 的同步:
bash
anchor keys sync
执行完毕这个操作之后,我们代码中的 id 和 Anchor.toml 中的 id 就一致了。
部署到本地环境:
还记得我们本地启动的测试环境吗?
yaml
➜ 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: KpdErpE6AYG8szQxg2MwYj2GFKXS3HJzjR3FzG6w8gd
Genesis Hash: GpWY36cVJZmxqMvchiUPgZHSPxdJcToGC5qQozKkGWyK
Version: 2.1.17
Shred Version: 9555
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:22 | Processed Slot: 48 | Confirmed Slot: 48 | Finalized Slot: 17 | Full Snapshot Slot: - | Incremental Snapshot Slot:
我们的测试和部署都要依赖于这个环境来做。
javascript
➜ solana_bank_demo git:(master) ✗ anchor deploy
Deploying cluster: http://127.0.0.1:8899
Upgrade authority: /Users/chupengfei/.config/solana/id.json
Deploying program "solana_bank_demo"...
Program path: /Users/chupengfei/Documents/myProject/Solana_project/solana_bank_demo/target/deploy/solana_bank_demo.so...
Program Id: 98Frhx1UD56goz5ns2vTR6WaqHLPsJE7Jux8iWgJr3Fv
Signature: 24FuV8YRdnnUuVbiA39MQPmKZb8gzmziHCeUpomfpkYEJR893nzpgEuCmZ3qBZQq68H5jv2E1K5iwBt3qdRkmTfo
Deploy success
➜ solana_bank_demo git:(master) ✗
从上面的部署信息输出可以看出,我们部署成功了。
本地环境跑测试用例:
程序部署成功之后,我们就要开始写测试用例,完善的测试用例能够保证程序的安全性。这里我已经准备好了测试用例:
ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { SolanaBankDemo } from "../target/types/solana_bank_demo";
import { PublicKey } from '@solana/web3.js';
import { expect } from 'chai';
describe("solana_bank", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.SolanaBank as Program<SolanaBankDemo>;
const provider = anchor.getProvider();
// 生成银行PDA地址
const [bankPDA] = PublicKey.findProgramAddressSync(
[Buffer.from("bank")],
program.programId
);
it("Initialize bank", async () => {
const tx = await program.methods
.initialize()
.accounts({
bank: bankPDA,
owner: provider.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
console.log("Bank initialized with tx:", tx);
// 验证初始化结果
const bankAccount = await program.account.bank.fetch(bankPDA);
expect(bankAccount.owner.toString()).to.equal(provider.publicKey.toString());
expect(bankAccount.totalBalance.toString()).to.equal("0");
});
it("Deposit funds", async () => {
// 生成用户账户PDA
const [userAccountPDA] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), provider.publicKey.toBuffer()],
program.programId
);
const depositAmount = new anchor.BN(1_000_000_000); // 1 SOL
const tx = await program.methods
.deposit(depositAmount)
.accounts({
bank: bankPDA,
userAccount: userAccountPDA,
user: provider.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
console.log("Deposit tx:", tx);
// 验证存款结果
const userAccount = await program.account.userAccount.fetch(userAccountPDA);
expect(userAccount.balance.toString()).to.equal(depositAmount.toString());
});
it("Get balance", async () => {
const [userAccountPDA] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), provider.publicKey.toBuffer()],
program.programId
);
const balance = await program.methods
.getBalance()
.accounts({
userAccount: userAccountPDA,
user: provider.publicKey,
})
.view();
console.log("User balance:", balance.toString());
// 验证余额结果
expect(balance.toString()).to.equal("1000000000");
});
it("Withdraw funds", async () => {
const [userAccountPDA] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), provider.publicKey.toBuffer()],
program.programId
);
const withdrawAmount = new anchor.BN(500_000_000); // 0.5 SOL
const tx = await program.methods
.withdraw(withdrawAmount)
.accounts({
bank: bankPDA,
userAccount: userAccountPDA,
user: provider.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
console.log("Withdraw tx:", tx);
// 验证取款结果
const userAccount = await program.account.userAccount.fetch(userAccountPDA);
expect(userAccount.balance.toString()).to.equal("500000000");
});
it("Should fail with insufficient funds", async () => {
const [userAccountPDA] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), provider.publicKey.toBuffer()],
program.programId
);
const withdrawAmount = new anchor.BN(1_000_000_000); // 尝试取出1 SOL(超过余额)
try {
await program.methods
.withdraw(withdrawAmount)
.accounts({
bank: bankPDA,
userAccount: userAccountPDA,
user: provider.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
expect.fail("应该失败但没有失败");
} catch (error) {
expect(error.toString()).to.include("Insufficient funds for withdrawal");
}
});
});
我们执行 anchor test 查看执行结果:
bash
➜ solana_bank_demo git:(master) ✗ anchor test
Finished `release` profile [optimized] target(s) in 0.33s
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.32s
Running unittests src/lib.rs (/Users/louis/Documents/myProject/Solana_project/solana_bank_demo/target/debug/deps/solana_bank_demo-829da3616c2d2786)
Found a 'test' script in the Anchor.toml. Running it as a test suite!
Running test suite: "/Users/louis/Documents/myProject/Solana_project/solana_bank_demo/Anchor.toml"
Error: Your configured rpc port: 8899 is already in use
发现报错了,这是因为,我们本地已经启动了测试环境,我们需要叫上一个配置来复用这个环境。
bash
➜ solana_bank_demo git:(master) ✗ anchor test --skip-local-validator
Finished `release` profile [optimized] target(s) in 0.30s
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.33s
Running unittests src/lib.rs (/Users/chupengfei/Documents/myProject/Solana_project/solana_bank_demo/target/debug/deps/solana_bank_demo-829da3616c2d2786)
Deploying cluster: http://127.0.0.1:8899
Upgrade authority: /Users/chupengfei/.config/solana/id.json
Deploying program "solana_bank_demo"...
Program path: /Users/chupengfei/Documents/myProject/Solana_project/solana_bank_demo/target/deploy/solana_bank_demo.so...
Program Id: 98Frhx1UD56goz5ns2vTR6WaqHLPsJE7Jux8iWgJr3Fv
Signature: 3b5YsQEyKpzKMfauJpArDd1eCeh2Pia1sn8ehwGBuNfxETogFBcbUvUiMsUgjvTqYbqnaYJkJjQPtQ63FWVffa6v
Deploy success
Found a 'test' script in the Anchor.toml. Running it as a test suite!
Running test suite: "/Users/chupengfei/Documents/myProject/Solana_project/solana_bank_demo/Anchor.toml"
yarn run v1.22.22
$ /Users/chupengfei/Documents/myProject/Solana_project/solana_bank_demo/node_modules/.bin/ts-mocha -p ./tsconfig.json -t 1000000 'tests/**/*.ts'
(node:64554) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
solana_bank
Bank initialized with tx: 2jrHk6x8dvXDrWjPwCY6aA1hRniLxeTyUrkPx7T9DGyU8vGszhGstskCMxVGZUsMBomwpDwMAJovW4Lzh9t4GjpS
✔ Initialize bank (290ms)
Deposit tx: 5ocrYK7MVBNanSrjvRWeBy9WJbSPwAvfQ3xFvjQTHMemQZcqoqvNy17ppbLVLvbSujVAPERsQqiK8Bba6KF8wn5q
✔ Deposit funds (461ms)
User balance: 1000000000
✔ Get balance
Withdraw tx: 5ykX6uGur9oHsnWr5QauhhwDVcaxBpJpTtfs6DF2M5SwpQAZAqN5re1XUEDtEuKb9AW6UJzeunh24FTF46mNMceG
✔ Withdraw funds (452ms)
✔ Should fail with insufficient funds
5 passing (1s)
✨ Done in 2.40s.
你看,我们的所有测试用例都执行完毕了,而且通过了。