Skip to content

Feature Request: Comprehensive Anchor Integration and Developer Experience Improvements #197

@brimigs

Description

@brimigs

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

  1. Code Reduction: 60-80% less boilerplate in Anchor tests
  2. Error Clarity: Zero manual discriminator handling errors
  3. Developer Velocity: 50% faster test writing
  4. 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

  1. Should IDL client generation be runtime or compile-time (proc macro)?
  2. Should fixture system be part of core or separate crate?
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions