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
```