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.
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)
indeposit()
function - Mitigation: Uses
checked_add()
with proper error handling - Risk: NONE - Protected by checked arithmetic
- Location:
Underflow Scenarios:
- During SOL withdrawals: Withdrawing more than total deposits
- Location:
vault.deposits.checked_sub(withdraw_amount)
inwithdraw_sol()
- Mitigation: Uses
checked_sub()
with proper error handling - Risk: NONE - Protected by checked arithmetic
- Location:
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()
inadd_to_next_round()
- Risk: NONE - Protected by checked arithmetic
- Location:
-
During deposit operations: Adding user investment per token amounts
- Location:
vault.add_to_next_round(user_account.investment_per_token)
indeposit()
- Mitigation: Uses
checked_add()
throughadd_to_next_round()
- Risk: NONE - Protected by checked arithmetic
- Location:
Underflow Scenarios:
- During investment amount adjustments: Reducing investment when users become inactive
- Location:
vault.subtract_from_next_round(amount)
inupdate_next_round_amount()
- Mitigation: Uses
checked_sub()
insubtract_from_next_round()
- Risk: NONE - Protected by checked arithmetic
- Location:
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)
instart_buy_stage()
- Risk: NONE - Direct assignment, no arithmetic operations
- Location:
Underflow Scenarios:
- No underflow risk: This variable is only set, never decremented
- Risk: NONE
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
instart_new_round()
- Mitigation: NONE - Uses unchecked increment
- Risk: VERY LOW - Would require trillions of years to overflow
- Location:
Underflow Scenarios:
- No underflow risk: This variable is only incremented, never decremented
- Risk: NONE
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)
instart_buy_stage()
- Mitigation: Uses
checked_add()
inadd_fees()
- Risk: NONE - Protected by checked arithmetic
- Location:
Underflow Scenarios:
- During fee withdrawals: Withdrawing more fees than accumulated
- Location:
vault.subtract_fees(amount)
inwithdraw_sol_fees()
- Mitigation: Balance check before subtraction + manual subtraction
- Risk: NONE - Protected by balance validation
- Location:
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)
inbuy_tokens_collective()
- Mitigation: Uses
checked_add()
inadd_or_update_token_balance()
- Risk: NONE - Protected by checked arithmetic
- Location:
Underflow Scenarios:
- During token sales: Selling more tokens than vault holds
- Location:
vault.subtract_token_balance(token_mint, token_amount_to_sell)
insell_tokens()
- Mitigation: Balance validation before subtraction
- Risk: NONE - Protected by balance checks
- Location:
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)
indeposit()
- Mitigation: Uses
checked_add()
inadd_sol_balance()
- Risk: NONE - Protected by checked arithmetic
- Location:
Underflow Scenarios:
-
During withdrawals: Withdrawing more SOL than user balance
- Location:
user_account.subtract_sol_balance(withdraw_amount)
inwithdraw_sol()
- Mitigation: Balance validation before subtraction
- Risk: NONE - Protected by balance checks
- Location:
-
During balance updates: Deducting investment amounts during round processing
- Location:
user_account.subtract_sol_balance(contribution)
inupdate_user_balances_batch()
- Mitigation: Balance validation before subtraction
- Risk: NONE - Protected by balance checks
- Location:
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
- Location: Direct assignment in
Underflow Scenarios:
- No underflow risk: This variable is only set, never decremented
- Risk: NONE
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)
inupdate_user_balances_batch()
- Mitigation: Uses
checked_add()
inadd_or_update_token_balance()
- Risk: LOW - Protected by checked arithmetic
- Location:
Underflow Scenarios:
- During token withdrawals: Withdrawing more tokens than user owns
- Location:
user_account.subtract_token_balance(token_mint, withdraw_amount)
inwithdraw_tokens()
- Mitigation: Balance validation before subtraction
- Risk: NONE - Protected by balance checks
- Location:
sol_balance: u64
- Same risks as UserAccount.sol_balanceamount_per_round: u64
- Same risks as UserAccount.investment_per_tokenlast_participated_round: u64
- Increment-only, very low overflow risktotal_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
amount_spent: u64
- Externally provided, validated against user balancetokens_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
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
(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 determinestotal_tokens_received
(buy_amount as u128 * 1_000_000) / tokens_received as u128
Overflow Scenarios:
- Uses u128 for calculation, preventing overflow
- Risk: NONE - Properly protected
-
Add checked arithmetic to round increments:
vault.current_round = vault.current_round.checked_add(1).ok_or(VaultErrorCode::MathOverflow)?;
-
Validate external inputs: Ensure all externally provided amounts are validated against available balances
-
Consider upper bounds: Implement reasonable maximum limits for investment amounts and deposits
-
Monitor vector growth:
token_holdings
andtoken_balances
vectors could grow large, affecting account size limits -
Add overflow tests: Create specific fuzz tests for edge cases around u64::MAX values
- Maximum value deposits: Test deposits of u64::MAX - 1
- Cumulative overflow: Multiple maximum deposits to trigger overflow
- Fee calculation edge cases: Investments near u64::MAX / 2
- Token allocation precision: Large token amounts with small SOL amounts
- Round counter stress test: Rapid round increments (though impractical)
- Vector size limits: Adding maximum number of token types