Solana 技术训练营 2026

2026年01月09日更新 202 人订阅
课程介绍
C4: Solana 程序开发入门: 简单链上数据存储程序扩展为可交易的代币程序
C6:Anchor 入门: SPL 与 Token 2022

使用 LiteSVM 进行测试

LiteSVM 101

Solana 程序测试传统上需要在速度和准确性之间做出选择。LiteSVM 通过提供一个既快速又功能齐全的进程内测试环境,消除了这种权衡。

LiteSVM 为 Solana 开发者提供了三个关键优势:

  • 比 solana-program-test 和 solana-test-validator 执行速度更快
  • 直接操作账户状态,以测试复杂的边界情况
  • 内置性能分析,用于识别优化和改进

什么是 LiteSVM

LiteSVM 是一个轻量级的 Solana 虚拟机,直接在您的测试进程中运行。由 Aursen 和 Exotic Markets 创建,它在保持与 Solana 运行时完全兼容的同时,消除了外部验证器的开销。

与传统的测试方法需要启动单独的验证器进程不同,LiteSVM 将虚拟机嵌入到您的测试中。这种架构在不牺牲测试准确性的情况下,显著提高了编译和执行速度。

该库提供了一个直观的 API,具有智能默认设置以便快速配置,同时还提供了广泛的配置选项以支持高级测试场景。

⚠️ LiteSVM 支持 Rust、TypeScript/JavaScript 和 Python(通过 solders)。

使用 TypeScript 的 LiteSVM

litesvm 包提供了核心测试基础设施,用于创建一个轻量级的 Solana 环境,您可以在其中直接操作账户状态并针对您的程序执行交易。

入门

将 LiteSVM 添加到您的项目中:

npm i --save-dev litesvm

LiteSVM 基础

首先声明您的程序 ID 并创建一个 LiteSVM 实例。

使用您在程序中定义的完全相同的程序 ID,以确保交易正确执行并在测试期间不会抛出 ProgramMismatch 错误:

import { LiteSVM } from "litesvm";
import { PublicKey } from "@solana/web3.js";

const programId = new PublicKey("22222222222222222222222222222222222222222222");

describe("test", () => {
    // Create a new instance of LiteSVM
    const svm = new LiteSVM();

    // Load the program with the right public key
    svm.addProgramFromFile(programId, "target/deploy/program.so");
})

要执行测试,请创建一个交易对象并使用 .sendTransaction(tx) 函数:

import { LiteSVM } from "litesvm";
import { Transaction } from "@solana/web3.js";

describe("test", () => {
    // Create a new instance of LiteSVM
    const svm = new LiteSVM();

    // Create a new Transaction
    const tx = new Transaction();

    // Add the latest blockhash
    tx.recentBlockhash = svm.latestBlockhash();

    // Add the instructions and the signers
    // tx.add(...ixs);
    // tx.sign(...signersKeypair);

    // Send the transaction
    svm.sendTransaction(tx);
})

账户

在使用 LiteSVM 测试 Solana 程序时,您将处理几种类型的账户,这些账户反映了真实世界的程序执行场景。

正确构建这些账户对于有效测试至关重要。

系统账户

最基本的账户类型是系统账户,主要有两种变体:

  • 付款账户:拥有 lamports 的账户,用于资助程序账户创建或 lamport 转账
  • 未初始化账户:没有 lamports 的空账户,通常用于表示等待初始化的程序账户

系统账户不包含数据,并由系统程序拥有。付款账户和未初始化账户的关键区别在于它们的 lamport 余额:付款账户有资金,而未初始化账户从空开始。

以下是在 LiteSVM 中创建 payer 账户的方法:

import { LiteSVM } from "litesvm";
import { Keypair, SystemProgram } from "@solana/web3.js";

describe("test", () => {
    // Create a new instance of LiteSVM
    const svm = new LiteSVM();

    // Create a new Account
    const account = Keypair.generate();

    // Add the Account with the modified data
    svm.setAccount(account.publicKey, {
        lamports: 100_000_000,
        data: Buffer.alloc(0),
        owner: SystemProgram.programId,
        executable: false,
    }); 
})

⚠️ 未初始化账户只是一个普通的生成账户,具有 Keypair.generate() - 无需额外设置。

程序账户

对于包含自定义数据结构的程序账户,您可以使用类似的方法。 您还需要将账户数据序列化到缓冲区中,这可以手动完成,也可以使用诸如 @coral-xyz/borsh 之类的库(参见示例 这里)。

import { LiteSVM } from "litesvm";
import { Keypair } from "@solana/web3.js";

describe("test", () => {
    // Create a new instance of LiteSVM
    const svm = new LiteSVM();

    // Create a new Account
    const account = Keypair.generate();

    // Populate the data of the Account
    const accountData = Buffer.alloc(SIZE_OF_THE_ACCOUNT);

    // Serialize the account data into the byte buffer defined above
    // ...

    // Grab the minimum amount of lamports to make it rent exempt
    const lamports = svm.minimumBalanceForRentExemption(SIZE_OF_THE_ACCOUNT);

    // Add the Account with the modified data
    svm.setAccount(account.publicKey, {
        lamports,
        data: accountData,
        owner: PROGRAM_ID,
        executable: false,
    });
})

⚠️ 在测试中,您无需计算精确的租金。您可以将 lamports 设置为一个较大的值,例如 100_000_000_000,并跳过租金计算,因为这些并不是真实的资金。

代币账户

要序列化 SPL 代币账户的数据,您可以使用 AccountLayout 和 MintLayout,它们来自 @solana/spl-token

import { LiteSVM } from "litesvm";
import { Keypair } from "@solana/web3.js";
import { TOKEN_PROGRAM_ID, AccountLayout, MintLayout, ACCOUNT_SIZE, MINT_SIZE } from "@solana/spl-token"

describe("test", () => {
    // Create a new instance of LiteSVM
    const svm = new LiteSVM();

    const owner = Keypair.generate();

    // Create a new Mint Account
    const mint = Keypair.generate();

    // Populate the data of the Mint Account
    let mintData = Buffer.alloc(MINT_SIZE);
    MintLayout.encode(
        {
          mintAuthorityOption: 1,
          mintAuthority: owner.publicKey,
          supply: BigInt(0),
          decimals: 0,
          isInitialized: true,
          freezeAuthorityOption: 0,
          freezeAuthority: PublicKey.default,
        },
        mintData
    )

    // Grab the minimum amount of lamports to make it rent exempt
    const lamports = svm.minimumBalanceForRentExemption(MINT_SIZE);

    // Add the Account with the modified data
    svm.setAccount(mint.publicKey, {
        lamports,
        data: mintData,
        owner: TOKEN_PROGRAM_ID,
        executable: false,
    });

    // Create a new Token Account
    const tokenAccount = Keypair.generate();

    // Populate the data of the Token Account
    const tokenAccountData = Buffer.alloc(ACCOUNT_SIZE);
    AccountLayout.encode(
        {
            mint: mint.publicKey,
            owner: owner.publicKey,
            amount: BigInt(100),
            delegateOption: 0,
            delegate: PublicKey.default,
            delegatedAmount: BigInt(0),
            state: 1,
            isNativeOption: 0,
            isNative: BigInt(0),
            closeAuthorityOption: 0,
            closeAuthority: PublicKey.default,
        },
        tokenAccountData,
    );

    // Grab the minimum amount of lamports to make it rent exempt
    const lamports = svm.minimumBalanceForRentExemption(ACCOUNT_SIZE);

    // Add the Account with the modified data
    svm.setAccount(tokenAccount.publicKey, {
        lamports,
        data: tokenAccountData,
        owner: TOKEN_PROGRAM_ID,
        executable: false,
    });
})

Execution

在创建账户并将其添加到您的 LiteSVM 实例后,您现在可以发送交易并验证您的程序逻辑。

在发送交易之前,您可以模拟结果:

// Simulate before executing
const simulatedResult = svm.simulateTransaction(tx);

然后发送交易并检查其日志:

// Execute and inspect logs
const result = svm.sendTransaction(tx);
console.log(result.logs);

高级功能

在执行之前和之后,您 LiteSVM 实例中包含的整个账本都是可读和可自定义的。

您可以操作 sysvar 值,例如时钟:

// Change the Clock
const newClock = svm.getClock();
newClock.unixTimestamp = 50n;
svm.setClock(newClock);

// Jump to a certain Slot
svm.warpToSlot(500);

// Expire the current blockhash
svm.expireBlockhash();

您还可以读取账户和协议数据:

// Get all the information about an account (data, lamports, owner, ...)
svm.getAccount(account.publicKey);

// Get the lamport balance of an account
svm.getBalance(account.publicKey);

或者配置运行时的行为:

// Sets the compute budget
const computeBudget = new ComputeBudget();
computeBudget.computeUnitLimit = 2_000_000n;
svm.withComputeBudget(computeBudget);

// Sets Sigverify as active
svm.withSigverify(true);

// Sets the Blockhash check as active
svm.withBlockhashCheck(true);

// Sets the default Sysvars
svm.withSysvars();

// Set the FeatureSet to use
svm.withFeatureSet(new FeatureSet(...))

使用 Rust 的 LiteSVM

litesvm 包提供了核心测试基础设施,用于创建一个轻量级的 Solana 环境,在该环境中您可以直接操作账户状态并针对您的程序执行交易。

入门

将 LiteSVM 添加到您的项目中:

cargo add --dev litesvm

LiteSVM 基础

首先声明您的程序 ID 并创建一个 LiteSVM 实例。

使用您在程序中定义的完全相同的程序 ID,以确保交易正确执行并且在测试期间不会抛出 ProgramMismatch 错误:

use litesvm::LiteSVM;
use solana_pubkey::{pubkey, Pubkey};

const program_id: Pubkey = pubkey!("22222222222222222222222222222222222222222222");

#[test]
fn test() {
    // Create a new instance of LiteSVM
    let mut svm = LiteSVM::new();

    // Load the program with the right publickey
    svm.add_program_from_file(program_id, "target/deploy/program.so");
}

要执行测试,请创建一个交易对象并使用 .send_transaction(tx) 函数:

use litesvm::LiteSVM;
use solana_transaction::Transaction;

#[test]
fn test() {
    // Create a new instance of LiteSVM
    let mut svm = LiteSVM::new();

    // Create a new Transaction
    let mut tx = Transaction::new_signed_with_payer(
        &[...ixs],
        Some(&payer.pubkey()),
        &[...signersKeypair],
        svm.latest_blockhash(),
    );

    // Send the Transaction
    let result = svm.send_transaction(tx).unwrap();
}

账户

在使用 LiteSVM 测试 Solana 程序时,您将处理多种类型的账户,这些账户反映了真实世界的程序执行场景。

正确构建这些账户对于有效测试至关重要。

系统账户

最基本的账户类型是系统账户,主要有两种变体:

  • 付款账户:拥有 lamports 的账户,用于资助程序账户的创建或 lamport 转账
  • 未初始化账户:没有 lamports 的空账户,通常用于表示等待初始化的程序账户

系统账户不包含数据,并由系统程序(System Program)拥有。付款账户和未初始化账户的关键区别在于它们的 lamport 余额:付款账户有资金,而未初始化账户从空开始。

以下是在 LiteSVM 中创建 payer 账户的方法:

use litesvm::LiteSVM;
use solana_account::Account;
use solana_keypair::Keypair;
use solana_pubkey::{pubkey, Pubkey};

#[test]
fn test() {
    // Create a new instance of LiteSVM
    let mut svm = LiteSVM::new();

    // Create a new Account
    let account = Keypair::new();

    // Add the Account with the modified data
    svm.set_account(
        account.pubkey(),
        Account {
            lamports: 100_000_000,
            data: [],
            owner: ID,
            executable: false,
            rent_epoch: 0,
        },
    );
}

⚠️ 未初始化账户只是一个普通的生成账户,具有 Keypair.generate() - 无需额外设置。

程序账户

对于包含自定义数据结构的程序账户,您可以使用类似的方法。 您还需要将账户数据序列化为字节数组,这可以手动完成,也可以使用诸如 borshbincode 或 solana_program_pack 等库来完成。

use litesvm::LiteSVM;
use solana_account::Account;
use solana_keypair::Keypair;
use solana_pubkey::{pubkey, Pubkey};

#[test]
fn test() {
    // Create a new instance of LiteSVM
    let mut svm = LiteSVM::new();

    // Create a new Account
    let account = Keypair::new();

    let mut account_data = [0; SIZE_OF_THE_ACCOUNT];

    // Serialize the account data into the byte array defined above
    // ...

    let lamports = svm.minimum_balance_for_rent_exemption(SIZE_OF_THE_ACCOUNT);

    // Add the Account with the modified data
    svm.set_account(
        account.pubkey(),
        Account {
            lamports,
            data: account_data,
            owner: ID,
            executable: false,
            rent_epoch: 0,
        },
    )
}

⚠️ 在测试中,您无需计算精确的租金。您可以将 lamports 设置为一个较大的值,例如 100_000_000_000,并跳过租金计算,因为这些并不是真实的资金。

代币账户

要序列化 SPL 代币账户的数据,您可以使用 spl_token::Mint 和 spl_token::Account,它们实现了 solana_program_pack::Pack

use litesvm::LiteSVM;
use solana_keypair::Keypair;
use solana_pubkey::{pubkey, Pubkey};
use solana_account::Account;
use spl_token::{ID as TOKEN_PROGRAM_ID, state::{Mint, Account as TokenAccount}};
use solana_program_pack::Pack;

#[test]
fn test() {
    // Create a new instance of LiteSVM
    let mut svm = LiteSVM::new();

    // Create a new Mint Account
    let mint = Keypair::new();

    // Populate the data of the Mint Account
    let mint_data = Mint {
        mint_authority: None.into(),
        supply: 0,
        decimals: 6,
        is_initialized: true,
        freeze_authority: None.into(),
    };

    let mut mint_account_data = vec![0; Mint::LEN];
    Mint::pack(mint_data, &mut mint_account_data).unwrap();

    // Grab the minimum amount of lamports to make it rent exempt
    let lamports = svm.minimum_balance_for_rent_exemption(Mint::LEN);

    // Add the Mint Account
    svm.set_account(
        mint.pubkey(),
        Account {
            lamports,
            data: mint_account_data,
            owner: TOKEN_PROGRAM_ID,
            executable: false,
            rent_epoch: 0,
        },
    );

    // Create a new Token Account
    let token_account = Keypair::new();
    let owner = Keypair::new();

    // Populate the data of the Token Account
    let token_account_data = 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(),
    };

    let mut token_account_data_bytes = vec![0; TokenAccount::LEN];
    TokenAccount::pack(token_account_data, &mut token_account_data_bytes).unwrap();

    // Grab the minimum amount of lamports to make it rent exempt
    let lamports = svm.minimum_balance_for_rent_exemption(TokenAccount::LEN);

    // Add the Token Account
    svm.set_account(
        token_account.pubkey(),
        Account {
            lamports,
            data: token_account_data_bytes,
            owner: TOKEN_PROGRAM_ID,
            executable: false,
            rent_epoch: 0,
        },
    );
}

Execution

在创建账户并将其添加到您的 LiteSVM 实例后,您现在可以发送交易并验证您的程序逻辑。

在发送交易之前,您可以模拟结果:

let simulated_result = svm.simulate_transaction(tx);

然后发送交易并检查其日志:

let result = svm.send_transaction(tx);
let logs = result.logs;

高级功能

在执行之前和之后,您 LiteSVM 实例中包含的整个账本都是可读和可自定义的。

您可以操作 sysvar 值,例如时钟:

// Change the Clock
let mut new_clock = svm.get_sysvar::<Clock>();
new_clock.unix_timestamp = 1735689600;
svm.set_sysvar::<Clock>(&new_clock);

// Jump to a certain Slot
svm.warp_to_slot(500);

// Expire the current blockhash
svm.expire_blockhash();

您还可以读取账户和协议数据:

// Get all the information about an account (data, lamports, owner, ...)
svm.get_account(&account.publickey);

// Get the lamport balance of an account
svm.get_balance(&account.publickey);

// Get the number of Compute Unit used till now
svm.get_compute_budget();

或者配置运行时的行为:

// Sets the compute budget
let compute_budget = ComputeBudget::default();
compute_budget.compute_unit_limit = 2_000_000;
svm.with_compute_budget(compute_budget);

// Sets Sigverify as active
svm.with_sigverify(true);

// Sets the Blockhash check as active
svm.with_blockhash_check(true);

// Sets the default Sysvars
svm.with_sysvars();

// Set the FeatureSet to use
svm.with_feature_set(FeatureSet::default())
点赞 0
收藏 0
分享

0 条评论

请先 登录 后评论