SSSwap

First Flight #41
Beginner FriendlyRust
100 EXP
View results
Submission Details
Severity: high
Valid

Critical Assumption of Equal Token Decimals Compromises Pool Integrity

Summary

The AMM contract does not validate whether token_mint_a and token_mint_b share the same number of decimals during pool initialization. Furthermore, key arithmetic operations that determine liquidity provisions and LP token minting assume both tokens use identical decimal scales. This design flaw allows users to create liquidity pools with mismatched token decimals, leading to incorrect ratio calculations, imbalanced reserves, and potential value extraction via arbitrage.

Vulnerability Details

In the initialize_pool function of liquidity_operations.rs, no check is performed to ensure that token_mint_a.decimals matches token_mint_b.decimals. Consequently, the internal accounting logic, which operates directly on raw u64 token amounts, assumes 1 unit of token A is equivalent to 1 unit of token B—an assumption that fails if the tokens have different decimal precision.

This mismatch propagates to two critical functions:

  • calculate_token_b_provision_with_a_given: This function computes how many units of token B must be added given an amount of token A, using the formula (reserve_b * amount_a) / reserve_a without any normalization to a common decimal scale.

  • calculate_lp_amount : It calculates the LP tokens to mint as sqrt(amount_a * amount_b) directly on raw u64 inputs, again ignoring the potential difference in token scales.

Proof of Concept

  • Token A: 6 decimals

  • Token B: 9 decimals

  • Initial reserves: 1 A = 1_000_000, 1 B = 1_000_000_000

  • User deposits 0.5 A (500_000 raw), contract computes 0.5 B (500_000_000 raw)

Despite appearing balanced, subsequent swaps drastically skew reserves due to the magnitude difference in decimals. This enables an attacker to extract liquidity by exploiting the inaccurate internal representation of value.

Impact

The absence of decimal normalization leads to misaligned liquidity ratios, breaking the invariant x * y = k that AMMs rely on. This results in:

  • Arbitrage opportunities due to distorted exchange rates

  • Value extraction at the expense of unsuspecting liquidity providers

  • Long-term pool imbalance and potential user fund losses

Tools Used

This issue was identified through manual code review.

Recommendations

Minimal Fix

Enforce decimal equality during pool initialization:

require!(
ctx.accounts.token_mint_a.decimals == ctx.accounts.token_mint_b.decimals
AmmError::DecimalsMismatch
);

Optional Robust Fix

Normalize all token values to a common internal scale (e.g., 18 decimals) before any arithmetic operations:

let normalized_reserve_a = (reserve_a as u128)
.checked_mul(10u128.pow((18 - decimals_a) as u32))
.unwrap();
let normalized_reserve_b = (reserve_b as u128)
.checked_mul(10u128.pow((18 - decimals_b) as u32))
.unwrap();

This ensures proportionality is preserved regardless of token mint configuration.

Updates

Lead Judging Commences

0xtimefliez Lead Judge 11 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Protocol is incompatible with differenct decimals

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.