LiteSVM
solders.litesvm
is the Python wrapper for LiteSVM. It brings best-in-class Solana testing
to Python, giving you a powerful, fast and ergonomic way to test Solana programs.
For a standard testing workflow, LiteSVM offers an experience superior to solana-test-validator
(slow, unwieldy)
and the old solders.bankrun
module (reasonably fast and powerful, but inherits a lot of warts from solana-program-test
).
Minimal example
This example just transfers lamports from Alice to Bob without loading any programs of our own.
from solders.keypair import Keypair
from solders.litesvm import LiteSVM
from solders.message import Message
from solders.pubkey import Pubkey
from solders.system_program import transfer
from solders.transaction import VersionedTransaction
def test_transfer() -> None:
receiver = Pubkey.new_unique()
client = LiteSVM()
payer = Keypair()
client.airdrop(payer.pubkey(), 1_000_000_000)
blockhash = client.latest_blockhash()
transfer_lamports = 1_000_000
ixs = [
transfer(
{
"from_pubkey": payer.pubkey(),
"to_pubkey": receiver,
"lamports": transfer_lamports,
}
)
]
msg = Message.new_with_blockhash(ixs, payer.pubkey(), blockhash)
tx = VersionedTransaction(msg, [payer])
client.send_transaction(tx)
balance_after = client.get_balance(receiver)
assert balance_after == transfer_lamports
Note: by default the LiteSVM
instance includes some core programs such as
the System Program and SPL Token.
Deploying programs
Most of the time we want to do more than just mess around with token transfers - we want to test our own programs.
Tip
If you want to pull a Solana program from mainnet or devnet, use the solana program dump
command from the Solana CLI.
To add a compiled program to our tests we can use the add_program_from_file
method.
Here’s an example using a [simple program](https://github.com/solana-labs/solana-program-library/tree/bd216c8103cd8eb9f5f32e742973e7afb52f3b81/examples/rust/logging) from the Solana Program Library that just does some logging:
from pathlib import Path
from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair
from solders.litesvm import LiteSVM
from solders.message import Message
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
from solders.transaction_metadata import TransactionMetadata
def test_logging() -> None:
program_id = Pubkey.from_string("Logging111111111111111111111111111111111111")
ix = Instruction(
program_id,
bytes([5, 10, 11, 12, 13, 14]),
[AccountMeta(Pubkey.new_unique(), is_signer=False, is_writable=True)],
)
client = LiteSVM()
payer = Keypair()
client.add_program_from_file(
program_id, Path("tests/fixtures/spl_example_logging.so")
)
client.airdrop(payer.pubkey(), 1_000_000_000)
blockhash = client.latest_blockhash()
msg = Message.new_with_blockhash([ix], payer.pubkey(), blockhash)
tx = VersionedTransaction(msg, [payer])
# let's sim it first
sim_res = client.simulate_transaction(tx)
meta = client.send_transaction(tx)
assert isinstance(meta, TransactionMetadata)
assert sim_res.meta() == meta
assert meta.logs()[1] == "Program log: static string"
assert (
meta.compute_units_consumed() < 10_000
) # not being precise here in case it changes
Time travel
Many programs rely on the Clock
sysvar: for example, a mint that doesn’t become available until after
a certain time. With litesvm
you can dynamically overwrite the Clock
sysvar using svm.set_clock()
.
Here’s an example using a program that panics if clock.unix_timestamp
is greater than 100
(which is on January 1st 1970):
from pathlib import Path
from solders.instruction import Instruction
from solders.keypair import Keypair
from solders.litesvm import LiteSVM
from solders.message import Message
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
from solders.transaction_metadata import FailedTransactionMetadata, TransactionMetadata
def test_set_clock() -> None:
program_id = Pubkey.new_unique()
client = LiteSVM()
client.add_program_from_file(
program_id, Path("tests/fixtures/solders_clock_example.so")
)
payer = Keypair()
client.airdrop(payer.pubkey(), 1_000_000_000)
blockhash = client.latest_blockhash()
ixs = [Instruction(program_id=program_id, data=b"", accounts=[])]
msg = Message.new_with_blockhash(ixs, payer.pubkey(), blockhash)
tx = VersionedTransaction(msg, [payer])
# set the time to January 1st 2000
initial_clock = client.get_clock()
initial_clock.unix_timestamp = 1735689600
client.set_clock(initial_clock)
# this will fail because it's not January 1970 anymore
bad_res = client.send_transaction(tx)
assert isinstance(bad_res, FailedTransactionMetadata)
# so let's turn back time
clock = client.get_clock()
clock.unix_timestamp = 50
client.set_clock(clock)
ixs2 = [
Instruction(
program_id=program_id,
data=b"foobar", # unused, this is just to dedup the transaction
accounts=[],
)
]
msg2 = Message.new_with_blockhash(ixs2, payer.pubkey(), blockhash)
tx2 = VersionedTransaction(msg2, [payer])
# now the transaction goes through
good_res = client.send_transaction(tx2)
assert isinstance(good_res, TransactionMetadata)
See also: svm.warp_to_slot()
, which lets you jump to a future slot.
Writing arbitrary accounts
LiteSVM lets you write any account data you want, regardless of whether the account state would even be possible.
Here’s an example where we give an account a bunch of USDC, even though we don’t have the USDC mint keypair. This is convenient for testing because it means we don’t have to work with fake USDC in our tests:
from solders.account import Account
from solders.litesvm import LiteSVM
from solders.pubkey import Pubkey
from solders.token import ID as TOKEN_PROGRAM_ID
from solders.token.associated import get_associated_token_address
from solders.token.state import TokenAccount, TokenAccountState
def test_infinite_usdc_mint() -> None:
owner = Pubkey.new_unique()
usdc_mint = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
ata = get_associated_token_address(owner, usdc_mint)
usdc_to_own = 1_000_000_000_000
token_acc = TokenAccount(
mint=usdc_mint,
owner=owner,
amount=usdc_to_own,
delegate=None,
state=TokenAccountState.Initialized,
is_native=None,
delegated_amount=0,
close_authority=None,
)
client = LiteSVM()
client.set_account(
ata,
Account(
lamports=1_000_000_000,
data=bytes(token_acc),
owner=TOKEN_PROGRAM_ID,
executable=False,
),
)
raw_account = client.get_account(ata)
assert raw_account is not None
raw_account_data = raw_account.data
assert TokenAccount.from_bytes(raw_account_data).amount == usdc_to_own
Copying Accounts from a live environment
If you want to copy accounts from mainnet or devnet, you can use the solana account
command in the Solana CLI to save account data to a file.
Other features
Other things you can do with litesvm
include:
Changing the max compute units and other compute budget behaviour using the
with_compute_budget
method.Disable transaction signature checking using
svm.with_sigverify(false)
.Find previous transactions using the
get_transaction
method.
When should I use solana-test-validator
?
While litesvm
is faster and more convenient, it is also less like a real RPC node.
So solana-test-validator
is still useful when you need to call RPC methods that LiteSVM
doesn’t support, or when you want to test something that depends on real-life validator behaviour
rather than just testing your program and client code.
In general though it is recommended to use litesvm
wherever possible, as it will make your life
much easier.
Supported platforms
litesvm
is supported on Linux x64 and MacOS targets. If you find a platform that is not supported
but which can run the litesvm
Rust crate, please open an issue.