or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

account-management.mdcryptographic-primitives.mderror-handling.mdindex.mdnetwork-sysvars.mdrpc-functionality.mdsystem-programs.mdtesting-infrastructure.mdtoken-operations.mdtransaction-construction.mdtransaction-status.md

testing-infrastructure.mddocs/

0

# Testing Infrastructure

1

2

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.

3

4

## Capabilities

5

6

### LiteSVM Runtime

7

8

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

9

10

```python { .api }

11

class LiteSVM:

12

"""

13

Lightweight Solana runtime for testing and transaction simulation.

14

15

Provides a complete local Solana environment without requiring network connectivity

16

or external validator nodes. Supports all core Solana features including:

17

- Transaction execution and simulation

18

- Account state management

19

- Built-in program execution (System, Token, etc.)

20

- Custom program deployment and execution

21

- Deterministic block production

22

"""

23

def __init__(self):

24

"""

25

Create new LiteSVM instance with default configuration.

26

27

Initializes a fresh Solana runtime with:

28

- Genesis accounts and configuration

29

- Built-in programs (System, Token, etc.)

30

- Default rent and fee configurations

31

- Empty validator set (single node)

32

"""

33

34

@classmethod

35

def new_with_config(cls, config: 'LiteSvmConfig') -> 'LiteSVM':

36

"""

37

Create LiteSVM with custom configuration.

38

39

Parameters:

40

- config: LiteSvmConfig, runtime configuration options

41

42

Returns:

43

LiteSVM instance with specified configuration

44

"""

45

46

def send_transaction(self, transaction: Union[Transaction, VersionedTransaction]) -> 'TransactionResult':

47

"""

48

Execute transaction and return detailed results.

49

50

Parameters:

51

- transaction: Union transaction to execute (must be signed)

52

53

Returns:

54

TransactionResult with execution metadata or error details

55

56

Raises:

57

- ValueError: if transaction is not properly signed

58

- RuntimeError: if transaction execution fails critically

59

"""

60

61

def simulate_transaction(

62

self,

63

transaction: Union[Transaction, VersionedTransaction],

64

config: Optional['SimulationConfig'] = None

65

) -> 'SimulateResult':

66

"""

67

Simulate transaction execution without committing state changes.

68

69

Parameters:

70

- transaction: Union transaction to simulate

71

- config: Optional[SimulationConfig], simulation parameters

72

73

Returns:

74

SimulateResult with execution results and logs

75

"""

76

77

def get_account(self, pubkey: Pubkey) -> Optional[Account]:

78

"""

79

Retrieve account data and metadata.

80

81

Parameters:

82

- pubkey: Pubkey, account address to query

83

84

Returns:

85

Optional[Account], account data or None if account doesn't exist

86

"""

87

88

def get_balance(self, pubkey: Pubkey) -> int:

89

"""

90

Get account balance in lamports.

91

92

Parameters:

93

- pubkey: Pubkey, account address

94

95

Returns:

96

int, account balance in lamports (0 if account doesn't exist)

97

"""

98

99

def airdrop(self, pubkey: Pubkey, lamports: int) -> None:

100

"""

101

Add lamports to account (test utility).

102

103

Parameters:

104

- pubkey: Pubkey, recipient account

105

- lamports: int, amount to add

106

107

Note:

108

Creates account if it doesn't exist

109

"""

110

111

def create_account(

112

self,

113

pubkey: Pubkey,

114

lamports: int,

115

data: bytes,

116

owner: Pubkey

117

) -> None:

118

"""

119

Create account with specified data and owner.

120

121

Parameters:

122

- pubkey: Pubkey, new account address

123

- lamports: int, initial balance

124

- data: bytes, account data

125

- owner: Pubkey, program that owns the account

126

127

Raises:

128

- ValueError: if account already exists

129

"""

130

131

def set_account(

132

self,

133

pubkey: Pubkey,

134

account: Account

135

) -> None:

136

"""

137

Set complete account state (test utility).

138

139

Parameters:

140

- pubkey: Pubkey, account address

141

- account: Account, complete account data to set

142

"""

143

144

def get_clock(self) -> Clock:

145

"""

146

Get current runtime clock information.

147

148

Returns:

149

Clock with current slot, epoch, and timestamp

150

"""

151

152

def get_rent(self) -> Rent:

153

"""

154

Get current rent configuration.

155

156

Returns:

157

Rent configuration for exemption calculations

158

"""

159

160

def advance_slot(self) -> None:

161

"""

162

Advance to next slot manually.

163

164

Increments slot counter and updates clock sysvar.

165

Useful for testing time-dependent behavior.

166

"""

167

168

def advance_slots(self, count: int) -> None:

169

"""

170

Advance multiple slots.

171

172

Parameters:

173

- count: int, number of slots to advance

174

"""

175

176

def set_slot(self, slot: int) -> None:

177

"""

178

Set current slot number.

179

180

Parameters:

181

- slot: int, slot number to set

182

183

Note:

184

Updates all time-dependent sysvars

185

"""

186

187

def get_latest_blockhash(self) -> Hash:

188

"""

189

Get current blockhash for transactions.

190

191

Returns:

192

Hash representing current block

193

"""

194

195

def get_program_accounts(

196

self,

197

program_id: Pubkey,

198

filters: Optional[List['AccountFilter']] = None

199

) -> List[tuple[Pubkey, Account]]:

200

"""

201

Get all accounts owned by program.

202

203

Parameters:

204

- program_id: Pubkey, program to search for

205

- filters: Optional[List[AccountFilter]], account filtering criteria

206

207

Returns:

208

List[tuple[Pubkey, Account]], matching accounts with addresses

209

"""

210

211

def load_program(self, program_id: Pubkey, elf_bytes: bytes) -> None:

212

"""

213

Load custom program into runtime.

214

215

Parameters:

216

- program_id: Pubkey, address for program

217

- elf_bytes: bytes, compiled program binary (ELF format)

218

219

Raises:

220

- ValueError: if program binary is invalid

221

"""

222

223

def get_transaction_count(self) -> int:

224

"""

225

Get total number of transactions processed.

226

227

Returns:

228

int, transaction count since runtime creation

229

"""

230

231

def get_signature_status(self, signature: Signature) -> Optional['TransactionStatus']:

232

"""

233

Get status of previously executed transaction.

234

235

Parameters:

236

- signature: Signature, transaction signature to query

237

238

Returns:

239

Optional[TransactionStatus], transaction status or None if not found

240

"""

241

```

242

243

### Transaction Results and Metadata

244

245

Detailed execution results and metadata from transaction processing.

246

247

```python { .api }

248

class TransactionMetadata:

249

"""

250

Successful transaction execution metadata.

251

"""

252

def __init__(

253

self,

254

compute_units_consumed: int,

255

fee: int,

256

inner_instructions: List['InnerInstruction'],

257

log_messages: List[str],

258

return_data: Optional[bytes],

259

status: 'TransactionStatus'

260

):

261

"""

262

Create transaction metadata for successful execution.

263

264

Parameters:

265

- compute_units_consumed: int, compute units used

266

- fee: int, transaction fee paid

267

- inner_instructions: List[InnerInstruction], cross-program invocations

268

- log_messages: List[str], program log messages

269

- return_data: Optional[bytes], program return data

270

- status: TransactionStatus, final transaction status

271

"""

272

273

@property

274

def compute_units_consumed(self) -> int:

275

"""Compute units used in execution."""

276

277

@property

278

def fee(self) -> int:

279

"""Transaction fee paid in lamports."""

280

281

@property

282

def inner_instructions(self) -> List['InnerInstruction']:

283

"""Cross-program invocations executed."""

284

285

@property

286

def log_messages(self) -> List[str]:

287

"""Program log messages."""

288

289

@property

290

def return_data(self) -> Optional[bytes]:

291

"""Program return data."""

292

293

@property

294

def status(self) -> 'TransactionStatus':

295

"""Final transaction status."""

296

297

def get_logs_for_program(self, program_id: Pubkey) -> List[str]:

298

"""

299

Filter log messages for specific program.

300

301

Parameters:

302

- program_id: Pubkey, program to filter logs for

303

304

Returns:

305

List[str], log messages from specified program

306

"""

307

308

class FailedTransactionMetadata:

309

"""

310

Failed transaction execution metadata.

311

"""

312

def __init__(

313

self,

314

compute_units_consumed: int,

315

fee: int,

316

inner_instructions: List['InnerInstruction'],

317

log_messages: List[str],

318

error: 'TransactionErrorType'

319

):

320

"""

321

Create failed transaction metadata.

322

323

Parameters:

324

- compute_units_consumed: int, compute units used before failure

325

- fee: int, transaction fee paid

326

- inner_instructions: List[InnerInstruction], instructions executed before failure

327

- log_messages: List[str], program logs before failure

328

- error: TransactionErrorType, error that caused failure

329

"""

330

331

@property

332

def compute_units_consumed(self) -> int:

333

"""Compute units used before failure."""

334

335

@property

336

def fee(self) -> int:

337

"""Transaction fee paid."""

338

339

@property

340

def inner_instructions(self) -> List['InnerInstruction']:

341

"""Instructions executed before failure."""

342

343

@property

344

def log_messages(self) -> List[str]:

345

"""Program logs before failure."""

346

347

@property

348

def error(self) -> 'TransactionErrorType':

349

"""Error that caused transaction failure."""

350

351

class SimulatedTransactionInfo:

352

"""

353

Transaction simulation results.

354

"""

355

def __init__(

356

self,

357

compute_units_consumed: int,

358

inner_instructions: List['InnerInstruction'],

359

log_messages: List[str],

360

return_data: Optional[bytes],

361

accounts: Optional[List[Account]]

362

):

363

"""

364

Create simulation results.

365

366

Parameters:

367

- compute_units_consumed: int, compute units that would be used

368

- inner_instructions: List[InnerInstruction], CPIs that would execute

369

- log_messages: List[str], program logs from simulation

370

- return_data: Optional[bytes], program return data from simulation

371

- accounts: Optional[List[Account]], final account states (if requested)

372

"""

373

374

@property

375

def compute_units_consumed(self) -> int:

376

"""Compute units that would be consumed."""

377

378

@property

379

def inner_instructions(self) -> List['InnerInstruction']:

380

"""Cross-program invocations that would execute."""

381

382

@property

383

def log_messages(self) -> List[str]:

384

"""Program log messages from simulation."""

385

386

@property

387

def return_data(self) -> Optional[bytes]:

388

"""Program return data from simulation."""

389

390

@property

391

def accounts(self) -> Optional[List[Account]]:

392

"""Final account states after simulation."""

393

394

class InnerInstruction:

395

"""

396

Inner instruction from cross-program invocation.

397

"""

398

def __init__(self, instruction_index: int, instructions: List[Instruction]):

399

"""

400

Create inner instruction container.

401

402

Parameters:

403

- instruction_index: int, parent instruction index

404

- instructions: List[Instruction], inner instructions executed

405

"""

406

407

@property

408

def instruction_index(self) -> int:

409

"""Parent instruction index that generated these inner instructions."""

410

411

@property

412

def instructions(self) -> List[Instruction]:

413

"""Inner instructions that were executed."""

414

415

# Type aliases for transaction results

416

TransactionResult = Union[TransactionMetadata, FailedTransactionMetadata]

417

SimulateResult = Union[SimulatedTransactionInfo, FailedTransactionMetadata]

418

```

419

420

### Configuration and Filtering

421

422

Runtime configuration options and account filtering capabilities.

423

424

```python { .api }

425

class LiteSvmConfig:

426

"""

427

Configuration options for LiteSVM runtime.

428

"""

429

def __init__(

430

self,

431

rent: Optional[Rent] = None,

432

fee_rate_governor: Optional['FeeRateGovernor'] = None,

433

genesis_config: Optional['GenesisConfig'] = None

434

):

435

"""

436

Create LiteSVM configuration.

437

438

Parameters:

439

- rent: Optional[Rent], rent configuration

440

- fee_rate_governor: Optional[FeeRateGovernor], fee calculation parameters

441

- genesis_config: Optional[GenesisConfig], genesis block configuration

442

"""

443

444

@classmethod

445

def default() -> 'LiteSvmConfig':

446

"""Create default configuration suitable for testing."""

447

448

class SimulationConfig:

449

"""

450

Configuration for transaction simulation.

451

"""

452

def __init__(

453

self,

454

sig_verify: bool = True,

455

replace_recent_blockhash: bool = False,

456

commitment: CommitmentLevel = CommitmentLevel.Processed,

457

accounts_to_return: Optional[List[Pubkey]] = None

458

):

459

"""

460

Create simulation configuration.

461

462

Parameters:

463

- sig_verify: bool, whether to verify signatures during simulation

464

- replace_recent_blockhash: bool, use current blockhash instead of transaction's

465

- commitment: CommitmentLevel, commitment level for simulation context

466

- accounts_to_return: Optional[List[Pubkey]], accounts to include in response

467

"""

468

469

class AccountFilter:

470

"""

471

Filter criteria for account queries.

472

"""

473

def __init__(self, offset: int, bytes: bytes):

474

"""

475

Create account filter by data content.

476

477

Parameters:

478

- offset: int, byte offset in account data

479

- bytes: bytes, data that must match at offset

480

"""

481

482

@classmethod

483

def memcmp(cls, offset: int, bytes: bytes) -> 'AccountFilter':

484

"""

485

Create memcmp filter.

486

487

Parameters:

488

- offset: int, byte offset to compare

489

- bytes: bytes, expected data at offset

490

491

Returns:

492

AccountFilter for memory comparison

493

"""

494

495

@classmethod

496

def data_size(cls, size: int) -> 'AccountFilter':

497

"""

498

Create data size filter.

499

500

Parameters:

501

- size: int, required account data size

502

503

Returns:

504

AccountFilter for data size matching

505

"""

506

```

507

508

## Usage Examples

509

510

### Basic Testing Setup

511

512

```python

513

from solders.litesvm import LiteSVM

514

from solders.keypair import Keypair

515

from solders.system_program import transfer, TransferParams

516

517

# Create test environment

518

svm = LiteSVM()

519

520

# Create test accounts

521

payer = Keypair()

522

recipient = Keypair()

523

524

# Fund payer account

525

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

526

527

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

528

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

529

530

# Create transfer transaction

531

transfer_ix = transfer(TransferParams(

532

from_pubkey=payer.pubkey(),

533

to_pubkey=recipient.pubkey(),

534

lamports=1_000_000_000 # 1 SOL

535

))

536

537

# Build and sign transaction

538

from solders.transaction import Transaction

539

tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())

540

tx.recent_blockhash = svm.get_latest_blockhash()

541

tx.sign([payer], tx.recent_blockhash)

542

543

# Execute transaction

544

result = svm.send_transaction(tx)

545

546

if isinstance(result, TransactionMetadata):

547

print("Transaction successful!")

548

print(f"Fee paid: {result.fee} lamports")

549

print(f"Compute units used: {result.compute_units_consumed}")

550

551

# Check updated balances

552

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

553

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

554

else:

555

print(f"Transaction failed: {result.error}")

556

```

557

558

### Token Program Testing

559

560

```python

561

from solders.token.associated import get_associated_token_address

562

from solders.instruction import Instruction, AccountMeta

563

from solders.token import ID as TOKEN_PROGRAM_ID

564

565

# Create token mint and accounts for testing

566

def setup_token_test_environment(svm: LiteSVM):

567

"""Set up token mint and associated accounts for testing."""

568

569

# Create mint authority

570

mint_authority = Keypair()

571

svm.airdrop(mint_authority.pubkey(), 5_000_000_000) # 5 SOL for fees

572

573

# Create mint account

574

mint = Keypair()

575

576

# Create mint account with proper size and owner

577

mint_account = Account(

578

lamports=svm.get_rent().minimum_balance(82), # Mint account size

579

data=bytes(82), # Empty mint data (will be initialized)

580

owner=TOKEN_PROGRAM_ID,

581

executable=False,

582

rent_epoch=0

583

)

584

svm.set_account(mint.pubkey(), mint_account)

585

586

# Initialize mint (this would normally be done via instruction)

587

# For testing, we'll create a properly initialized mint

588

from solders.token.state import Mint

589

mint_data = Mint(

590

mint_authority=mint_authority.pubkey(),

591

supply=0,

592

decimals=9,

593

is_initialized=True,

594

freeze_authority=None

595

)

596

597

mint_account_initialized = Account(

598

lamports=mint_account.lamports,

599

data=mint_data.serialize(),

600

owner=TOKEN_PROGRAM_ID,

601

executable=False,

602

rent_epoch=0

603

)

604

svm.set_account(mint.pubkey(), mint_account_initialized)

605

606

return mint_authority, mint

607

608

def test_token_operations(svm: LiteSVM):

609

"""Test token minting and transfers."""

610

mint_authority, mint = setup_token_test_environment(svm)

611

612

# Create user accounts

613

user1 = Keypair()

614

user2 = Keypair()

615

svm.airdrop(user1.pubkey(), 2_000_000_000) # Fund for fees

616

svm.airdrop(user2.pubkey(), 2_000_000_000)

617

618

# Calculate associated token accounts

619

user1_ata = get_associated_token_address(user1.pubkey(), mint.pubkey())

620

user2_ata = get_associated_token_address(user2.pubkey(), mint.pubkey())

621

622

print(f"User1 ATA: {user1_ata}")

623

print(f"User2 ATA: {user2_ata}")

624

625

# Test ATA creation and token minting would go here

626

# This requires implementing the full SPL token instruction set

627

628

return mint_authority, mint, user1, user2

629

```

630

631

### Transaction Simulation

632

633

```python

634

def test_transaction_simulation(svm: LiteSVM):

635

"""Test transaction simulation before execution."""

636

637

# Setup accounts

638

payer = Keypair()

639

recipient = Keypair()

640

svm.airdrop(payer.pubkey(), 5_000_000_000)

641

642

# Create transaction

643

transfer_ix = transfer(TransferParams(

644

from_pubkey=payer.pubkey(),

645

to_pubkey=recipient.pubkey(),

646

lamports=2_000_000_000 # 2 SOL

647

))

648

649

tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())

650

tx.recent_blockhash = svm.get_latest_blockhash()

651

tx.sign([payer], tx.recent_blockhash)

652

653

# Simulate before executing

654

simulation_config = SimulationConfig(

655

sig_verify=True,

656

replace_recent_blockhash=False,

657

accounts_to_return=[payer.pubkey(), recipient.pubkey()]

658

)

659

660

sim_result = svm.simulate_transaction(tx, simulation_config)

661

662

if isinstance(sim_result, SimulatedTransactionInfo):

663

print("Simulation successful!")

664

print(f"Estimated compute units: {sim_result.compute_units_consumed}")

665

print(f"Log messages: {len(sim_result.log_messages)}")

666

667

# Show final account states

668

if sim_result.accounts:

669

for i, account in enumerate(sim_result.accounts):

670

print(f"Account {i} final balance: {account.lamports}")

671

672

# Now execute the actual transaction

673

result = svm.send_transaction(tx)

674

print(f"Actual execution result: {type(result).__name__}")

675

676

else:

677

print(f"Simulation failed: {sim_result.error}")

678

print("Transaction would fail, not executing")

679

```

680

681

### Program Testing

682

683

```python

684

def test_custom_program(svm: LiteSVM):

685

"""Test custom program deployment and execution."""

686

687

# Load custom program (would need actual program binary)

688

program_id = Keypair().pubkey() # Random program ID for example

689

690

# Note: In real usage, you'd load actual program bytecode:

691

# program_elf = open("my_program.so", "rb").read()

692

# svm.load_program(program_id, program_elf)

693

694

# For this example, we'll test with system program functionality

695

payer = Keypair()

696

svm.airdrop(payer.pubkey(), 10_000_000_000)

697

698

# Create account for program data

699

data_account = Keypair()

700

701

# Create account creation instruction

702

from solders.system_program import create_account, CreateAccountParams

703

create_ix = create_account(CreateAccountParams(

704

from_pubkey=payer.pubkey(),

705

to_pubkey=data_account.pubkey(),

706

lamports=svm.get_rent().minimum_balance(100),

707

space=100,

708

owner=program_id # Our "custom" program owns this account

709

))

710

711

# Execute account creation

712

tx = Transaction.new_with_payer([create_ix], payer.pubkey())

713

tx.recent_blockhash = svm.get_latest_blockhash()

714

tx.sign([payer, data_account], tx.recent_blockhash)

715

716

result = svm.send_transaction(tx)

717

718

if isinstance(result, TransactionMetadata):

719

print("Account created successfully!")

720

721

# Verify account was created

722

created_account = svm.get_account(data_account.pubkey())

723

if created_account:

724

print(f"Account owner: {created_account.owner}")

725

print(f"Account size: {len(created_account.data)} bytes")

726

727

# Test program account query

728

program_accounts = svm.get_program_accounts(program_id)

729

print(f"Found {len(program_accounts)} accounts owned by program")

730

731

else:

732

print(f"Account creation failed: {result.error}")

733

```

734

735

### Advanced Testing Scenarios

736

737

```python

738

def test_multi_instruction_transaction(svm: LiteSVM):

739

"""Test transaction with multiple instructions and cross-program invocations."""

740

741

# Setup

742

payer = Keypair()

743

recipient1 = Keypair()

744

recipient2 = Keypair()

745

svm.airdrop(payer.pubkey(), 10_000_000_000)

746

747

# Create multiple instructions

748

from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price

749

750

instructions = [

751

set_compute_unit_limit(300_000), # Set compute budget

752

set_compute_unit_price(1000), # Set priority fee

753

transfer(TransferParams( # First transfer

754

from_pubkey=payer.pubkey(),

755

to_pubkey=recipient1.pubkey(),

756

lamports=1_000_000_000

757

)),

758

transfer(TransferParams( # Second transfer

759

from_pubkey=payer.pubkey(),

760

to_pubkey=recipient2.pubkey(),

761

lamports=500_000_000

762

))

763

]

764

765

# Execute transaction

766

tx = Transaction.new_with_payer(instructions, payer.pubkey())

767

tx.recent_blockhash = svm.get_latest_blockhash()

768

tx.sign([payer], tx.recent_blockhash)

769

770

result = svm.send_transaction(tx)

771

772

if isinstance(result, TransactionMetadata):

773

print("Multi-instruction transaction successful!")

774

print(f"Total compute units: {result.compute_units_consumed}")

775

print(f"Transaction fee: {result.fee}")

776

print(f"Log messages: {len(result.log_messages)}")

777

778

# Verify balances

779

balances = {

780

"payer": svm.get_balance(payer.pubkey()),

781

"recipient1": svm.get_balance(recipient1.pubkey()),

782

"recipient2": svm.get_balance(recipient2.pubkey())

783

}

784

785

for name, balance in balances.items():

786

print(f"{name} balance: {balance:,} lamports")

787

else:

788

print(f"Transaction failed: {result.error}")

789

790

def test_time_dependent_behavior(svm: LiteSVM):

791

"""Test behavior that depends on slots/time."""

792

793

initial_clock = svm.get_clock()

794

print(f"Initial slot: {initial_clock.slot}")

795

print(f"Initial epoch: {initial_clock.epoch}")

796

797

# Advance time

798

svm.advance_slots(100)

799

800

updated_clock = svm.get_clock()

801

print(f"After advancing slot: {updated_clock.slot}")

802

print(f"Slot difference: {updated_clock.slot - initial_clock.slot}")

803

804

# Test blockhash changes

805

initial_blockhash = svm.get_latest_blockhash()

806

svm.advance_slots(1)

807

new_blockhash = svm.get_latest_blockhash()

808

809

print(f"Blockhash changed: {initial_blockhash != new_blockhash}")

810

811

def benchmark_transaction_performance(svm: LiteSVM):

812

"""Benchmark transaction execution performance."""

813

import time

814

815

# Setup

816

payers = [Keypair() for _ in range(100)]

817

recipients = [Keypair() for _ in range(100)]

818

819

for payer in payers:

820

svm.airdrop(payer.pubkey(), 2_000_000_000)

821

822

# Prepare transactions

823

transactions = []

824

for payer, recipient in zip(payers, recipients):

825

transfer_ix = transfer(TransferParams(

826

from_pubkey=payer.pubkey(),

827

to_pubkey=recipient.pubkey(),

828

lamports=1_000_000

829

))

830

831

tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())

832

tx.recent_blockhash = svm.get_latest_blockhash()

833

tx.sign([payer], tx.recent_blockhash)

834

transactions.append(tx)

835

836

# Execute and time

837

start_time = time.time()

838

results = []

839

840

for tx in transactions:

841

result = svm.send_transaction(tx)

842

results.append(result)

843

844

end_time = time.time()

845

846

# Analyze results

847

successful = sum(1 for r in results if isinstance(r, TransactionMetadata))

848

total_time = end_time - start_time

849

850

print(f"Executed {len(transactions)} transactions in {total_time:.3f}s")

851

print(f"Success rate: {successful}/{len(transactions)} ({successful/len(transactions)*100:.1f}%)")

852

print(f"Throughput: {len(transactions)/total_time:.1f} TPS")

853

854

if successful > 0:

855

successful_results = [r for r in results if isinstance(r, TransactionMetadata)]

856

avg_compute = sum(r.compute_units_consumed for r in successful_results) / len(successful_results)

857

avg_fee = sum(r.fee for r in successful_results) / len(successful_results)

858

859

print(f"Average compute units: {avg_compute:.0f}")

860

print(f"Average fee: {avg_fee:.0f} lamports")

861

```

862

863

### Testing Utilities

864

865

```python

866

class TestEnvironment:

867

"""Helper class for managing test environments."""

868

869

def __init__(self):

870

self.svm = LiteSVM()

871

self.funded_accounts = {}

872

873

def create_funded_keypair(self, lamports: int = 10_000_000_000) -> Keypair:

874

"""Create and fund a new keypair."""

875

keypair = Keypair()

876

self.svm.airdrop(keypair.pubkey(), lamports)

877

self.funded_accounts[keypair.pubkey()] = keypair

878

return keypair

879

880

def execute_and_assert_success(self, transaction: Transaction) -> TransactionMetadata:

881

"""Execute transaction and assert it succeeds."""

882

result = self.svm.send_transaction(transaction)

883

assert isinstance(result, TransactionMetadata), f"Transaction failed: {result.error if hasattr(result, 'error') else 'Unknown error'}"

884

return result

885

886

def simulate_and_assert_success(self, transaction: Transaction) -> SimulatedTransactionInfo:

887

"""Simulate transaction and assert it succeeds."""

888

result = self.svm.simulate_transaction(transaction)

889

assert isinstance(result, SimulatedTransactionInfo), f"Simulation failed: {result.error if hasattr(result, 'error') else 'Unknown error'}"

890

return result

891

892

def assert_balance_change(self, pubkey: Pubkey, expected_change: int, tolerance: int = 0):

893

"""Assert account balance changed by expected amount."""

894

current_balance = self.svm.get_balance(pubkey)

895

if pubkey not in self.last_balances:

896

self.last_balances[pubkey] = current_balance

897

return

898

899

actual_change = current_balance - self.last_balances[pubkey]

900

assert abs(actual_change - expected_change) <= tolerance, \

901

f"Expected balance change {expected_change}, got {actual_change}"

902

903

self.last_balances[pubkey] = current_balance

904

905

def snapshot_balances(self):

906

"""Take snapshot of all tracked account balances."""

907

self.last_balances = {}

908

for pubkey in self.funded_accounts.keys():

909

self.last_balances[pubkey] = self.svm.get_balance(pubkey)

910

911

# Usage example

912

def test_with_environment():

913

"""Example using the test environment helper."""

914

env = TestEnvironment()

915

916

# Create test accounts

917

payer = env.create_funded_keypair(10_000_000_000) # 10 SOL

918

recipient = env.create_funded_keypair(0) # Empty account

919

920

# Snapshot initial state

921

env.snapshot_balances()

922

923

# Create and execute transaction

924

transfer_amount = 1_000_000_000 # 1 SOL

925

transfer_ix = transfer(TransferParams(

926

from_pubkey=payer.pubkey(),

927

to_pubkey=recipient.pubkey(),

928

lamports=transfer_amount

929

))

930

931

tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())

932

tx.recent_blockhash = env.svm.get_latest_blockhash()

933

tx.sign([payer], tx.recent_blockhash)

934

935

result = env.execute_and_assert_success(tx)

936

937

# Verify expected balance changes

938

env.assert_balance_change(recipient.pubkey(), transfer_amount)

939

env.assert_balance_change(payer.pubkey(), -(transfer_amount + result.fee))

940

941

print("Test completed successfully!")

942

```

943

944

## Performance and Limitations

945

946

### Performance Characteristics

947

948

```python

949

def measure_litesvm_performance():

950

"""Measure LiteSVM performance characteristics."""

951

svm = LiteSVM()

952

953

# Measure account creation throughput

954

start = time.time()

955

for i in range(1000):

956

keypair = Keypair()

957

svm.airdrop(keypair.pubkey(), 1000000)

958

account_creation_time = time.time() - start

959

960

print(f"Account creation: {1000/account_creation_time:.0f} accounts/second")

961

962

# Measure transaction throughput

963

payers = [Keypair() for _ in range(100)]

964

recipients = [Keypair() for _ in range(100)]

965

966

for payer in payers:

967

svm.airdrop(payer.pubkey(), 100_000_000)

968

969

transactions = []

970

for payer, recipient in zip(payers, recipients):

971

transfer_ix = transfer(TransferParams(

972

from_pubkey=payer.pubkey(),

973

to_pubkey=recipient.pubkey(),

974

lamports=1_000_000

975

))

976

tx = Transaction.new_with_payer([transfer_ix], payer.pubkey())

977

tx.recent_blockhash = svm.get_latest_blockhash()

978

tx.sign([payer], tx.recent_blockhash)

979

transactions.append(tx)

980

981

start = time.time()

982

for tx in transactions:

983

svm.send_transaction(tx)

984

transaction_time = time.time() - start

985

986

print(f"Transaction throughput: {len(transactions)/transaction_time:.0f} TPS")

987

```

988

989

### Limitations and Considerations

990

991

```python

992

# LiteSVM limitations to be aware of:

993

994

# 1. No network communication - purely local execution

995

# 2. Single-threaded execution model

996

# 3. Simplified validator behavior

997

# 4. No stake/vote program functionality

998

# 5. Limited to loaded programs only

999

# 6. Deterministic but not identical to mainnet timing

1000

1001

# Best practices:

1002

def litesvm_best_practices():

1003

"""Best practices for using LiteSVM effectively."""

1004

1005

# 1. Use for unit tests and integration tests

1006

# 2. Test edge cases and error conditions

1007

# 3. Benchmark performance locally

1008

# 4. Validate with devnet/testnet before mainnet

1009

# 5. Keep test environments isolated

1010

# 6. Use proper assertion helpers

1011

# 7. Clean up resources between tests

1012

1013

pass

1014

```