本文介绍了LiteSVM,一个轻量级的Solana虚拟机,它允许在Rust测试环境中运行完整的Solana运行时,无需外部验证器。文章详细说明了如何使用LiteSVM测试Anchor程序,包括环境设置、编写程序测试、以及使用anchor-litesvm减少样板代码。通过具体的代码示例和测试案例,展示了LiteSVM在简化Solana程序测试流程、提高测试效率方面的优势。
快速、可靠的测试至关重要,因为 Solana 程序变得越来越复杂,并且团队将更多的工作流程自动化。传统方法通常依赖于solana-test-validator、Docker 或后台进程,这些都会降低速度,并使测试更难在机器和 CI 环境中重现。
LiteSVM 通过完全在你的 Rust 测试流程中运行完整的 Solana 运行时来解决这个问题。你可以对账户、插槽和系统变量进行细粒度的控制,更快的反馈循环,以及在任何地方行为一致的测试,而无需外部验证器或额外的设置。
本指南将引导你使用 LiteSVM 以更快、更简化的方式端到端地测试 Anchor 程序。
你将学习设置 LiteSVM 来测试 Anchor 程序所需的一切,包括如何:
anchor-litesvm 减少样板代码本指南假设你对 Solana 编程、Anchor、Rust 和单元测试有基本的了解。
你还应该具备:
| 依赖项 | 版本 |
|---|---|
| Solana CLI | 3.0.6 |
| Anchor | 0.31.1 |
| LiteSVM | 0.8.1 |
| Node | 24.8.0 |
| Rust | 1.90.0 |
| solana-kite | 0.1.0 |
| anchor-litesvm | 0.2.0 |
LiteSVM 是一个轻量级的 Solana 虚拟机,可以在你的测试中运行,因此你无需启动单独的验证器即可测试程序。嵌入 VM 可以消除开销并快速执行测试。它附带一个直观的 API,可用于 Rust、TypeScript/JavaScript 和 Python(通过 solders 库)。
LiteSVM 的主要功能:
将 LiteSVM 添加到你的项目就像包含 LiteSVM crates 一样简单:
LiteSVM Core(基本测试框架)
cargo add --dev litesvm
SPL Token 支持(可选)
cargo add --dev litesvm-token
我们将使用现有的 Anchor Escrow 程序来学习如何使用 LiteSVM 实现测试。
Anchor Escrow 程序通过将 token 锁定在程序控制的金库中,直到双方都满足约定的条款,从而实现双方之间安全、无需信任的 token 交换。示例程序包括 token 交换(初始化、交换、取消)和基线测试。
该程序实现了三个核心指令:
make_offer: 允许 maker 通过指定他们想要交易多少 token A 来换取 token B 来创建 offertake_offer: 使 taker 能够接受 offer 并执行交换refund_offer: 允许 maker 取消他们的 offer 并收回他们的 token克隆存储库:
git clone git@github.com:quiknode-labs/you-will-build-a-solana-program.git anchor-escrow-2025
cd anchor-escrow-2025
运行现有测试以确认你的环境已正确设置:
cargo test
预期输出:
running 11 tests
test test_id ... ok
test tests::test_same_token_mints_fails ... ok
test tests::test_zero_token_a_offered_amount_fails ... ok
test tests::test_take_offer_insufficient_funds_fails ... ok
test tests::test_duplicate_offer_id_fails ... ok
test tests::test_make_offer_succeeds ... ok
test tests::test_insufficient_funds_fails ... ok
test tests::test_non_maker_cannot_refund_offer ... ok
test tests::test_zero_token_b_wanted_amount_fails ... ok
test tests::test_take_offer_success ... ok
test tests::test_refund_offer_success ... ok
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.10s
每个测试都遵循一致的模式:
setup_escrow_test() 以创建一个新的 EscrowTestEnvironment,其中包含 LiteSVM、部署程序、铸造 token 并为用户帐户提供资金。我们的测试的辅助函数位于 programs/escrow/src/escrow_test_helpers.rs。
以下是你将使用的一些关键测试助手:
EscrowTestEnvironment。make、take 和 refund 的交易。test_make_offer_succeeds 验证了基本的 offer 创建流程。
该测试设置环境,生成唯一的 offer ID,派生必要的 PDA(offer 帐户和金库),构建帐户结构,创建一个指令,指示 Alice 提供 1 个 TOKEN_A 以换取 1 个 TOKEN_B,发送交易,并断言成功。
programs/escrow/src/tests.rs
#[test]
fn test_make_offer_succeeds() {
let mut test_environment = setup_escrow_test();
let offer_id = generate_offer_id();
let (offer_account, _offer_bump) = get_pda_and_bump(&seeds!["offer", offer_id], &test_environment.program_id);
let vault = spl_associated_token_account::get_associated_token_address(
&offer_account,
&test_environment.token_mint_a.pubkey(),
);
let make_offer_accounts = build_make_offer_accounts(
test_environment.alice.pubkey(),
test_environment.token_mint_a.pubkey(),
test_environment.token_mint_b.pubkey(),
test_environment.alice_token_account_a,
offer_account,
vault,
);
let make_offer_instruction = build_make_offer_instruction(
offer_id,
1 * TOKEN_A,
1 * TOKEN_B,
make_offer_accounts,
);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![make_offer_instruction],
&[&test_environment.alice],
&test_environment.alice.pubkey(),
);
assert!(result.is_ok(), "Valid offer should succeed");
}
test_duplicate_offer_id_fails 确保 offer ID 是唯一的。
Alice 首先使用特定 ID 成功创建一个 offer,然后 Bob 尝试使用相同的 ID 创建不同的 offer(这将派生到相同的 PDA),并且测试验证此第二次交易失败,因为该帐户已存在。
programs/escrow/src/tests.rs
#[test]
fn test_duplicate_offer_id_fails() {
let mut test_environment = setup_escrow_test();
let offer_id = generate_offer_id();
let (offer_account, _offer_bump) = get_pda_and_bump(&seeds!["offer", offer_id], &test_environment.program_id);
let vault = spl_associated_token_account::get_associated_token_address(
&offer_account,
&test_environment.token_mint_a.pubkey(),
);
let make_offer_accounts = build_make_offer_accounts(
test_environment.alice.pubkey(),
test_environment.token_mint_a.pubkey(),
test_environment.token_mint_b.pubkey(),
test_environment.alice_token_account_a,
offer_account,
vault,
);
let make_offer_instruction = build_make_offer_instruction(
offer_id,
1 * TOKEN_A,
1 * TOKEN_B,
make_offer_accounts,
);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![make_offer_instruction],
&[&test_environment.alice],
&test_environment.alice.pubkey(),
);
assert!(result.is_ok(), "First offer should succeed");
let make_offer_accounts_with_existing_offer_id = build_make_offer_accounts(
test_environment.bob.pubkey(),
test_environment.token_mint_a.pubkey(),
test_environment.token_mint_b.pubkey(),
test_environment.bob_token_account_a,
offer_account,
vault,
);
let make_offer_instruction_with_existing_offer_id = build_make_offer_instruction(
offer_id,
1 * TOKEN_A,
1 * TOKEN_B,
make_offer_accounts_with_existing_offer_id,
);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![make_offer_instruction_with_existing_offer_id],
&[&test_environment.bob],
&test_environment.bob.pubkey(),
);
assert!(result.is_err(), "Second offer with same ID should fail");
}
test_insufficient_funds_fails 检查余额验证。
Alice 试图为 1000 个 TOKEN_A 创建一个 offer,尽管她的帐户中只有 10 个 TOKEN_A,并且测试确认由于资金不足,交易失败。
programs/escrow/src/tests.rs
#[test]
fn test_insufficient_funds_fails() {
let mut test_environment = setup_escrow_test();
// 尝试创建 offer,其中 token 数量超过 Alice 拥有的数量
let offer_id = generate_offer_id();
let (offer_account, _offer_bump) = get_pda_and_bump(&seeds!["offer", offer_id], &test_environment.program_id);
let vault = spl_associated_token_account::get_associated_token_address(
&offer_account,
&test_environment.token_mint_a.pubkey(),
);
let make_offer_accounts = build_make_offer_accounts(
test_environment.alice.pubkey(),
test_environment.token_mint_a.pubkey(),
test_environment.token_mint_b.pubkey(),
test_environment.alice_token_account_a,
offer_account,
vault,
);
let make_offer_instruction = build_make_offer_instruction(
offer_id,
1000 * TOKEN_A, // 尝试提供 1000 个 token (Alice 只有 10 个)
1 * TOKEN_B,
make_offer_accounts,
);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![make_offer_instruction],
&[&test_environment.alice],
&test_environment.alice.pubkey(),
);
assert!(result.is_err(), "Offer with insufficient funds should fail");
}
test_same_token_mints_fails 强制执行不同的 token 类型。
该测试构建了一个 offer,其中 TOKEN_A 和 TOKEN_B 都指向相同的 mint,并验证程序拒绝此无效配置。
programs/escrow/src/tests.rs
#[test]
fn test_same_token_mints_fails() {
let mut test_environment = setup_escrow_test();
// 尝试为 token_a 和 token_b 创建具有相同 token mint 的 offer
let offer_id = generate_offer_id();
let (offer_account, _offer_bump) = get_pda_and_bump(&seeds!["offer", offer_id], &test_environment.program_id);
let vault = spl_associated_token_account::get_associated_token_address(
&offer_account,
&test_environment.token_mint_a.pubkey(),
);
let make_offer_accounts = build_make_offer_accounts(
test_environment.alice.pubkey(),
test_environment.token_mint_a.pubkey(),
test_environment.token_mint_a.pubkey(), // 两个都使用相同的 mint
test_environment.alice_token_account_a,
offer_account,
vault,
);
let make_offer_instruction =
build_make_offer_instruction(offer_id, 1 * TOKEN_A, 1 * TOKEN_B, make_offer_accounts);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![make_offer_instruction],
&[&test_environment.alice],
&test_environment.alice.pubkey(),
);
assert!(result.is_err(), "Offer with same token mints should fail");
}
test_zero_token_b_wanted_amount_fails 验证非零的 wanted amount。
该测试创建了一个 offer,请求 0 个 TOKEN_B 以换取 1 个 TOKEN_A,并确认程序拒绝零金额 offer。
programs/escrow/src/tests.rs
#[test]
fn test_zero_token_b_wanted_amount_fails() {
let mut test_environment = setup_escrow_test();
// 尝试创建零 token_b_wanted_amount 的 offer
let offer_id = generate_offer_id();
let (offer_account, _offer_bump) = get_pda_and_bump(&seeds!["offer", offer_id], &test_environment.program_id);
let vault = spl_associated_token_account::get_associated_token_address(
&offer_account,
&test_environment.token_mint_a.pubkey(),
);
let make_offer_accounts = build_make_offer_accounts(
test_environment.alice.pubkey(),
test_environment.token_mint_a.pubkey(),
test_environment.token_mint_b.pubkey(),
test_environment.alice_token_account_a,
offer_account,
vault,
);
let make_offer_instruction = build_make_offer_instruction(
offer_id,
1 * TOKEN_A,
0, // 零 wanted amount
make_offer_accounts,
);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![make_offer_instruction],
&[&test_environment.alice],
&test_environment.alice.pubkey(),
);
assert!(
result.is_err(),
"Offer with zero token_b_wanted_amount should fail"
);
}
test_zero_token_a_offered_amount_fails 验证非零的 offered amount。
与之前的测试类似,这会创建一个 offer,其中提供 0 个 TOKEN_A 以换取 1 个 TOKEN_B,并验证是否拒绝。
programs/escrow/src/tests.rs
#[test]
fn test_zero_token_a_offered_amount_fails() {
let mut test_environment = setup_escrow_test();
// 尝试创建零 token_a_offered_amount 的 offer
let offer_id = generate_offer_id();
let (offer_account, _offer_bump) = get_pda_and_bump(&seeds!["offer", offer_id], &test_environment.program_id);
let vault = spl_associated_token_account::get_associated_token_address(
&offer_account,
&test_environment.token_mint_a.pubkey(),
);
let make_offer_accounts = build_make_offer_accounts(
test_environment.alice.pubkey(),
test_environment.token_mint_a.pubkey(),
test_environment.token_mint_b.pubkey(),
test_environment.alice_token_account_a,
offer_account,
vault,
);
let make_offer_instruction = build_make_offer_instruction(
offer_id,
0, // 零 offered amount
1 * TOKEN_B,
make_offer_accounts,
);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![make_offer_instruction],
&[&test_environment.alice],
&test_environment.alice.pubkey(),
);
assert!(
result.is_err(),
"Offer with zero token_a_offered_amount should fail"
);
}
test_take_offer_success 验证了完整的交换流程。
Alice 创建了一个 offer,提供 3 个 TOKEN_A,想要 2 个 TOKEN_B,Bob 接受了它,并且测试验证了最终余额(Alice:7 个 TOKEN_A 和 2 个 TOKEN_B,Bob:3 个 TOKEN_A 和 3 个 TOKEN_B),并确认 offer 帐户已关闭。
programs/escrow/src/tests.rs
#[test]
fn test_take_offer_success() {
let mut test_environment = setup_escrow_test();
// Alice 创建了一个 offer:3 个 token A 换 2 个 token B
let offer_id = generate_offer_id();
let alice = test_environment.alice.insecure_clone();
let alice_token_account_a = test_environment.alice_token_account_a;
let (offer_account, vault) = execute_make_offer(
&mut test_environment,
offer_id,
&alice,
alice_token_account_a,
3 * TOKEN_A,
2 * TOKEN_B,
).unwrap();
// Bob 接受了 offer
let bob = test_environment.bob.insecure_clone();
let bob_token_account_a = test_environment.bob_token_account_a;
let bob_token_account_b = test_environment.bob_token_account_b;
let alice_token_account_b = test_environment.alice_token_account_b;
execute_take_offer(
&mut test_environment,
&bob,
&alice,
bob_token_account_a,
bob_token_account_b,
alice_token_account_b,
offer_account,
vault,
).unwrap();
// 检查余额
assert_token_balance(
&test_environment.litesvm,
&test_environment.alice_token_account_a,
7 * TOKEN_A,
"Alice 应该剩下 7 个 token A",
);
assert_token_balance(
&test_environment.litesvm,
&test_environment.alice_token_account_b,
2 * TOKEN_B,
"Alice 应该收到 2 个 token B",
);
assert_token_balance(
&test_environment.litesvm,
&test_environment.bob_token_account_a,
3 * TOKEN_A,
"Bob 应该收到 3 个 token A",
);
assert_token_balance(
&test_environment.litesvm,
&test_environment.bob_token_account_b,
3 * TOKEN_B,
"Bob 应该剩下 3 个 token B",
);
// 检查 offer 帐户在被接受后是否已关闭
check_account_is_closed(
&test_environment.litesvm,
&offer_account,
"Offer 帐户在被接受后应关闭"
);
}
test_take_offer_insufficient_funds_fails 确保 taker 有足够的余额。
Alice 创建了一个 offer,想要 1000 个 TOKEN_B(Bob 只有 5 个),Bob 试图接受它,并且测试确认由于 Bob 的资金不足,交易失败。
programs/escrow/src/tests.rs
#[test]
fn test_take_offer_insufficient_funds_fails() {
let mut test_environment = setup_escrow_test();
// 从 Alice 那里创建一个用于大量 token B 的 offer
let large_token_b_amount = 1000 * TOKEN_B; // 远大于 Bob 的余额(他有 5 个)
let offer_id = generate_offer_id();
let (offer_account, _offer_bump) = get_pda_and_bump(&seeds!["offer", offer_id], &test_environment.program_id);
let vault = spl_associated_token_account::get_associated_token_address(
&offer_account,
&test_environment.token_mint_a.pubkey(),
);
let make_offer_accounts = build_make_offer_accounts(
test_environment.alice.pubkey(),
test_environment.token_mint_a.pubkey(),
test_environment.token_mint_b.pubkey(),
test_environment.alice_token_account_a,
offer_account,
vault,
);
let make_offer_instruction = build_make_offer_instruction(
offer_id,
1 * TOKEN_A,
large_token_b_amount,
make_offer_accounts,
);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![make_offer_instruction],
&[&test_environment.alice],
&test_environment.alice.pubkey(),
);
assert!(result.is_ok(), "Alice's offer should succeed");
// 尝试让拥有 token B 不足的 Bob 接受 offer
let take_offer_accounts = TakeOfferAccounts {
associated_token_program: spl_associated_token_account::ID,
token_program: spl_token::ID,
system_program: anchor_lang::system_program::ID,
taker: test_environment.bob.pubkey(),
maker: test_environment.alice.pubkey(),
token_mint_a: test_environment.token_mint_a.pubkey(),
token_mint_b: test_environment.token_mint_b.pubkey(),
taker_token_account_a: test_environment.bob_token_account_a,
taker_token_account_b: test_environment.bob_token_account_b,
maker_token_account_b: test_environment.alice_token_account_b,
offer_account,
vault,
};
let take_offer_instruction = build_take_offer_instruction(take_offer_accounts);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![take_offer_instruction],
&[&test_environment.bob],
&test_environment.bob.pubkey(),
);
assert!(
result.is_err(),
"拥有资金不足的 Take offer 应该失败"
);
}
test_refund_offer_success 验证了退款机制。
Alice 创建了一个 offer,锁定了 3 个 TOKEN_A(余额降至 7 个),退还了它,并且测试验证了她的余额恢复到 10 个 TOKEN_A,并且 offer 帐户已关闭。
programs/escrow/src/tests.rs
#[test]
fn test_refund_offer_success() {
let mut test_environment = setup_escrow_test();
// Alice 创建了一个 offer:3 个 token A 换 2 个 token B
let offer_id = generate_offer_id();
let alice = test_environment.alice.insecure_clone();
let alice_token_account_a = test_environment.alice_token_account_a;
let (offer_account, vault) = execute_make_offer(
&mut test_environment,
offer_id,
&alice,
alice_token_account_a,
3 * TOKEN_A,
2 * TOKEN_B,
).unwrap();
// 检查 Alice 的余额在创建 offer 后是否减少
assert_token_balance(
&test_environment.litesvm,
&test_environment.alice_token_account_a,
7 * TOKEN_A,
"Alice 在创建 offer 后应该剩下 7 个 token A",
);
// Alice 退还了 offer
execute_refund_offer(
&mut test_environment,
&alice,
alice_token_account_a,
offer_account,
vault,
).unwrap();
// 检查 Alice 的余额在退款后是否已恢复
assert_token_balance(
&test_environment.litesvm,
&test_environment.alice_token_account_a,
10 * TOKEN_A,
"Alice 在退款后应该取回全部 10 个 token A",
);
// 检查 offer 帐户是否已关闭
check_account_is_closed(
&test_environment.litesvm,
&offer_account,
"Offer 帐户在退款后应关闭"
);
}
test_non_maker_cannot_refund_offer 强制执行授权。
Alice 创建了一个 offer,Bob 试图通过使用他的密钥对签名来退款,测试确认交易失败,并验证 Alice 的余额仍然为 7 个 TOKEN_A,并且 offer 帐户仍然存在。
programs/escrow/src/tests.rs
#[test]
fn test_non_maker_cannot_refund_offer() {
let mut test_environment = setup_escrow_test();
// Alice 创建了一个 offer:3 个 token A 换 2 个 token B
let offer_id = generate_offer_id();
let (offer_account, _offer_bump) = get_pda_and_bump(&seeds!["offer", offer_id], &test_environment.program_id);
let vault = spl_associated_token_account::get_associated_token_address(
&offer_account,
&test_environment.token_mint_a.pubkey(),
);
let make_offer_accounts = build_make_offer_accounts(
test_environment.alice.pubkey(),
test_environment.token_mint_a.pubkey(),
test_environment.token_mint_b.pubkey(),
test_environment.alice_token_account_a,
offer_account,
vault,
);
let make_offer_instruction = build_make_offer_instruction(
offer_id,
3 * TOKEN_A,
2 * TOKEN_B,
make_offer_accounts,
);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![make_offer_instruction],
&[&test_environment.alice],
&test_environment.alice.pubkey(),
);
assert!(result.is_ok(), "Alice's offer should succeed");
// Bob 尝试退还 Alice 的 offer(应该失败)
let refund_offer_accounts = RefundOfferAccounts {
token_program: spl_token::ID,
system_program: anchor_lang::system_program::ID,
maker: test_environment.bob.pubkey(),
token_mint_a: test_environment.token_mint_a.pubkey(),
maker_token_account_a: test_environment.alice_token_account_a,
offer_account,
vault,
};
let refund_instruction = build_refund_offer_instruction(refund_offer_accounts);
let result = send_transaction_from_instructions(
&mut test_environment.litesvm,
vec![refund_instruction],
&[&test_environment.bob],
&test_environment.bob.pubkey(),
);
assert!(
result.is_err(),
"非 maker 不应能够退还 offer"
);
// 验证 Alice 的余额是否仍然相同(offer 未退还)
assert_token_balance(
&test_environment.litesvm,
&test_environment.alice_token_account_a,
7 * TOKEN_A,
"在未能退还后,Alice 的余额应保持不变",
);
// 验证 offer 帐户是否仍然存在(反转检查)
let offer_account_data = test_environment.litesvm.get_account(&offer_account);
assert!(
offer_account_data.is_some() && !offer_account_data.unwrap().data.is_empty(),
"在未能退还后,Offer 帐户应仍然存在"
);
}
LiteSVM 提供了强大的功能,这些功能超出了本指南中的示例测试。以下各节展示了一些你可以在测试中实现的先进技术,以实现更大的控制。
你通常需要一个有资金的付款人和未初始化的帐户用于 PDA。使用 LiteSVM,你可以在需要时空投到位并写入原始帐户字节。
use solana_account::Account;
let pda_key = Keypair::new().pubkey();
svm.set_account(
pda_key,
Account {
lamports: 100_000_000_000,
data: vec![0u8; 128],
owner: PROGRAM_ID,
executable: false,
rent_epoch: 0,
},
);
如果你的流程使用 SPL Token,请使用 Pack 序列化 Mint 和 Account。将所有者正确设置为 TOKEN_PROGRAM_ID,并使帐户免租金或简单地超额出资。
use spl_token::{ID as TOKEN_PROGRAM_ID, state::{Mint, Account as TokenAccount}};
use solana_program_pack::Pack;
use solana_keypair::Keypair;
let mint = Keypair::new();
let mut mint_bytes = vec![0; Mint::LEN];
Mint::pack(
Mint { mint_authority: None.into(), supply: 0, decimals: 6, is_initialized: true, freeze_authority: None.into() },
&mut mint_bytes
).unwrap();
svm.set_account(
mint.pubkey(),
solana_account::Account { lamports: svm.minimum_balance_for_rent_exemption(Mint::LEN), data: mint_bytes, owner: TOKEN_PROGRAM_ID, executable: false, rent_epoch: 0 }
);
let token_acc = Keypair::new();
let owner = Keypair::new();
let mut token_bytes = vec![0; TokenAccount::LEN];
TokenAccount::pack(
TokenAccount {
mint: mint.pubkey(),
owner: owner.pubkey(),
amount: 0,
delegate: None.into(),
state: spl_token::state::AccountState::Initialized,
is_native: None.into(),
delegated_amount: 0,
close_authority: None.into(),
},
&mut token_bytes
).unwrap();
svm.set_account(
token_acc.pubkey(),
solana_account::Account { lamports: svm.minimum_balance_for_rent_exemption(TokenAccount::LEN), data: token_bytes, owner: TOKEN_PROGRAM_ID, executable: false, rent_epoch: 0 }
);
在提交之前,使用模拟来预览日志和指令错误。执行以更新内存中的 ledger 并断言帐户数据和余额。读回帐户并解析你的结构以进行验证。
let res = svm.send_transaction(tx).unwrap();
assert!(res.logs.iter().any(|l| l.contains("Event:EscrowInitialized")));
let acc = svm.get_account(&pda_key).expect("exists");
assert_eq!(acc.owner, PROGRAM_ID);
通过调整系统变量和运行时配置来重现过期路径、陈旧订单和计算回归。通过改变 Clock 来提前时间,跳转到未来的插槽,使区块哈希过期以测试错误处理,并在分析时调整计算预算。
use solana_sdk::clock::Clock;
let mut clock: Clock = svm.get_sysvar::<Clock>();
clock.unix_timestamp += 3600;
svm.set_sysvar::<Clock>(&clock);
svm.warp_to_slot(5```rust
const PROGRAM_ID: &str = "8jR5GeNzeweq35Uo84kGP3v1NcBaZWH5u62k7PxN4T2y";
const TOKEN_A: u64 = 1_000_000_000; // 9位小数的1个token
const TOKEN_B: u64 = 1_000_000_000; // 9位小数的1个token
#[test]
fn test_make_offer_succeeds() {
// 使用escrow program初始化AnchorLiteSVM
let program_id = Pubkey::from_str(PROGRAM_ID).unwrap();
let program_data = fs::read("../../target/deploy/escrow.so").unwrap();
let mut ctx = AnchorLiteSVM::build_with_program(program_id, &program_data);
// 使用anchor-litesvm助手函数创建有资金的账户
let mint_authority = ctx.svm.create_funded_account(1_000_000_000).unwrap();
let alice = ctx.svm.create_funded_account(1_000_000_000).unwrap();
// 使用anchor-litesvm助手函数创建token mint
let token_mint_a = ctx.svm.create_token_mint(&mint_authority, 9).unwrap();
let token_mint_b = ctx.svm.create_token_mint(&mint_authority, 9).unwrap();
// 为Alice创建关联token账户 (token A)
let alice_token_account_a = ctx.svm
.create_associated_token_account(&token_mint_a.pubkey(), &alice)
.unwrap();
// 将token mint到Alice的token账户
ctx.svm
.mint_to(
&token_mint_a.pubkey(),
&alice_token_account_a,
&mint_authority,
10 * TOKEN_A, // Alice获得10个token A
)
.unwrap();
// 生成offer ID
let offer_id = 1u64;
// 使用anchor-litesvm助手函数为offer账户派生PDA
let (offer_account, _offer_bump) = ctx
.svm
.get_pda_with_bump(&[b"offer", &offer_id.to_le_bytes()], &program_id);
// 计算vault的关联token地址
let vault = spl_associated_token_account::get_associated_token_address(
&offer_account,
&token_mint_a.pubkey(),
);
// 手动构建make_offer指令数据
// Anchor discriminator (8 bytes) + offer_id (8 bytes) + token_a_offered_amount (8 bytes) + token_b_wanted_amount (8 bytes)
let discriminator_input = b"global:make_offer";
let discriminator = anchor_lang::solana_program::hash::hash(discriminator_input).to_bytes()[..8].to_vec();
let mut instruction_data = discriminator;
instruction_data.extend_from_slice(&offer_id.to_le_bytes());
instruction_data.extend_from_slice(&(1 * TOKEN_A).to_le_bytes());
instruction_data.extend_from_slice(&(1 * TOKEN_B).to_le_bytes());
// 构建账户元数据
let account_metas = vec![\
AccountMeta::new_readonly(spl_associated_token_account::ID, false),\
AccountMeta::new_readonly(spl_token::ID, false),\
AccountMeta::new_readonly(anchor_lang::system_program::ID, false),\
AccountMeta::new(alice.pubkey(), true),\
AccountMeta::new_readonly(token_mint_a.pubkey(), false),\
AccountMeta::new_readonly(token_mint_b.pubkey(), false),\
AccountMeta::new(alice_token_account_a, false),\
AccountMeta::new(offer_account, false),\
AccountMeta::new(vault, false),\
];
let make_offer_ix = Instruction {
program_id,
accounts: account_metas,
data: instruction_data,
};
// 使用anchor-litesvm助手函数执行指令并断言成功
let result = ctx.execute_instruction(make_offer_ix, &[&alice]).unwrap();
result.assert_success();
}
}
要让我们的新测试通过 cargo test 运行,你需要在 lib.rs 中注册测试模块,以便它包含在 crate 的测试工具中:
#[cfg(test)]
mod anchor_tests;
重新运行测试并查找:
test anchor_tests::tests::anchor_test_make_offer_succeeds ... ok
既然你了解了 anchor-litesvm 如何减少对样板代码的需求,请尝试重写其他测试以获得更深入的了解。
很高兴你能坚持到这里!你已经了解了如何使用 LiteSVM 在进程内完整运行 Solana 程序,设置可重用的测试工具,加载已编译的 Anchor 程序,动态构建账户和token状态,并在不启动单独的验证器的情况下验证真实的指令流。你还预览了anchor-litesvm如何减少样板代码,以便你可以专注于程序的逻辑而不是连接。
从这里开始,你将处于有利的位置来扩展这些测试、添加新场景,并将 LiteSVM 用作更快地交付更安全的 Solana 程序的核心部分。
如果你对新主题有任何反馈或要求,请告诉我们。我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!