LogoAnchor 中文文档

LiteSVM

使用LiteSVM在Rust、TS/JS或Python中为Solana程序编写测试。

概述

litesvm 是一个快速且轻量级的测试Solana程序的库。它通过创建一个优化用于程序开发者的内存中的Solana虚拟机来工作。这使得它的运行和编译速度远快于诸如solana-program-testsolana-test-validator等替代方案。litesvm提供Rust、TS/JS和Python版本(作为solders库的一部分)。

安装

cargo add litesvm --dev

最小示例

use litesvm::LiteSVM;
use solana_message::Message;
use solana_pubkey::Pubkey;
use solana_system_interface::instruction::transfer;
use solana_keypair::Keypair;
use solana_signer::Signer;
use solana_transaction::Transaction;
 
let from_keypair = Keypair::new();
let from = from_keypair.pubkey();
let to = Pubkey::new_unique();
 
let mut svm = LiteSVM::new();
svm.airdrop(&from, 10_000).unwrap();
 
let instruction = transfer(&from, &to, 64);
let tx = Transaction::new(
    &[&from_keypair],
    Message::new(&[instruction], Some(&from)),
    svm.latest_blockhash(),
);
let tx_res = svm.send_transaction(tx).unwrap();
 
let from_account = svm.get_account(&from);
let to_account = svm.get_account(&to);
assert_eq!(from_account.unwrap().lamports, 4936);
assert_eq!(to_account.unwrap().lamports, 64);

部署程序

大多数情况下,我们希望做的不仅仅是玩弄代币转账——我们希望测试自己的程序。

提示:如果您想从mainnet或devnet拉取一个Solana程序,请使用Solana CLI中的solana program dump命令。

要将编译的程序添加到我们的测试中,可以使用.add_program_from_file

下面是一个使用来自Solana程序库的简单程序的示例,它只进行一些日志记录:

use {
    litesvm::LiteSVM,
    solana_instruction::{account_meta::AccountMeta, Instruction},
    solana_keypair::Keypair,
    solana_pubkey::{pubkey, Pubkey},
    solana_message::{Message, VersionedMessage},
    solana_signer::Signer,
    solana_transaction::VersionedTransaction,
};
 
fn test_logging() {
    let program_id = pubkey!("Logging111111111111111111111111111111111111");
    let account_meta = AccountMeta {
        pubkey: Pubkey::new_unique(),
        is_signer: false,
        is_writable: true,
    };
    let ix = Instruction {
        program_id,
        accounts: vec![account_meta],
        data: vec![5, 10, 11, 12, 13, 14],
    };
    let mut svm = LiteSVM::new();
    let payer = Keypair::new();
    let bytes = include_bytes!("../../node-litesvm/program_bytes/spl_example_logging.so");
    svm.add_program(program_id, bytes);
    svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
    let blockhash = svm.latest_blockhash();
    let msg = Message::new_with_blockhash(&[ix], Some(&payer.pubkey()), &blockhash);
    let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[payer]).unwrap();
    // 首先进行仿真
    let sim_res = svm.simulate_transaction(tx.clone()).unwrap();
    let meta = svm.send_transaction(tx).unwrap();
    assert_eq!(sim_res.meta, meta);
    assert_eq!(meta.logs[1], "Program log: static string");
    assert!(meta.compute_units_consumed < 10_000) // 这里不精确以防可能变化
}

时间旅行

许多程序依赖于Clock sysvar:例如,某个代币在特定时间之后才可用。使用litesvm,您可以动态覆盖Clock sysvar,方法是使用svm.set_sysvar::<Clock>()(或在TS中使用.setClock,在Python中使用.set_clock)。下面是一个示例,使用一个在clock.unix_timestamp大于100时会崩溃的程序(该时间为1970年1月1日):

use {
    litesvm::LiteSVM,
    solana_clock::Clock,
    solana_instruction::Instruction,
    use solana_keypair::Keypair,
    solana_message::{Message, VersionedMessage},
    solana_pubkey::Pubkey,
    solana_signer::Signer,
    solana_transaction::VersionedTransaction,
};
 
fn test_set_clock() {
    let program_id = Pubkey::new_unique();
    let mut svm = LiteSVM::new();
    let bytes = include_bytes!("../../node-litesvm/program_bytes/litesvm_clock_example.so");
    svm.add_program(program_id, bytes);
    let payer = Keypair::new();
    let payer_address = payer.pubkey();
    svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
    let blockhash = svm.latest_blockhash();
    let ixs = [Instruction {
        program_id,
        data: vec![],
        accounts: vec![],
    }];
    let msg = Message::new_with_blockhash(&ixs, Some(&payer_address), &blockhash);
    let versioned_msg = VersionedMessage::Legacy(msg);
    let tx = VersionedTransaction::try_new(versioned_msg, &[&payer]).unwrap();
    // 设置时间为2000年1月1日
    let mut initial_clock = svm.get_sysvar::<Clock>();
    initial_clock.unix_timestamp = 1735689600;
    svm.set_sysvar::<Clock>(&initial_clock);
    // 这将失败,因为不再是1970年1月
    svm.send_transaction(tx).unwrap_err();
    // 所以让我们回到过去
    let mut clock = svm.get_sysvar::<Clock>();
    clock.unix_timestamp = 50;
    svm.set_sysvar::<Clock>(&clock);
    let ixs2 = [Instruction {
        program_id,
        data: vec![1], // 未使用,仅用于去重交易
        accounts: vec![],
    }];
    let msg2 = Message::new_with_blockhash(&ixs2, Some(&payer_address), &blockhash);
    let versioned_msg2 = VersionedMessage::Legacy(msg2);
    let tx2 = VersionedTransaction::try_new(versioned_msg2, &[&payer]).unwrap();
    // 此时交易通过
    svm.send_transaction(tx2).unwrap();
}

另请参见:warp_to_slot,它允许您跳到未来的区块。

写入任意账户

LiteSVM允许您编写任意账户数据,无论账户状态是否有可能。

这里是一个示例,我们为一个账户提供了一堆USDC,尽管我们没有USDC铸币的密钥对。这在测试中非常方便,因为这意味着我们不必在测试中使用假的USDC:

use {
    litesvm::LiteSVM,
    solana_account::Account,
    solana_program_option::COption,
    solana_program_pack::Pack,
    solana_pubkey::{pubkey, Pubkey},
    spl_associated_token_account_client::address::get_associated_token_address,
    spl_token::{
        state::{Account as TokenAccount, AccountState},
        ID as TOKEN_PROGRAM_ID,
    },
};
 
fn test_infinite_usdc_mint() {
    let owner = Pubkey::new_unique();
    let usdc_mint = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
    let ata = get_associated_token_address(&owner, &usdc_mint);
    let usdc_to_own = 1_000_000_000_000;
    let token_acc = TokenAccount {
        mint: usdc_mint,
        owner: owner,
        amount: usdc_to_own,
        delegate: COption::None,
        state: AccountState::Initialized,
        is_native: COption::None,
        delegated_amount: 0,
        close_authority: COption::None,
    };
    let mut svm = LiteSVM::new();
    let mut token_acc_bytes = [0u8; TokenAccount::LEN];
    TokenAccount::pack(token_acc, &mut token_acc_bytes).unwrap();
    svm.set_account(
        ata,
        Account {
            lamports: 1_000_000_000,
            data: token_acc_bytes.to_vec(),
            owner: TOKEN_PROGRAM_ID,
            executable: false,
            rent_epoch: 0,
        },
    )
    .unwrap();
    let raw_account = svm.get_account(&ata).unwrap();
    assert_eq!(
        TokenAccount::unpack(&raw_account.data).unwrap().amount,
        usdc_to_own
    )
}

从实时环境复制账户

如果您想从mainnet或devnet复制账户,可以使用Solana CLI中的solana account命令将账户数据保存到文件中。

其他功能

litesvm相关的其他操作包括:

何时使用 solana-test-validator

虽然litesvm速度更快、更方便,但它也与真正的RPC节点相比,降低了相似性。因此,当您需要调用LiteSVM不支持的RPC方法,或当您想要测试某些依赖于实际验证者行为的内容,而不仅仅是测试您的程序和客户端代码时,solana-test-validator仍然是有用的。

不过,通常建议在可能的情况下使用litesvm,因为这将使您的生活更轻松。

On this page

在GitHub上编辑