Skip to main content
Sigvex

Lamport Token Confusion

Detects confusion between native SOL (lamports) and SPL token operations leading to incorrect value calculations.

Lamport Token Confusion

Overview

Remediation Guide: How to Fix Lamport Token Confusion

The lamport-token confusion detector identifies programs that mix native SOL (lamport) operations with SPL token operations in ways that suggest decimal confusion, account type mismatches, or arithmetic errors. Native SOL uses 9 decimals (1 SOL = 1,000,000,000 lamports), while SPL tokens use varying decimals (e.g., USDC uses 6). Confusing these leads to incorrect value calculations.

Why This Is an Issue

Solana has two types of value: native SOL (lamports, stored in account.lamports) and SPL tokens (stored in token account data). Confusing them causes:

  • Incorrect decimal conversion: Using 10^6 divisor for SOL (should be 10^9), causing 1000x value errors
  • Account type mismatch: Treating a system account as a token account or vice versa
  • Mixed arithmetic: Combining lamport and token amounts in calculations without conversion
  • Fee calculation errors: Computing fees in wrong denomination

Real-world incidents include Orca’s decimal handling errors and multiple DeFi protocol arithmetic bugs.

CWE mapping: CWE-682 (Incorrect Calculation).

How to Resolve

use solana_program::native_token::LAMPORTS_PER_SOL;

pub fn calculate_value(accounts: &[AccountInfo]) -> ProgramResult {
    let sol_account = &accounts[0];
    let token_account = &accounts[1];

    // Clearly separate lamport and token operations
    let sol_balance_lamports = sol_account.lamports();
    let sol_balance_ui = sol_balance_lamports as f64 / LAMPORTS_PER_SOL as f64;

    let token_data = Account::unpack(&token_account.data.borrow())?;
    let token_balance_raw = token_data.amount;
    let token_decimals = get_mint_decimals(token_data.mint)?;
    let token_balance_ui = token_balance_raw as f64 / 10f64.powi(token_decimals as i32);

    // Never mix raw lamports with raw token amounts
    Ok(())
}

Examples

Vulnerable Code

pub fn calculate_fee(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    // VULNERABLE: using wrong divisor for SOL
    let sol_amount = accounts[0].lamports() / 1_000_000; // Wrong! Should be 1_000_000_000

    // VULNERABLE: mixing lamport and token amounts
    let total = sol_amount + token_amount; // Different scales!
    Ok(())
}

Fixed Code

pub fn calculate_fee(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let sol_amount = accounts[0].lamports() / LAMPORTS_PER_SOL; // Correct: 10^9

    // Convert to common scale before mixing
    let token_in_sol = token_amount * sol_price / token_price;
    let total_in_sol = sol_amount + token_in_sol;
    Ok(())
}

Sample Sigvex Output

{
  "detector_id": "lamport-token-confusion",
  "severity": "high",
  "confidence": 0.75,
  "description": "Function mixes native SOL (lamport) and SPL token operations with suspicious decimal conversion using divisor 1000000 (expected 1000000000 for SOL).",
  "location": { "function": "calculate_fee", "offset": 2 }
}

Detection Methodology

  1. Operation evidence collection: Identifies lamport operations (TransferLamports, lamport field access) and token operations (SPL Token CPI calls) within each function.
  2. Decimal analysis: Detects division or multiplication by suspicious constants (10^6, 10^8, 10^10) that suggest wrong decimal handling for SOL.
  3. Mixed operation detection: Flags functions that perform both lamport and token operations, which increases confusion risk.
  4. Constant analysis: Identifies the use of LAMPORTS_PER_SOL (10^9) vs suspicious alternatives.

Limitations

  • Constant folding by the compiler may obscure decimal divisors, making them harder to detect.
  • Legitimate protocols that handle both SOL and tokens will naturally have mixed operations; false positives are possible.
  • Dynamic divisors from external data (e.g., price feeds) are not tracked.

References