|
@@ -0,0 +1,623 @@
|
|
|
+---
|
|
|
+title: LiteSVM
|
|
|
+description: Write tests for Solana programs in Rust, TS/JS or Python using LiteSVM.
|
|
|
+---
|
|
|
+
|
|
|
+## Overview
|
|
|
+
|
|
|
+[`litesvm`](https://github.com/LiteSVM/litesvm) is a fast and lightweight library for testing Solana programs.
|
|
|
+It works by creating an in-process Solana VM optimized for program developers.
|
|
|
+This makes it much faster to run and compile than alternatives like `solana-program-test` and `solana-test-validator`.
|
|
|
+`litesvm` is available in Rust, TS/JS and Python (as part of the [`solders`](https://pypi.org/project/solders/) library).
|
|
|
+
|
|
|
+### Installation
|
|
|
+
|
|
|
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
|
|
+
|
|
|
+<Tabs items={["Rust", "TS/JS", "Python"]}>
|
|
|
+```sh tab="Rust"
|
|
|
+cargo add litesvm --dev
|
|
|
+```
|
|
|
+
|
|
|
+```sh tab="TS/JS"
|
|
|
+npm i litesvm -D
|
|
|
+```
|
|
|
+
|
|
|
+```sh tab="Python"
|
|
|
+uv add solders # from solders import litesvm
|
|
|
+```
|
|
|
+
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+### Minimal Example
|
|
|
+
|
|
|
+<Tabs items={["Rust", "TypeScript", "Python"]}>
|
|
|
+
|
|
|
+```rust tab="Rust"
|
|
|
+use litesvm::LiteSVM;
|
|
|
+use solana_message::Message;
|
|
|
+use solana_pubkey::Pubkey;
|
|
|
+use solana_system_interface::instruction::transfer;
|
|
|
+use solana_keypair::Keypair;
|
|
|
+use solana_signer::Signer;
|
|
|
+use solana_transaction::Transaction;
|
|
|
+
|
|
|
+let from_keypair = Keypair::new();
|
|
|
+let from = from_keypair.pubkey();
|
|
|
+let to = Pubkey::new_unique();
|
|
|
+
|
|
|
+let mut svm = LiteSVM::new();
|
|
|
+svm.airdrop(&from, 10_000).unwrap();
|
|
|
+
|
|
|
+let instruction = transfer(&from, &to, 64);
|
|
|
+let tx = Transaction::new(
|
|
|
+ &[&from_keypair],
|
|
|
+ Message::new(&[instruction], Some(&from)),
|
|
|
+ svm.latest_blockhash(),
|
|
|
+);
|
|
|
+let tx_res = svm.send_transaction(tx).unwrap();
|
|
|
+
|
|
|
+let from_account = svm.get_account(&from);
|
|
|
+let to_account = svm.get_account(&to);
|
|
|
+assert_eq!(from_account.unwrap().lamports, 4936);
|
|
|
+assert_eq!(to_account.unwrap().lamports, 64);
|
|
|
+```
|
|
|
+
|
|
|
+```ts tab="TypeScript"
|
|
|
+import { LiteSVM } from "litesvm";
|
|
|
+import {
|
|
|
+ PublicKey,
|
|
|
+ Transaction,
|
|
|
+ SystemProgram,
|
|
|
+ Keypair,
|
|
|
+ LAMPORTS_PER_SOL,
|
|
|
+} from "@solana/web3.js";
|
|
|
+
|
|
|
+test("one transfer", () => {
|
|
|
+ const svm = new LiteSVM();
|
|
|
+ const payer = new Keypair();
|
|
|
+ svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL));
|
|
|
+ const receiver = PublicKey.unique();
|
|
|
+ const blockhash = svm.latestBlockhash();
|
|
|
+ const transferLamports = 1_000_000n;
|
|
|
+ const ixs = [
|
|
|
+ SystemProgram.transfer({
|
|
|
+ fromPubkey: payer.publicKey,
|
|
|
+ toPubkey: receiver,
|
|
|
+ lamports: transferLamports,
|
|
|
+ }),
|
|
|
+ ];
|
|
|
+ const tx = new Transaction();
|
|
|
+ tx.recentBlockhash = blockhash;
|
|
|
+ tx.add(...ixs);
|
|
|
+ tx.sign(payer);
|
|
|
+ svm.sendTransaction(tx);
|
|
|
+ const balanceAfter = svm.getBalance(receiver);
|
|
|
+ expect(balanceAfter).toBe(transferLamports);
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+```python tab="Python"
|
|
|
+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
|
|
|
+```
|
|
|
+
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+## 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 [`.add_program_from_file`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.add_program_from_file).
|
|
|
+
|
|
|
+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:
|
|
|
+
|
|
|
+<Tabs items={["Rust", "TypeScript", "Python"]}>
|
|
|
+
|
|
|
+```rust tab="Rust"
|
|
|
+use {
|
|
|
+ litesvm::LiteSVM,
|
|
|
+ solana_instruction::{account_meta::AccountMeta, Instruction},
|
|
|
+ solana_keypair::Keypair,
|
|
|
+ solana_pubkey::{pubkey, Pubkey},
|
|
|
+ solana_message::{Message, VersionedMessage},
|
|
|
+ solana_signer::Signer,
|
|
|
+ solana_transaction::VersionedTransaction,
|
|
|
+};
|
|
|
+
|
|
|
+fn test_logging() {
|
|
|
+ let program_id = pubkey!("Logging111111111111111111111111111111111111");
|
|
|
+ let account_meta = AccountMeta {
|
|
|
+ pubkey: Pubkey::new_unique(),
|
|
|
+ is_signer: false,
|
|
|
+ is_writable: true,
|
|
|
+ };
|
|
|
+ let ix = Instruction {
|
|
|
+ program_id,
|
|
|
+ accounts: vec![account_meta],
|
|
|
+ data: vec![5, 10, 11, 12, 13, 14],
|
|
|
+ };
|
|
|
+ let mut svm = LiteSVM::new();
|
|
|
+ let payer = Keypair::new();
|
|
|
+ let bytes = include_bytes!("../../node-litesvm/program_bytes/spl_example_logging.so");
|
|
|
+ svm.add_program(program_id, bytes);
|
|
|
+ svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
|
|
|
+ let blockhash = svm.latest_blockhash();
|
|
|
+ let msg = Message::new_with_blockhash(&[ix], Some(&payer.pubkey()), &blockhash);
|
|
|
+ let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[payer]).unwrap();
|
|
|
+ // let's sim it first
|
|
|
+ let sim_res = svm.simulate_transaction(tx.clone()).unwrap();
|
|
|
+ let meta = svm.send_transaction(tx).unwrap();
|
|
|
+ assert_eq!(sim_res.meta, meta);
|
|
|
+ assert_eq!(meta.logs[1], "Program log: static string");
|
|
|
+ assert!(meta.compute_units_consumed < 10_000) // not being precise here in case it changes
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts tab="TypeScript"
|
|
|
+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");
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+```python tab="Python"
|
|
|
+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
|
|
|
+```
|
|
|
+
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+## 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_sysvar::<Clock>()`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.set_sysvar)
|
|
|
+(or `.setClock` in TS, or `.set_clock` in Python).
|
|
|
+Here's an example using a program that panics if `clock.unix_timestamp` is greater than 100
|
|
|
+(which is on January 1st 1970):
|
|
|
+
|
|
|
+<Tabs items={["Rust", "TypeScript", "Python"]}>
|
|
|
+```rust tab="Rust"
|
|
|
+use {
|
|
|
+ litesvm::LiteSVM,
|
|
|
+ solana_clock::Clock,
|
|
|
+ solana_instruction::Instruction,
|
|
|
+ use solana_keypair::Keypair,
|
|
|
+ solana_message::{Message, VersionedMessage},
|
|
|
+ solana_pubkey::Pubkey,
|
|
|
+ solana_signer::Signer,
|
|
|
+ solana_transaction::VersionedTransaction,
|
|
|
+};
|
|
|
+
|
|
|
+fn test_set_clock() {
|
|
|
+ let program_id = Pubkey::new_unique();
|
|
|
+ let mut svm = LiteSVM::new();
|
|
|
+ let bytes = include_bytes!("../../node-litesvm/program_bytes/litesvm_clock_example.so");
|
|
|
+ svm.add_program(program_id, bytes);
|
|
|
+ let payer = Keypair::new();
|
|
|
+ let payer_address = payer.pubkey();
|
|
|
+ svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
|
|
|
+ let blockhash = svm.latest_blockhash();
|
|
|
+ let ixs = [Instruction {
|
|
|
+ program_id,
|
|
|
+ data: vec![],
|
|
|
+ accounts: vec![],
|
|
|
+ }];
|
|
|
+ let msg = Message::new_with_blockhash(&ixs, Some(&payer_address), &blockhash);
|
|
|
+ let versioned_msg = VersionedMessage::Legacy(msg);
|
|
|
+ let tx = VersionedTransaction::try_new(versioned_msg, &[&payer]).unwrap();
|
|
|
+ // set the time to January 1st 2000
|
|
|
+ let mut initial_clock = svm.get_sysvar::<Clock>();
|
|
|
+ initial_clock.unix_timestamp = 1735689600;
|
|
|
+ svm.set_sysvar::<Clock>(&initial_clock);
|
|
|
+ // this will fail because it's not January 1970 anymore
|
|
|
+ svm.send_transaction(tx).unwrap_err();
|
|
|
+ // so let's turn back time
|
|
|
+ let mut clock = svm.get_sysvar::<Clock>();
|
|
|
+ clock.unix_timestamp = 50;
|
|
|
+ svm.set_sysvar::<Clock>(&clock);
|
|
|
+ let ixs2 = [Instruction {
|
|
|
+ program_id,
|
|
|
+ data: vec![1], // unused, this is just to dedup the transaction
|
|
|
+ accounts: vec![],
|
|
|
+ }];
|
|
|
+ let msg2 = Message::new_with_blockhash(&ixs2, Some(&payer_address), &blockhash);
|
|
|
+ let versioned_msg2 = VersionedMessage::Legacy(msg2);
|
|
|
+ let tx2 = VersionedTransaction::try_new(versioned_msg2, &[&payer]).unwrap();
|
|
|
+ // now the transaction goes through
|
|
|
+ svm.send_transaction(tx2).unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+```ts tab="TypeScript"
|
|
|
+import {
|
|
|
+ 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);
|
|
|
+});
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+```python tab="Python"
|
|
|
+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)
|
|
|
+
|
|
|
+```
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+See also: [`warp_to_slot`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.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:
|
|
|
+
|
|
|
+<Tabs items={["Rust", "TypeScript", "Python"]}>
|
|
|
+```rust tab="Rust"
|
|
|
+use {
|
|
|
+ litesvm::LiteSVM,
|
|
|
+ solana_account::Account,
|
|
|
+ solana_program_option::COption,
|
|
|
+ solana_program_pack::Pack,
|
|
|
+ solana_pubkey::{pubkey, Pubkey},
|
|
|
+ spl_associated_token_account_client::address::get_associated_token_address,
|
|
|
+ spl_token::{
|
|
|
+ state::{Account as TokenAccount, AccountState},
|
|
|
+ ID as TOKEN_PROGRAM_ID,
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+fn test_infinite_usdc_mint() {
|
|
|
+ let owner = Pubkey::new_unique();
|
|
|
+ let usdc_mint = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
|
+ let ata = get_associated_token_address(&owner, &usdc_mint);
|
|
|
+ let usdc_to_own = 1_000_000_000_000;
|
|
|
+ let token_acc = TokenAccount {
|
|
|
+ mint: usdc_mint,
|
|
|
+ owner: owner,
|
|
|
+ amount: usdc_to_own,
|
|
|
+ delegate: COption::None,
|
|
|
+ state: AccountState::Initialized,
|
|
|
+ is_native: COption::None,
|
|
|
+ delegated_amount: 0,
|
|
|
+ close_authority: COption::None,
|
|
|
+ };
|
|
|
+ let mut svm = LiteSVM::new();
|
|
|
+ let mut token_acc_bytes = [0u8; TokenAccount::LEN];
|
|
|
+ TokenAccount::pack(token_acc, &mut token_acc_bytes).unwrap();
|
|
|
+ svm.set_account(
|
|
|
+ ata,
|
|
|
+ Account {
|
|
|
+ lamports: 1_000_000_000,
|
|
|
+ data: token_acc_bytes.to_vec(),
|
|
|
+ owner: TOKEN_PROGRAM_ID,
|
|
|
+ executable: false,
|
|
|
+ rent_epoch: 0,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ let raw_account = svm.get_account(&ata).unwrap();
|
|
|
+ assert_eq!(
|
|
|
+ TokenAccount::unpack(&raw_account.data).unwrap().amount,
|
|
|
+ usdc_to_own
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+```ts tab="TypeScript"
|
|
|
+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);
|
|
|
+});
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+```python tab="Python"
|
|
|
+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
|
|
|
+
|
|
|
+```
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+## 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 [`.with_compute_budget`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.with_compute_budget).
|
|
|
+* Disable transaction signature checking using [`.with_sigverify(false)`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.with_sigverify).
|
|
|
+* Find previous transactions using [`.get_transaction`](https://docs.rs/litesvm/latest/litesvm/struct.LiteSVM.html#method.get_transaction).
|
|
|
+
|
|
|
+## 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.
|