Skip to content

Instantly share code, notes, and snippets.

@cNoveron
Last active July 29, 2025 23:20
Show Gist options
  • Save cNoveron/64883ff7ee9cd6e73cc11dce1d2c5a8a to your computer and use it in GitHub Desktop.
Save cNoveron/64883ff7ee9cd6e73cc11dce1d2c5a8a to your computer and use it in GitHub Desktop.

Overflow and Underflow Analysis for Vault State Variables

Overview

This document analyzes all state variables in the vault program for potential overflow and underflow vulnerabilities. The analysis covers the Vault account, UserAccount account, and related data structures.

State Variable Analysis

1. Vault Account State Variables

1.1 deposits: u64

Range: 0 to 18,446,744,073,709,551,615 lamports (~18.4 quintillion lamports or ~18.4 billion SOL)

Overflow Scenarios:

  • During deposit operations: Multiple large deposits could cause cumulative overflow
    • Location: vault.add_deposits(deposit_amount) in deposit() function
    • Mitigation: Uses checked_add() with proper error handling
    • Risk: NONE - Protected by checked arithmetic

Underflow Scenarios:

  • During SOL withdrawals: Withdrawing more than total deposits
    • Location: vault.deposits.checked_sub(withdraw_amount) in withdraw_sol()
    • Mitigation: Uses checked_sub() with proper error handling
    • Risk: NONE - Protected by checked arithmetic

1.2 next_round_buy_amount: u64

Range: 0 to 18,446,744,073,709,551,615 lamports

Overflow Scenarios:

  • During user investment updates: Accumulating investment amounts from many users

    • Location: vault.add_to_next_round(amount) in multiple functions
    • Mitigation: Uses checked_add() in add_to_next_round()
    • Risk: NONE - Protected by checked arithmetic
  • During deposit operations: Adding user investment per token amounts

    • Location: vault.add_to_next_round(user_account.investment_per_token) in deposit()
    • Mitigation: Uses checked_add() through add_to_next_round()
    • Risk: NONE - Protected by checked arithmetic

Underflow Scenarios:

  • During investment amount adjustments: Reducing investment when users become inactive
    • Location: vault.subtract_from_next_round(amount) in update_next_round_amount()
    • Mitigation: Uses checked_sub() in subtract_from_next_round()
    • Risk: NONE - Protected by checked arithmetic

1.3 current_round_spend: u64

Range: 0 to 18,446,744,073,709,551,615 lamports

Overflow Scenarios:

  • During direct assignment: No overflow risk as it's set directly
    • Location: vault.set_current_spend(net_buy_amount) in start_buy_stage()
    • Risk: NONE - Direct assignment, no arithmetic operations

Underflow Scenarios:

  • No underflow risk: This variable is only set, never decremented
    • Risk: NONE

1.4 current_round: u64

Range: 0 to 18,446,744,073,709,551,615

Overflow Scenarios:

  • During round increments: After ~18.4 quintillion rounds
    • Location: vault.current_round += 1 in start_new_round()
    • Mitigation: NONE - Uses unchecked increment
    • Risk: VERY LOW - Would require trillions of years to overflow

Underflow Scenarios:

  • No underflow risk: This variable is only incremented, never decremented
    • Risk: NONE

1.5 accumulated_fees: u64

Range: 0 to 18,446,744,073,709,551,615 lamports

Overflow Scenarios:

  • During fee accumulation: Adding 2% fees from each round
    • Location: vault.add_fees(fee_amount) in start_buy_stage()
    • Mitigation: Uses checked_add() in add_fees()
    • Risk: NONE - Protected by checked arithmetic

Underflow Scenarios:

  • During fee withdrawals: Withdrawing more fees than accumulated
    • Location: vault.subtract_fees(amount) in withdraw_sol_fees()
    • Mitigation: Balance check before subtraction + manual subtraction
    • Risk: NONE - Protected by balance validation

1.6 token_holdings: Vec<TokenBalance>

TokenBalance.amount: u64 per token

Overflow Scenarios:

  • During token purchases: Adding received tokens to existing balance
    • Location: vault.add_or_update_token_balance(target_token, tokens_received) in buy_tokens_collective()
    • Mitigation: Uses checked_add() in add_or_update_token_balance()
    • Risk: NONE - Protected by checked arithmetic

Underflow Scenarios:

  • During token sales: Selling more tokens than vault holds
    • Location: vault.subtract_token_balance(token_mint, token_amount_to_sell) in sell_tokens()
    • Mitigation: Balance validation before subtraction
    • Risk: NONE - Protected by balance checks

2. UserAccount State Variables

2.1 sol_balance: u64

Range: 0 to 18,446,744,073,709,551,615 lamports

Overflow Scenarios:

  • During deposits: Adding large deposit amounts
    • Location: user_account.add_sol_balance(deposit_amount) in deposit()
    • Mitigation: Uses checked_add() in add_sol_balance()
    • Risk: NONE - Protected by checked arithmetic

Underflow Scenarios:

  • During withdrawals: Withdrawing more SOL than user balance

    • Location: user_account.subtract_sol_balance(withdraw_amount) in withdraw_sol()
    • Mitigation: Balance validation before subtraction
    • Risk: NONE - Protected by balance checks
  • During balance updates: Deducting investment amounts during round processing

    • Location: user_account.subtract_sol_balance(contribution) in update_user_balances_batch()
    • Mitigation: Balance validation before subtraction
    • Risk: NONE - Protected by balance checks

2.2 investment_per_token: u64

Range: 0 to 18,446,744,073,709,551,615 lamports

Overflow Scenarios:

  • During investment setting: Setting extremely large investment amounts
    • Location: Direct assignment in set_user_investment()
    • Risk: NONE - Direct assignment, no arithmetic operations

Underflow Scenarios:

  • No underflow risk: This variable is only set, never decremented
    • Risk: NONE

2.3 token_balances: Vec<TokenBalance>

TokenBalance.amount: u64 per token

Overflow Scenarios:

  • During token allocation: Adding tokens received from collective purchases
    • Location: user_account.add_or_update_token_balance(token_mint, tokens) in update_user_balances_batch()
    • Mitigation: Uses checked_add() in add_or_update_token_balance()
    • Risk: LOW - Protected by checked arithmetic

Underflow Scenarios:

  • During token withdrawals: Withdrawing more tokens than user owns
    • Location: user_account.subtract_token_balance(token_mint, withdraw_amount) in withdraw_tokens()
    • Mitigation: Balance validation before subtraction
    • Risk: NONE - Protected by balance checks

3. Data Structure Variables

3.1 UserPortfolio (Event/View Structure)

  • sol_balance: u64 - Same risks as UserAccount.sol_balance
  • amount_per_round: u64 - Same risks as UserAccount.investment_per_token
  • last_participated_round: u64 - Increment-only, very low overflow risk
  • total_rounds_participated: u32 - Range: 0 to 4,294,967,295, increment-only

Overflow Scenarios:

  • total_rounds_participated: After ~4.3 billion rounds per user
    • Risk: VERY LOW - Would require millions of years of participation

3.2 UserBuyData (Instruction Parameter)

  • amount_spent: u64 - Externally provided, validated against user balance
  • tokens_received: u64 - Externally provided, validated against actual tokens

Overflow Scenarios:

  • Malicious input: External data could contain max u64 values
    • Risk: MEDIUM - Depends on proper validation in instruction handlers

Critical Mathematical Operations

4.1 Fee Calculations

let fee_amount = (total_investment * 2) / 100;

Overflow Scenarios:

  • When total_investment > u64::MAX / 2 (extremely large)
  • Risk: VERY LOW - Would require ~9 billion SOL investment

4.2 Token Allocation Calculations

(user_net_contribution as u128 * total_tokens_received as u128 / total_sol_spent as u128) as u64

Overflow Scenarios:

  • Intermediate calculation uses u128, preventing overflow in multiplication
  • Final cast to u64 could truncate if result > u64::MAX
  • Risk: NONE - Uses u128 for intermediate calculations, and depends on net_buy_amount which in turn determines total_tokens_received

4.3 Price Calculations

(buy_amount as u128 * 1_000_000) / tokens_received as u128

Overflow Scenarios:

  • Uses u128 for calculation, preventing overflow
  • Risk: NONE - Properly protected

Recommendations

  1. Add checked arithmetic to round increments:

    vault.current_round = vault.current_round.checked_add(1).ok_or(VaultErrorCode::MathOverflow)?;
  2. Validate external inputs: Ensure all externally provided amounts are validated against available balances

  3. Consider upper bounds: Implement reasonable maximum limits for investment amounts and deposits

  4. Monitor vector growth: token_holdings and token_balances vectors could grow large, affecting account size limits

  5. Add overflow tests: Create specific fuzz tests for edge cases around u64::MAX values

Test Scenarios for Fuzzing

  1. Maximum value deposits: Test deposits of u64::MAX - 1
  2. Cumulative overflow: Multiple maximum deposits to trigger overflow
  3. Fee calculation edge cases: Investments near u64::MAX / 2
  4. Token allocation precision: Large token amounts with small SOL amounts
  5. Round counter stress test: Rapid round increments (though impractical)
  6. Vector size limits: Adding maximum number of token types
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment