-
Notifications
You must be signed in to change notification settings - Fork 124
Description
Summary
We want to get Rust testing for Solana programs to have the best developer experience possible and there are several improvements that can be made to make this easier and more intuitive for all developers.
Current Pain Points
1. Manual Anchor Account Handling
Current State:
// Developers must manually handle discriminators
fn get_counter_data(svm: &LiteSVM, counter_pubkey: &Pubkey) -> Counter {
let account = svm.get_account(&counter_pubkey).unwrap();
let mut data = &account.data[8..]; // Manual discriminator skip
Counter::deserialize(&mut data).unwrap()
}Impact:
- Error-prone discriminator management
- Boilerplate in every test
- Lost type safety
2. No IDL Integration
Current State:
// Manual instruction construction despite IDL existence
let accounts = vec![
AccountMeta::new(*payer, true),
AccountMeta::new(*counter, true),
AccountMeta::new_readonly(system_program::ID, false),
];
let data = anchor_lang::instruction::Initialize {}.data();
let ix = Instruction { program_id: ID, accounts, data };Impact:
- 60-70% of test code is boilerplate
- No compile-time validation against IDL
- Manual account ordering
3. Program Loading Complexity
Current State:
// Brittle path construction
let program_bytes = include_bytes!("../../target/deploy/test.so");
// Or runtime loading with error-prone paths
let so_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../target/deploy/test.so");Impact:
- Tests fail mysteriously when paths change
- No automatic compilation
- No program discovery
Proposed Solution: litesvm-anchor Crate
Core Features
1. Automatic Anchor Account Management
// Proposed API
impl LiteSVM {
/// Get an Anchor account with automatic discriminator handling
pub fn get_anchor_account<T: AccountDeserialize>(&self, pubkey: &Pubkey) -> Result<T> {
let account = self.get_account(pubkey)
.ok_or(LiteSVMError::AccountNotFound)?;
T::try_deserialize(&mut &account.data[8..])
.map_err(|e| LiteSVMError::AnchorDeserialization(e))
}
/// Set an Anchor account with automatic discriminator
pub fn set_anchor_account<T: AccountSerialize + Discriminator>(
&mut self,
pubkey: Pubkey,
account: T
) -> Result<()> {
let mut data = Vec::with_capacity(8 + account.size());
data.extend_from_slice(&T::DISCRIMINATOR);
account.try_serialize(&mut data)?;
self.set_account(pubkey, Account {
data,
owner: self.program_id,
..Default::default()
})
}
}2. IDL-Based Client Generation
// Proposed API
pub struct AnchorProgram {
idl: Idl,
program_id: Pubkey,
}
impl AnchorProgram {
pub fn from_idl(path: &str) -> Result<Self> { ... }
pub fn instruction(&self, name: &str) -> InstructionBuilder { ... }
}
// Usage
let program = AnchorProgram::from_idl("target/idl/counter.json")?;
let ix = program
.instruction("initialize")
.accounts(|a| a
.payer(payer.pubkey())
.counter(counter.pubkey())
.system_program(system_program::id())
)
.build()?;3. Automatic Program Discovery and Loading
// Proposed API
impl LiteSVM {
/// Load all programs from Anchor.toml
pub fn with_anchor_programs() -> Result<Self> {
let config = AnchorConfig::discover()?;
let mut svm = Self::new();
for (name, program_id) in config.programs {
let so_path = config.target_dir.join(format!("{}.so", name));
// Auto-compile if needed
if should_recompile(&so_path)? {
compile_program(&name)?;
}
svm.add_program(program_id, &std::fs::read(so_path)?)?;
}
Ok(svm)
}
}4. Test Fixture System
// Proposed API
pub struct TestFixture {
pub payer: Keypair,
pub accounts: HashMap<String, Keypair>,
pub mints: HashMap<String, Pubkey>,
}
impl LiteSVM {
pub fn fixture() -> FixtureBuilder {
FixtureBuilder::new()
}
}
// Usage
let fixture = svm.fixture()
.with_payer(10_SOL)
.with_account("alice", 5_SOL)
.with_account("bob", 5_SOL)
.with_token_mint("usdc", 6)
.build()?;5. Enhanced Error Messages
// Proposed error handling
#[derive(Debug, thiserror::Error)]
pub enum AnchorError {
#[error("Anchor constraint violation: {constraint} on account {account}")]
ConstraintViolation {
constraint: String,
account: Pubkey,
details: String,
},
#[error("Anchor error {code}: {message}")]
AnchorProgramError {
code: u32,
message: String,
file: String,
line: u32,
},
}
impl LiteSVM {
pub fn send_transaction_with_anchor_errors(&mut self, tx: Transaction)
-> Result<(), AnchorError> {
// Decode Anchor errors with full context
}
}Additional Quality-of-Life Improvements
1. Time Manipulation Helpers
impl LiteSVM {
pub fn advance_clock_by(&mut self, duration: Duration) { ... }
pub fn warp_to_timestamp(&mut self, timestamp: i64) { ... }
pub fn advance_slots(&mut self, slots: u64) { ... }
}2. PDA and Token Helpers
impl LiteSVM {
pub fn find_program_address(&self, seeds: &[&[u8]], program_id: &Pubkey)
-> (Pubkey, u8) { ... }
pub fn create_token_account(&mut self, owner: &Pubkey, mint: &Pubkey)
-> Result<Pubkey> { ... }
pub fn create_associated_token_account(&mut self, owner: &Pubkey, mint: &Pubkey)
-> Result<Pubkey> { ... }
}3. Transaction Builder
impl LiteSVM {
pub fn transaction(&self) -> TransactionBuilder {
TransactionBuilder::new(self.latest_blockhash())
}
}
// Usage
let result = svm.transaction()
.add_instruction(ix1)
.add_instruction(ix2)
.sign_and_send(&[&payer])?;Implementation Plan
Phase 1: Core Anchor Support (Priority: High)
- Automatic discriminator handling
- Basic IDL parsing
- Anchor account helpers
Phase 2: Developer Ergonomics (Priority: High)
- Program auto-discovery from Anchor.toml
- Test fixture system
- Enhanced error messages
Phase 3: Advanced Features (Priority: Medium)
- Full IDL-based client generation
- Transaction builder pattern
- Time manipulation helpers
Phase 4: Integration (Priority: Medium)
- VS Code extension support
- Test generation CLI
- Migration guide from anchor-test
Expected Impact
Before (Current State)
// 50+ lines of boilerplate for simple test
fn test_increment() {
let mut svm = LiteSVM::new();
let payer = Keypair::new();
svm.airdrop(&payer.pubkey(), 10_000_000_000).unwrap();
let program_bytes = include_bytes!("../../target/deploy/test.so");
svm.add_program(ID, program_bytes).unwrap();
let counter = Keypair::new();
let accounts = vec![
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new(counter.pubkey(), true),
AccountMeta::new_readonly(system_program::ID, false),
];
let data = anchor_lang::instruction::Initialize {}.data();
let ix = Instruction { program_id: ID, accounts, data };
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&payer.pubkey()),
&[&payer, &counter],
svm.latest_blockhash(),
);
svm.send_transaction(tx).unwrap();
// Manual deserialization
let account = svm.get_account(&counter.pubkey()).unwrap();
let mut data = &account.data[8..];
let counter_data = Counter::deserialize(&mut data).unwrap();
assert_eq!(counter_data.count, 0);
}After (With Proposed Changes)
// 10 lines for the same test
#[test]
fn test_increment() {
let mut svm = LiteSVM::with_anchor_programs().unwrap();
let fixture = svm.fixture().with_payer(10_SOL).build().unwrap();
let program = AnchorProgram::from_idl("target/idl/counter.json").unwrap();
let counter = Keypair::new();
svm.transaction()
.add(program.instruction("initialize").accounts(|a| a
.payer(fixture.payer.pubkey())
.counter(counter.pubkey())
))
.sign_and_send(&[&fixture.payer, &counter]).unwrap();
let counter_data: Counter = svm.get_anchor_account(&counter.pubkey()).unwrap();
assert_eq!(counter_data.count, 0);
}Reduction: 80% less boilerplate, 100% more readable
Success Metrics
- Code Reduction: 60-80% less boilerplate in Anchor tests
- Error Clarity: Zero manual discriminator handling errors
- Developer Velocity: 50% faster test writing
- Adoption: Become the standard for Anchor testing
Backwards Compatibility
All proposed changes would be additive through a new litesvm-anchor crate, ensuring zero breaking changes to existing users.
Community Feedback Requested
- Should IDL client generation be runtime or compile-time (proc macro)?
- Should fixture system be part of core or separate crate?
- What other Anchor-specific helpers are most needed?
Conclusion
These improvements would transform liteSVM from a capable testing framework into the definitive solution for Anchor development. The proposed changes address every major pain point identified through extensive real-world testing, and would reduce test code by 60-80% while improving reliability and maintainability.
The Anchor ecosystem desperately needs better testing tools. With these changes, liteSVM could fill that gap and become the standard testing framework for the thousands of Anchor programs being built on Solana.
I'm happy to contribute to the implementation of these features. Please let me know which areas the maintainers would like to prioritize, and I can start with a PR for those components. I'd also like to get feedback and have a discussion on these updates before working on any code changes