# Tutorial
# 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 addProgramFromFile
method.
Here's an example using a simple program (opens new window) from the Solana Program Library that just does some logging:
import { LiteSVM, TransactionMetadata } from "litesvm";
import {
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
Transaction,
TransactionInstruction,
} from "@solana/web3.js";
test("spl logging", () => {
const programId = PublicKey.unique();
const svm = new LiteSVM();
svm.addProgramFromFile(programId, "program_bytes/spl_example_logging.so");
const payer = new Keypair();
svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL));
const blockhash = svm.latestBlockhash();
const ixs = [
new TransactionInstruction({
programId,
keys: [
{ pubkey: PublicKey.unique(), isSigner: false, isWritable: false },
],
}),
];
const tx = new Transaction();
tx.recentBlockhash = blockhash;
tx.add(...ixs);
tx.sign(payer);
// let's sim it first
const simRes = svm.simulateTransaction(tx);
const sendRes = svm.sendTransaction(tx);
if (sendRes instanceof TransactionMetadata) {
expect(simRes.meta().logs()).toEqual(sendRes.logs());
expect(sendRes.logs()[1]).toBe("Program log: static string");
} else {
throw new Error("Unexpected tx failure");
}
});
# 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.setClock()
.
Here's an example using a program that panics if clock.unix_timestamp
is greater than 100
(which is on January 1st 1970):
import {
Clock,
FailedTransactionMetadata,
LiteSVM,
TransactionMetadata,
} from "litesvm";
import {
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
Transaction,
TransactionInstruction,
} from "@solana/web3.js";
test("clock", () => {
const programId = PublicKey.unique();
const svm = new LiteSVM();
svm.addProgramFromFile(programId, "program_bytes/litesvm_clock_example.so");
const payer = new Keypair();
svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL));
const blockhash = svm.latestBlockhash();
const ixs = [
new TransactionInstruction({ keys: [], programId, data: Buffer.from("") }),
];
const tx = new Transaction();
tx.recentBlockhash = blockhash;
tx.add(...ixs);
tx.sign(payer);
// set the time to January 1st 2000
const initialClock = svm.getClock();
initialClock.unixTimestamp = 1735689600n;
svm.setClock(initialClock);
// this will fail because the contract wants it to be January 1970
const failed = svm.sendTransaction(tx);
if (failed instanceof FailedTransactionMetadata) {
expect(failed.err().toString()).toContain("ProgramFailedToComplete");
} else {
throw new Error("Expected transaction failure here");
}
// so let's turn back time
const newClock = svm.getClock();
newClock.unixTimestamp = 50n;
svm.setClock(newClock);
const ixs2 = [
new TransactionInstruction({
keys: [],
programId,
data: Buffer.from("foobar"), // unused, just here to dedup the tx
}),
];
const tx2 = new Transaction();
tx2.recentBlockhash = blockhash;
tx2.add(...ixs2);
tx2.sign(payer);
// now the transaction goes through
const success = svm.sendTransaction(tx2);
expect(success).toBeInstanceOf(TransactionMetadata);
});
See also: svm.warpToSlot()
, 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:
import { LiteSVM } from "litesvm";
import { PublicKey } from "@solana/web3.js";
import {
getAssociatedTokenAddressSync,
AccountLayout,
ACCOUNT_SIZE,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
test("infinite usdc mint", () => {
const owner = PublicKey.unique();
const usdcMint = new PublicKey(
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
);
const ata = getAssociatedTokenAddressSync(usdcMint, owner, true);
const usdcToOwn = 1_000_000_000_000n;
const tokenAccData = Buffer.alloc(ACCOUNT_SIZE);
AccountLayout.encode(
{
mint: usdcMint,
owner,
amount: usdcToOwn,
delegateOption: 0,
delegate: PublicKey.default,
delegatedAmount: 0n,
state: 1,
isNativeOption: 0,
isNative: 0n,
closeAuthorityOption: 0,
closeAuthority: PublicKey.default,
},
tokenAccData,
);
const svm = new LiteSVM();
svm.setAccount(ata, {
lamports: 1_000_000_000,
data: tokenAccData,
owner: TOKEN_PROGRAM_ID,
executable: false,
});
const rawAccount = svm.getAccount(ata);
expect(rawAccount).not.toBeNull();
const rawAccountData = rawAccount?.data;
const decoded = AccountLayout.decode(rawAccountData);
expect(decoded.amount).toBe(usdcToOwn);
});
# 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.
Or, if you want to pull live data every time you test, you can do this with a few lines of code. Here's a simple example that pulls account data from devnet and passes it to LiteSVM:
import { LiteSVM } from "litesvm";
import { PublicKey, Connection } from "@solana/web3.js";
test("copy accounts from devnet", async () => {
const owner = PublicKey.unique();
const usdcMint = new PublicKey(
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
);
const connection = new Connection("https://api.devnet.solana.com");
const accountInfo = await connection.getAccountInfo(usdcMint);
// the rent epoch goes above 2**53 which breaks web3.js, so just set it to 0;
accountInfo.rentEpoch = 0;
const svm = new LiteSVM();
svm.setAccount(usdcMint, accountInfo);
const rawAccount = svm.getAccount(usdcMint);
expect(rawAccount).not.toBeNull();
});
# Other features
Other things you can do with litesvm
include:
- Changing the max compute units and other compute budget behaviour using the
withComputeBudget
method. - Disable transaction signature checking using
svm.withSigverify(false)
. - Find previous transactions using the
getTransaction
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 I would recommend using 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.