Skip to main content

Overview

The light-account-checks crate provides unified account validation for Light Protocol programs. It offers a consistent API across both solana-program and pinocchio SDKs through trait abstraction. Crate: light-account-checks
Location: program-libs/account-checks/
Error Codes: 12006-12021

Key Features

SDK Abstraction

Single codebase works with both Solana and Pinocchio account types

Type Safety

8-byte discriminators ensure correct account types

Rich Errors

Detailed error messages with file:line:column locations

Zero Cost

No runtime overhead from abstraction

Core Types

AccountInfoTrait

Trait abstraction over account info types:
pub trait AccountInfoTrait {
    fn key(&self) -> &[u8; 32];
    fn is_signer(&self) -> bool;
    fn is_writable(&self) -> bool;
    fn lamports(&self) -> u64;
    fn data_len(&self) -> usize;
    fn owner(&self) -> &[u8; 32];
    fn try_borrow_data(&self) -> Result<Ref<[u8]>, BorrowError>;
    fn try_borrow_mut_data(&self) -> Result<RefMut<[u8]>, BorrowError>;
    
    // PDA derivation
    fn find_program_address(
        seeds: &[&[u8]],
        program_id: &[u8; 32]
    ) -> ([u8; 32], u8);
}
Implementations:
  • solana_program::account_info::AccountInfo (feature: solana)
  • pinocchio::account_info::AccountInfo (feature: pinocchio)
  • TestAccountInfo (feature: test-only)

AccountIterator

Enhanced iterator with error location tracking:
let mut iter = AccountIterator::new(accounts);

// Get next account with validation
let authority = iter.next_signer()?;
let state = iter.next_mut()?;
let readonly = iter.next_non_mut()?;

// Optional accounts
let delegate = iter.next_option()?;
Error messages include:
Error: Expected writable account at index 2
  at program.rs:47:23

Validation Functions

Ownership Checks

Verifies account is owned by expected program.
pub fn check_owner<A: AccountInfoTrait>(
    expected_owner: &[u8; 32],
    account: &A,
) -> Result<(), AccountError>
Use case: Verify account belongs to your program before reading/writing.
Checks if account is a program account (executable).
pub fn check_program<A: AccountInfoTrait>(
    account: &A,
) -> Result<(), AccountError>
Use case: Validate CPI targets are executable programs.

Permission Checks

Verifies account signed the transaction.
pub fn check_signer<A: AccountInfoTrait>(
    account: &A,
) -> Result<(), AccountError>
Use case: Ensure authority accounts have valid signatures.
Verifies mutability matches expectation.
pub fn check_mut<A: AccountInfoTrait>(
    account: &A,
) -> Result<(), AccountError>

pub fn check_non_mut<A: AccountInfoTrait>(
    account: &A,
) -> Result<(), AccountError>
Use case: Prevent accidental writes to readonly accounts.

Discriminator Validation

Validates account type via 8-byte prefix.
pub fn check_discriminator<T: Discriminator>(
    bytes: &[u8],
) -> Result<(), AccountError>
Example:
use light_batched_merkle_tree::merkle_tree::BatchedMerkleTreeAccount;

check_discriminator::<BatchedMerkleTreeAccount>(&account_data)?;
Initializes account with type discriminator.
pub fn set_discriminator<T: Discriminator>(
    bytes: &mut [u8],
) -> Result<(), AccountError>
Use case: Called once during account initialization.

PDA Validation

Verifies PDA derivation and finds bump seed.
pub fn check_pda_seeds<A: AccountInfoTrait>(
    account: &A,
    seeds: &[&[u8]],
    program_id: &[u8; 32],
) -> Result<u8, AccountError>
Returns: Canonical bump seedExample:
let bump = check_pda_seeds(
    config_account,
    &[b"config", mint.key()],
    &program_id,
)?;
Verifies PDA with known bump seed.
pub fn check_pda_seeds_with_bump<A: AccountInfoTrait>(
    account: &A,
    seeds: &[&[u8]],
    bump: &[u8],
    program_id: &[u8; 32],
) -> Result<(), AccountError>
Use case: When bump is stored in account data.

Rent Exemption

Validates account has sufficient lamports for rent exemption.
pub fn check_account_balance_is_rent_exempt<A: AccountInfoTrait>(
    account: &A,
    expected_size: usize,
) -> Result<u64, AccountError>
Returns: Rent-exempt balance for the size

Combined Validators

Combined validation: writable + owned + discriminator.
pub fn check_account_info_mut<T: Discriminator, A: AccountInfoTrait>(
    program_id: &[u8; 32],
    account: &A,
) -> Result<(), AccountError>
Combined validation: readonly + owned + discriminator.
pub fn check_account_info_non_mut<T: Discriminator, A: AccountInfoTrait>(
    program_id: &[u8; 32],
    account: &A,
) -> Result<(), AccountError>

Error Codes

CodeErrorDescription
12006InvalidDiscriminatorAccount type mismatch
12007OwnerMismatchAccount owned by wrong program
12008AccountNotSignerRequired signer missing
12009AccountMutableAccount is writable when readonly expected
12010InvalidAccountSizeAccount data too small
12011AccountImmutableAccount is readonly when writable expected
12012AlreadyInitializedAccount already has discriminator
12013SignerCheckFailedAuthority signature validation failed
12014InvalidPdaPDA derivation doesn’t match
12015InvalidBumpInvalid bump seed for PDA
12016BorrowAccountDataFailedFailed to borrow account data
12017BorrowAccountDataMutFailedFailed to mutably borrow account data
12018NotRentExemptAccount balance below rent exemption
12019AccountNotInitializedAccount discriminator is zero
12020InvalidProgramExecutableAccount is not executable
12021InvalidAccountIndexIndex out of bounds

Usage Patterns

Basic Validation

use light_account_checks::{
    checks::*,
    AccountInfoTrait,
    AccountError,
};

pub fn process(
    program_id: &[u8; 32],
    accounts: &[AccountInfo],
) -> Result<(), AccountError> {
    let [authority, state, readonly] = accounts else {
        return Err(AccountError::InvalidAccountIndex);
    };
    
    // Validate authority
    check_signer(authority)?;
    
    // Validate state account
    check_account_info_mut::<StateAccount>(
        program_id,
        state,
    )?;
    
    // Validate readonly
    check_non_mut(readonly)?;
    
    Ok(())
}

With AccountIterator

use light_account_checks::{
    AccountIterator,
    checks::*,
};

pub fn process(accounts: &[AccountInfo]) -> Result<(), AccountError> {
    let mut iter = AccountIterator::new(accounts);
    
    let authority = iter.next_signer()?;
    let state = iter.next_mut()?;
    let mint = iter.next_non_mut()?;
    let delegate = iter.next_option()?;  // Optional
    
    // Validate types
    check_discriminator::<StateAccount>(
        &state.try_borrow_data()?
    )?;
    
    Ok(())
}

PDA Validation

use light_account_checks::checks::*;

// Validate and get bump
let seeds = &[b"config", mint_pubkey.as_ref()];
let bump = check_pda_seeds(
    config_account,
    seeds,
    &program_id,
)?;

// Store bump for future use
config.bump = bump;

// Later: validate with known bump
check_pda_seeds_with_bump(
    config_account,
    seeds,
    &[bump],
    &program_id,
)?;

Custom Account Types

use light_account_checks::discriminator::Discriminator;

#[repr(C)]
pub struct MyAccount {
    pub discriminator: [u8; 8],
    pub data: u64,
}

impl Discriminator for MyAccount {
    const LIGHT_DISCRIMINATOR: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
}

// Now use with validation functions
check_discriminator::<MyAccount>(&account_data)?;

Feature Flags

solana
feature
Enables solana-program::account_info::AccountInfo implementation
pinocchio
feature
Enables pinocchio::account_info::AccountInfo implementation
test-only
feature
Enables TestAccountInfo mock implementation for unit tests
msg
feature
Enables detailed error messages with solana_msg::msg!()

Testing

#[cfg(test)]
mod tests {
    use super::*;
    use light_account_checks::{
        account_info::test_account_info::TestAccountInfo,
        checks::*,
    };
    
    #[test]
    fn test_owner_check() {
        let program_id = [1u8; 32];
        let account = TestAccountInfo {
            owner: program_id,
            ..Default::default()
        };
        
        assert!(check_owner(&program_id, &account).is_ok());
    }
}

Best Practices

Perform all account validation at the start of instruction processing, before any state changes.
Prefer check_account_info_mut and check_account_info_non_mut for common validation patterns.
Use AccountIterator for cleaner code with better error messages including source locations.
Always use constant discriminators to catch type mismatches at compile time.

Resources

Source Code

View on GitHub

API Docs

Rust documentation

Build docs developers (and LLMs) love