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
- Operation evidence collection: Identifies lamport operations (TransferLamports, lamport field access) and token operations (SPL Token CPI calls) within each function.
- Decimal analysis: Detects division or multiplication by suspicious constants (10^6, 10^8, 10^10) that suggest wrong decimal handling for SOL.
- Mixed operation detection: Flags functions that perform both lamport and token operations, which increases confusion risk.
- 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.
Related Detectors
- Token Decimal Mismatch — detects decimal mismatch between different SPL tokens
- Integer Overflow — detects arithmetic overflow