CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-solders

Python bindings for Solana Rust tools providing high-performance blockchain development primitives, RPC functionality, and testing infrastructure.

Pending
Overview
Eval results
Files

testing-infrastructure.mddocs/

Testing Infrastructure

LiteSVM lightweight runtime for local testing and transaction simulation without requiring a full Solana network. This provides a complete local Solana environment for development, testing, and integration scenarios with fast execution and deterministic results.

Capabilities

LiteSVM Runtime

Lightweight Solana Virtual Machine for local testing and development with full program execution capabilities.

class LiteSVM:
    """
    Lightweight Solana runtime for testing and transaction simulation.
    
    Provides a complete local Solana environment without requiring network connectivity
    or external validator nodes. Supports all core Solana features including:
    - Transaction execution and simulation
    - Account state management
    - Built-in program execution (System, Token, etc.)
    - Custom program deployment and execution
    - Deterministic block production
    """
    def __init__(self):
        """
        Create new LiteSVM instance with default configuration.
        
        Initializes a fresh Solana runtime with:
        - Genesis accounts and configuration
        - Built-in programs (System, Token, etc.)
        - Default rent and fee configurations
        - Empty validator set (single node)
        """

    @classmethod
    def new_with_config(cls, config: 'LiteSvmConfig') -> 'LiteSVM':
        """
        Create LiteSVM with custom configuration.
        
        Parameters:
        - config: LiteSvmConfig, runtime configuration options
        
        Returns:
        LiteSVM instance with specified configuration
        """

    def send_transaction(self, transaction: Union[Transaction, VersionedTransaction]) -> 'TransactionResult':
        """
        Execute transaction and return detailed results.
        
        Parameters:
        - transaction: Union transaction to execute (must be signed)
        
        Returns:
        TransactionResult with execution metadata or error details
        
        Raises:
        - ValueError: if transaction is not properly signed
        - RuntimeError: if transaction execution fails critically
        """

    def simulate_transaction(
        self,
        transaction: Union[Transaction, VersionedTransaction],
        config: Optional['SimulationConfig'] = None
    ) -> 'SimulateResult':
        """
        Simulate transaction execution without committing state changes.
        
        Parameters:
        - transaction: Union transaction to simulate
        - config: Optional[SimulationConfig], simulation parameters
        
        Returns:
        SimulateResult with execution results and logs
        """

    def get_account(self, pubkey: Pubkey) -> Optional[Account]:
        """
        Retrieve account data and metadata.
        
        Parameters:
        - pubkey: Pubkey, account address to query
        
        Returns:
        Optional[Account], account data or None if account doesn't exist
        """

    def get_balance(self, pubkey: Pubkey) -> int:
        """
        Get account balance in lamports.
        
        Parameters:
        - pubkey: Pubkey, account address
        
        Returns:
        int, account balance in lamports (0 if account doesn't exist)
        """

    def airdrop(self, pubkey: Pubkey, lamports: int) -> None:
        """
        Add lamports to account (test utility).
        
        Parameters:
        - pubkey: Pubkey, recipient account
        - lamports: int, amount to add
        
        Note:
        Creates account if it doesn't exist
        """

    def create_account(
        self,
        pubkey: Pubkey,
        lamports: int,
        data: bytes,
        owner: Pubkey
    ) -> None:
        """
        Create account with specified data and owner.
        
        Parameters:
        - pubkey: Pubkey, new account address
        - lamports: int, initial balance
        - data: bytes, account data
        - owner: Pubkey, program that owns the account
        
        Raises:
        - ValueError: if account already exists
        """

    def set_account(
        self,
        pubkey: Pubkey,
        account: Account
    ) -> None:
        """
        Set complete account state (test utility).
        
        Parameters:
        - pubkey: Pubkey, account address
        - account: Account, complete account data to set
        """

    def get_clock(self) -> Clock:
        """
        Get current runtime clock information.
        
        Returns:
        Clock with current slot, epoch, and timestamp
        """

    def get_rent(self) -> Rent:
        """
        Get current rent configuration.
        
        Returns:
        Rent configuration for exemption calculations
        """

    def advance_slot(self) -> None:
        """
        Advance to next slot manually.
        
        Increments slot counter and updates clock sysvar.
        Useful for testing time-dependent behavior.
        """

    def advance_slots(self, count: int) -> None:
        """
        Advance multiple slots.
        
        Parameters:
        - count: int, number of slots to advance
        """

    def set_slot(self, slot: int) -> None:
        """
        Set current slot number.
        
        Parameters:
        - slot: int, slot number to set
        
        Note:
        Updates all time-dependent sysvars
        """

    def get_latest_blockhash(self) -> Hash:
        """
        Get current blockhash for transactions.
        
        Returns:
        Hash representing current block
        """

    def get_program_accounts(
        self,
        program_id: Pubkey,
        filters: Optional[List['AccountFilter']] = None
    ) -> List[tuple[Pubkey, Account]]:
        """
        Get all accounts owned by program.
        
        Parameters:
        - program_id: Pubkey, program to search for
        - filters: Optional[List[AccountFilter]], account filtering criteria
        
        Returns:
        List[tuple[Pubkey, Account]], matching accounts with addresses
        """

    def load_program(self, program_id: Pubkey, elf_bytes: bytes) -> None:
        """
        Load custom program into runtime.
        
        Parameters:
        - program_id: Pubkey, address for program
        - elf_bytes: bytes, compiled program binary (ELF format)
        
        Raises:
        - ValueError: if program binary is invalid
        """

    def get_transaction_count(self) -> int:
        """
        Get total number of transactions processed.
        
        Returns:
        int, transaction count since runtime creation
        """

    def get_signature_status(self, signature: Signature) -> Optional['TransactionStatus']:
        """
        Get status of previously executed transaction.
        
        Parameters:
        - signature: Signature, transaction signature to query
        
        Returns:
        Optional[TransactionStatus], transaction status or None if not found
        """

Transaction Results and Metadata

Detailed execution results and metadata from transaction processing.

class TransactionMetadata:
    """
    Successful transaction execution metadata.
    """
    def __init__(
        self,
        compute_units_consumed: int,
        fee: int,
        inner_instructions: List['InnerInstruction'],
        log_messages: List[str],
        return_data: Optional[bytes],
        status: 'TransactionStatus'
    ):
        """
        Create transaction metadata for successful execution.
        
        Parameters:
        - compute_units_consumed: int, compute units used
        - fee: int, transaction fee paid
        - inner_instructions: List[InnerInstruction], cross-program invocations
        - log_messages: List[str], program log messages
        - return_data: Optional[bytes], program return data
        - status: TransactionStatus, final transaction status
        """

    @property
    def compute_units_consumed(self) -> int:
        """Compute units used in execution."""

    @property
    def fee(self) -> int:
        """Transaction fee paid in lamports."""

    @property
    def inner_instructions(self) -> List['InnerInstruction']:
        """Cross-program invocations executed."""

    @property
    def log_messages(self) -> List[str]:
        """Program log messages."""

    @property
    def return_data(self) -> Optional[bytes]:
        """Program return data."""

    @property
    def status(self) -> 'TransactionStatus':
        """Final transaction status."""

    def get_logs_for_program(self, program_id: Pubkey) -> List[str]:
        """
        Filter log messages for specific program.
        
        Parameters:
        - program_id: Pubkey, program to filter logs for
        
        Returns:
        List[str], log messages from specified program
        """

class FailedTransactionMetadata:
    """
    Failed transaction execution metadata.
    """
    def __init__(
        self,
        compute_units_consumed: int,
        fee: int,
        inner_instructions: List['InnerInstruction'],
        log_messages: List[str],
        error: 'TransactionErrorType'
    ):
        """
        Create failed transaction metadata.
        
        Parameters:
        - compute_units_consumed: int, compute units used before failure
        - fee: int, transaction fee paid
        - inner_instructions: List[InnerInstruction], instructions executed before failure
        - log_messages: List[str], program logs before failure
        - error: TransactionErrorType, error that caused failure
        """

    @property
    def compute_units_consumed(self) -> int:
        """Compute units used before failure."""

    @property
    def fee(self) -> int:
        """Transaction fee paid."""

    @property
    def inner_instructions(self) -> List['InnerInstruction']:
        """Instructions executed before failure."""

    @property
    def log_messages(self) -> List[str]:
        """Program logs before failure."""

    @property
    def error(self) -> 'TransactionErrorType':
        """Error that caused transaction failure."""

class SimulatedTransactionInfo:
    """
    Transaction simulation results.
    """
    def __init__(
        self,
        compute_units_consumed: int,
        inner_instructions: List['InnerInstruction'],
        log_messages: List[str],
        return_data: Optional[bytes],
        accounts: Optional[List[Account]]
    ):
        """
        Create simulation results.
        
        Parameters:
        - compute_units_consumed: int, compute units that would be used
        - inner_instructions: List[InnerInstruction], CPIs that would execute
        - log_messages: List[str], program logs from simulation
        - return_data: Optional[bytes], program return data from simulation
        - accounts: Optional[List[Account]], final account states (if requested)
        """

    @property
    def compute_units_consumed(self) -> int:
        """Compute units that would be consumed."""

    @property
    def inner_instructions(self) -> List['InnerInstruction']:
        """Cross-program invocations that would execute."""

    @property
    def log_messages(self) -> List[str]:
        """Program log messages from simulation."""

    @property
    def return_data(self) -> Optional[bytes]:
        """Program return data from simulation."""

    @property
    def accounts(self) -> Optional[List[Account]]:
        """Final account states after simulation."""

class InnerInstruction:
    """
    Inner instruction from cross-program invocation.
    """
    def __init__(self, instruction_index: int, instructions: List[Instruction]):
        """
        Create inner instruction container.
        
        Parameters:
        - instruction_index: int, parent instruction index
        - instructions: List[Instruction], inner instructions executed
        """

    @property
    def instruction_index(self) -> int:
        """Parent instruction index that generated these inner instructions."""

    @property
    def instructions(self) -> List[Instruction]:
        """Inner instructions that were executed."""

# Type aliases for transaction results
TransactionResult = Union[TransactionMetadata, FailedTransactionMetadata]
SimulateResult = Union[SimulatedTransactionInfo, FailedTransactionMetadata]

Configuration and Filtering

Runtime configuration options and account filtering capabilities.

class LiteSvmConfig:
    """
    Configuration options for LiteSVM runtime.
    """
    def __init__(
        self,
        rent: Optional[Rent] = None,
        fee_rate_governor: Optional['FeeRateGovernor'] = None,
        genesis_config: Optional['GenesisConfig'] = None
    ):
        """
        Create LiteSVM configuration.
        
        Parameters:
        - rent: Optional[Rent], rent configuration
        - fee_rate_governor: Optional[FeeRateGovernor], fee calculation parameters
        - genesis_config: Optional[GenesisConfig], genesis block configuration
        """

    @classmethod
    def default() -> 'LiteSvmConfig':
        """Create default configuration suitable for testing."""

class SimulationConfig:
    """
    Configuration for transaction simulation.
    """
    def __init__(
        self,
        sig_verify: bool = True,
        replace_recent_blockhash: bool = False,
        commitment: CommitmentLevel = CommitmentLevel.Processed,
        accounts_to_return: Optional[List[Pubkey]] = None
    ):
        """
        Create simulation configuration.
        
        Parameters:
        - sig_verify: bool, whether to verify signatures during simulation
        - replace_recent_blockhash: bool, use current blockhash instead of transaction's
        - commitment: CommitmentLevel, commitment level for simulation context
        - accounts_to_return: Optional[List[Pubkey]], accounts to include in response
        """

class AccountFilter:
    """
    Filter criteria for account queries.
    """
    def __init__(self, offset: int, bytes: bytes):
        """
        Create account filter by data content.
        
        Parameters:
        - offset: int, byte offset in account data
        - bytes: bytes, data that must match at offset
        """

    @classmethod
    def memcmp(cls, offset: int, bytes: bytes) -> 'AccountFilter':
        """
        Create memcmp filter.
        
        Parameters:
        - offset: int, byte offset to compare
        - bytes: bytes, expected data at offset
        
        Returns:
        AccountFilter for memory comparison
        """

    @classmethod
    def data_size(cls, size: int) -> 'AccountFilter':
        """
        Create data size filter.
        
        Parameters:
        - size: int, required account data size
        
        Returns:
        AccountFilter for data size matching
        """

Usage Examples

Basic Testing Setup

from solders.litesvm import LiteSVM
from solders.keypair import Keypair
from solders.system_program import transfer, TransferParams

# Create test environment
svm = LiteSVM()

# Create test accounts
payer = Keypair()
recipient = Keypair()

# Fund payer account
svm.airdrop(payer.pubkey(), 10_000_000_000)  # 10 SOL

print(f"Payer balance: {svm.get_balance(payer.pubkey())} lamports")
print(f"Recipient balance: {svm.get_balance(recipient.pubkey())} lamports")

# Create transfer transaction
transfer_ix = transfer(TransferParams(
    from_pubkey=payer.pubkey(),
    to_pubkey=recipient.pubkey(),
    lamports=1_000_000_000  # 1 SOL
))

# Build and sign transaction
from solders.transaction import Transaction
tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())
tx.recent_blockhash = svm.get_latest_blockhash()
tx.sign([payer], tx.recent_blockhash)

# Execute transaction
result = svm.send_transaction(tx)

if isinstance(result, TransactionMetadata):
    print("Transaction successful!")
    print(f"Fee paid: {result.fee} lamports")
    print(f"Compute units used: {result.compute_units_consumed}")
    
    # Check updated balances
    print(f"Payer balance after: {svm.get_balance(payer.pubkey())} lamports")
    print(f"Recipient balance after: {svm.get_balance(recipient.pubkey())} lamports")
else:
    print(f"Transaction failed: {result.error}")

Token Program Testing

from solders.token.associated import get_associated_token_address
from solders.instruction import Instruction, AccountMeta
from solders.token import ID as TOKEN_PROGRAM_ID

# Create token mint and accounts for testing
def setup_token_test_environment(svm: LiteSVM):
    """Set up token mint and associated accounts for testing."""
    
    # Create mint authority
    mint_authority = Keypair()
    svm.airdrop(mint_authority.pubkey(), 5_000_000_000)  # 5 SOL for fees
    
    # Create mint account
    mint = Keypair()
    
    # Create mint account with proper size and owner
    mint_account = Account(
        lamports=svm.get_rent().minimum_balance(82),  # Mint account size
        data=bytes(82),  # Empty mint data (will be initialized)
        owner=TOKEN_PROGRAM_ID,
        executable=False,
        rent_epoch=0
    )
    svm.set_account(mint.pubkey(), mint_account)
    
    # Initialize mint (this would normally be done via instruction)
    # For testing, we'll create a properly initialized mint
    from solders.token.state import Mint
    mint_data = Mint(
        mint_authority=mint_authority.pubkey(),
        supply=0,
        decimals=9,
        is_initialized=True,
        freeze_authority=None
    )
    
    mint_account_initialized = Account(
        lamports=mint_account.lamports,
        data=mint_data.serialize(),
        owner=TOKEN_PROGRAM_ID,
        executable=False,
        rent_epoch=0
    )
    svm.set_account(mint.pubkey(), mint_account_initialized)
    
    return mint_authority, mint

def test_token_operations(svm: LiteSVM):
    """Test token minting and transfers."""
    mint_authority, mint = setup_token_test_environment(svm)
    
    # Create user accounts
    user1 = Keypair()
    user2 = Keypair()
    svm.airdrop(user1.pubkey(), 2_000_000_000)  # Fund for fees
    svm.airdrop(user2.pubkey(), 2_000_000_000)
    
    # Calculate associated token accounts
    user1_ata = get_associated_token_address(user1.pubkey(), mint.pubkey())
    user2_ata = get_associated_token_address(user2.pubkey(), mint.pubkey())
    
    print(f"User1 ATA: {user1_ata}")
    print(f"User2 ATA: {user2_ata}")
    
    # Test ATA creation and token minting would go here
    # This requires implementing the full SPL token instruction set
    
    return mint_authority, mint, user1, user2

Transaction Simulation

def test_transaction_simulation(svm: LiteSVM):
    """Test transaction simulation before execution."""
    
    # Setup accounts
    payer = Keypair()
    recipient = Keypair()
    svm.airdrop(payer.pubkey(), 5_000_000_000)
    
    # Create transaction
    transfer_ix = transfer(TransferParams(
        from_pubkey=payer.pubkey(),
        to_pubkey=recipient.pubkey(),
        lamports=2_000_000_000  # 2 SOL
    ))
    
    tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())
    tx.recent_blockhash = svm.get_latest_blockhash()
    tx.sign([payer], tx.recent_blockhash)
    
    # Simulate before executing
    simulation_config = SimulationConfig(
        sig_verify=True,
        replace_recent_blockhash=False,
        accounts_to_return=[payer.pubkey(), recipient.pubkey()]
    )
    
    sim_result = svm.simulate_transaction(tx, simulation_config)
    
    if isinstance(sim_result, SimulatedTransactionInfo):
        print("Simulation successful!")
        print(f"Estimated compute units: {sim_result.compute_units_consumed}")
        print(f"Log messages: {len(sim_result.log_messages)}")
        
        # Show final account states
        if sim_result.accounts:
            for i, account in enumerate(sim_result.accounts):
                print(f"Account {i} final balance: {account.lamports}")
        
        # Now execute the actual transaction
        result = svm.send_transaction(tx)
        print(f"Actual execution result: {type(result).__name__}")
        
    else:
        print(f"Simulation failed: {sim_result.error}")
        print("Transaction would fail, not executing")

Program Testing

def test_custom_program(svm: LiteSVM):
    """Test custom program deployment and execution."""
    
    # Load custom program (would need actual program binary)
    program_id = Keypair().pubkey()  # Random program ID for example
    
    # Note: In real usage, you'd load actual program bytecode:
    # program_elf = open("my_program.so", "rb").read()
    # svm.load_program(program_id, program_elf)
    
    # For this example, we'll test with system program functionality
    payer = Keypair()
    svm.airdrop(payer.pubkey(), 10_000_000_000)
    
    # Create account for program data
    data_account = Keypair()
    
    # Create account creation instruction
    from solders.system_program import create_account, CreateAccountParams
    create_ix = create_account(CreateAccountParams(
        from_pubkey=payer.pubkey(),
        to_pubkey=data_account.pubkey(),
        lamports=svm.get_rent().minimum_balance(100),
        space=100,
        owner=program_id  # Our "custom" program owns this account
    ))
    
    # Execute account creation
    tx = Transaction.new_with_payer([create_ix], payer.pubkey())
    tx.recent_blockhash = svm.get_latest_blockhash()
    tx.sign([payer, data_account], tx.recent_blockhash)
    
    result = svm.send_transaction(tx)
    
    if isinstance(result, TransactionMetadata):
        print("Account created successfully!")
        
        # Verify account was created
        created_account = svm.get_account(data_account.pubkey())
        if created_account:
            print(f"Account owner: {created_account.owner}")
            print(f"Account size: {len(created_account.data)} bytes")
        
        # Test program account query
        program_accounts = svm.get_program_accounts(program_id)
        print(f"Found {len(program_accounts)} accounts owned by program")
        
    else:
        print(f"Account creation failed: {result.error}")

Advanced Testing Scenarios

def test_multi_instruction_transaction(svm: LiteSVM):
    """Test transaction with multiple instructions and cross-program invocations."""
    
    # Setup
    payer = Keypair()
    recipient1 = Keypair()
    recipient2 = Keypair()
    svm.airdrop(payer.pubkey(), 10_000_000_000)
    
    # Create multiple instructions
    from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price
    
    instructions = [
        set_compute_unit_limit(300_000),  # Set compute budget
        set_compute_unit_price(1000),     # Set priority fee
        transfer(TransferParams(          # First transfer
            from_pubkey=payer.pubkey(),
            to_pubkey=recipient1.pubkey(),
            lamports=1_000_000_000
        )),
        transfer(TransferParams(          # Second transfer
            from_pubkey=payer.pubkey(),
            to_pubkey=recipient2.pubkey(),
            lamports=500_000_000
        ))
    ]
    
    # Execute transaction
    tx = Transaction.new_with_payer(instructions, payer.pubkey())
    tx.recent_blockhash = svm.get_latest_blockhash()
    tx.sign([payer], tx.recent_blockhash)
    
    result = svm.send_transaction(tx)
    
    if isinstance(result, TransactionMetadata):
        print("Multi-instruction transaction successful!")
        print(f"Total compute units: {result.compute_units_consumed}")
        print(f"Transaction fee: {result.fee}")
        print(f"Log messages: {len(result.log_messages)}")
        
        # Verify balances
        balances = {
            "payer": svm.get_balance(payer.pubkey()),
            "recipient1": svm.get_balance(recipient1.pubkey()),
            "recipient2": svm.get_balance(recipient2.pubkey())
        }
        
        for name, balance in balances.items():
            print(f"{name} balance: {balance:,} lamports")
    else:
        print(f"Transaction failed: {result.error}")

def test_time_dependent_behavior(svm: LiteSVM):
    """Test behavior that depends on slots/time."""
    
    initial_clock = svm.get_clock()
    print(f"Initial slot: {initial_clock.slot}")
    print(f"Initial epoch: {initial_clock.epoch}")
    
    # Advance time
    svm.advance_slots(100)
    
    updated_clock = svm.get_clock()
    print(f"After advancing slot: {updated_clock.slot}")
    print(f"Slot difference: {updated_clock.slot - initial_clock.slot}")
    
    # Test blockhash changes
    initial_blockhash = svm.get_latest_blockhash()
    svm.advance_slots(1)
    new_blockhash = svm.get_latest_blockhash()
    
    print(f"Blockhash changed: {initial_blockhash != new_blockhash}")

def benchmark_transaction_performance(svm: LiteSVM):
    """Benchmark transaction execution performance."""
    import time
    
    # Setup
    payers = [Keypair() for _ in range(100)]
    recipients = [Keypair() for _ in range(100)]
    
    for payer in payers:
        svm.airdrop(payer.pubkey(), 2_000_000_000)
    
    # Prepare transactions
    transactions = []
    for payer, recipient in zip(payers, recipients):
        transfer_ix = transfer(TransferParams(
            from_pubkey=payer.pubkey(),
            to_pubkey=recipient.pubkey(),
            lamports=1_000_000
        ))
        
        tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())
        tx.recent_blockhash = svm.get_latest_blockhash()
        tx.sign([payer], tx.recent_blockhash)
        transactions.append(tx)
    
    # Execute and time
    start_time = time.time()
    results = []
    
    for tx in transactions:
        result = svm.send_transaction(tx)
        results.append(result)
    
    end_time = time.time()
    
    # Analyze results
    successful = sum(1 for r in results if isinstance(r, TransactionMetadata))
    total_time = end_time - start_time
    
    print(f"Executed {len(transactions)} transactions in {total_time:.3f}s")
    print(f"Success rate: {successful}/{len(transactions)} ({successful/len(transactions)*100:.1f}%)")
    print(f"Throughput: {len(transactions)/total_time:.1f} TPS")
    
    if successful > 0:
        successful_results = [r for r in results if isinstance(r, TransactionMetadata)]
        avg_compute = sum(r.compute_units_consumed for r in successful_results) / len(successful_results)
        avg_fee = sum(r.fee for r in successful_results) / len(successful_results)
        
        print(f"Average compute units: {avg_compute:.0f}")
        print(f"Average fee: {avg_fee:.0f} lamports")

Testing Utilities

class TestEnvironment:
    """Helper class for managing test environments."""
    
    def __init__(self):
        self.svm = LiteSVM()
        self.funded_accounts = {}
    
    def create_funded_keypair(self, lamports: int = 10_000_000_000) -> Keypair:
        """Create and fund a new keypair."""
        keypair = Keypair()
        self.svm.airdrop(keypair.pubkey(), lamports)
        self.funded_accounts[keypair.pubkey()] = keypair
        return keypair
    
    def execute_and_assert_success(self, transaction: Transaction) -> TransactionMetadata:
        """Execute transaction and assert it succeeds."""
        result = self.svm.send_transaction(transaction)
        assert isinstance(result, TransactionMetadata), f"Transaction failed: {result.error if hasattr(result, 'error') else 'Unknown error'}"
        return result
    
    def simulate_and_assert_success(self, transaction: Transaction) -> SimulatedTransactionInfo:
        """Simulate transaction and assert it succeeds."""
        result = self.svm.simulate_transaction(transaction)
        assert isinstance(result, SimulatedTransactionInfo), f"Simulation failed: {result.error if hasattr(result, 'error') else 'Unknown error'}"
        return result
    
    def assert_balance_change(self, pubkey: Pubkey, expected_change: int, tolerance: int = 0):
        """Assert account balance changed by expected amount."""
        current_balance = self.svm.get_balance(pubkey)
        if pubkey not in self.last_balances:
            self.last_balances[pubkey] = current_balance
            return
        
        actual_change = current_balance - self.last_balances[pubkey]
        assert abs(actual_change - expected_change) <= tolerance, \
               f"Expected balance change {expected_change}, got {actual_change}"
        
        self.last_balances[pubkey] = current_balance
    
    def snapshot_balances(self):
        """Take snapshot of all tracked account balances."""
        self.last_balances = {}
        for pubkey in self.funded_accounts.keys():
            self.last_balances[pubkey] = self.svm.get_balance(pubkey)

# Usage example
def test_with_environment():
    """Example using the test environment helper."""
    env = TestEnvironment()
    
    # Create test accounts
    payer = env.create_funded_keypair(10_000_000_000)  # 10 SOL
    recipient = env.create_funded_keypair(0)           # Empty account
    
    # Snapshot initial state
    env.snapshot_balances()
    
    # Create and execute transaction
    transfer_amount = 1_000_000_000  # 1 SOL
    transfer_ix = transfer(TransferParams(
        from_pubkey=payer.pubkey(),
        to_pubkey=recipient.pubkey(),
        lamports=transfer_amount
    ))
    
    tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())
    tx.recent_blockhash = env.svm.get_latest_blockhash()
    tx.sign([payer], tx.recent_blockhash)
    
    result = env.execute_and_assert_success(tx)
    
    # Verify expected balance changes
    env.assert_balance_change(recipient.pubkey(), transfer_amount)
    env.assert_balance_change(payer.pubkey(), -(transfer_amount + result.fee))
    
    print("Test completed successfully!")

Performance and Limitations

Performance Characteristics

def measure_litesvm_performance():
    """Measure LiteSVM performance characteristics."""
    svm = LiteSVM()
    
    # Measure account creation throughput
    start = time.time()
    for i in range(1000):
        keypair = Keypair()
        svm.airdrop(keypair.pubkey(), 1000000)
    account_creation_time = time.time() - start
    
    print(f"Account creation: {1000/account_creation_time:.0f} accounts/second")
    
    # Measure transaction throughput
    payers = [Keypair() for _ in range(100)]
    recipients = [Keypair() for _ in range(100)]
    
    for payer in payers:
        svm.airdrop(payer.pubkey(), 100_000_000)
    
    transactions = []
    for payer, recipient in zip(payers, recipients):
        transfer_ix = transfer(TransferParams(
            from_pubkey=payer.pubkey(),
            to_pubkey=recipient.pubkey(),
            lamports=1_000_000
        ))
        tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())
        tx.recent_blockhash = svm.get_latest_blockhash()
        tx.sign([payer], tx.recent_blockhash)
        transactions.append(tx)
    
    start = time.time()
    for tx in transactions:
        svm.send_transaction(tx)
    transaction_time = time.time() - start
    
    print(f"Transaction throughput: {len(transactions)/transaction_time:.0f} TPS")

Limitations and Considerations

# LiteSVM limitations to be aware of:

# 1. No network communication - purely local execution
# 2. Single-threaded execution model
# 3. Simplified validator behavior
# 4. No stake/vote program functionality
# 5. Limited to loaded programs only
# 6. Deterministic but not identical to mainnet timing

# Best practices:
def litesvm_best_practices():
    """Best practices for using LiteSVM effectively."""
    
    # 1. Use for unit tests and integration tests
    # 2. Test edge cases and error conditions
    # 3. Benchmark performance locally
    # 4. Validate with devnet/testnet before mainnet
    # 5. Keep test environments isolated
    # 6. Use proper assertion helpers
    # 7. Clean up resources between tests
    
    pass

Install with Tessl CLI

npx tessl i tessl/pypi-solders

docs

account-management.md

cryptographic-primitives.md

error-handling.md

index.md

network-sysvars.md

rpc-functionality.md

system-programs.md

testing-infrastructure.md

token-operations.md

transaction-construction.md

transaction-status.md

tile.json