SSSwap

First Flight #41
Beginner FriendlyRust
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

Residual Dust from Truncation Breaks AMM Invariant

Summary

The AMM contract performs integer division using checked_div in several liquidity-related functions, resulting in truncation of fractional values. These lost remainders accumulate as residual “dust” in the vaults. Over time, this dust skews the x·y=k invariant, creates unaccounted imbalances, and enables repeated micropayment attacks to extract the unrewarded dust.

Vulnerability Details

Multiple functions in liquidity_operations.rs conduct financial calculations with integer arithmetic, specifically using checked_div. This implicitly floors the division result, discarding any remainder. The resulting “dust” remains in the vault, unallocated to users or LPs.

Affected Functions

  • calculate_token_b_provision_with_a_given:
    Uses reserve_b * amount_a / reserve_a without handling the division remainder.

  • add_liquidity :
    Computes LP to mint with amount_a * total_lp_supply / reserve_a, again truncating any fractional output.

  • calculate_lp_amount:
    Applies .sqrt() on a multiplication product, truncating any non-square result.

Impact

  • Dust Accumulation: Repeated operations slowly build up unused funds in vaults.

  • Micropayment Exploits: Attackers can perform small transactions to harvest dust left behind.

  • User Losses: Liquidity providers receive less than their proportional share, impacting trust.

  • Curve Distortion: The AMM invariant degrades over time, especially with many small transactions.

Proof of Concept

With reserve_a = 1003, reserve_b = 2001, and amount_a = 5:

prod = 2001 * 5 = 10005
amount_b = floor(10005 / 1003) = 9
residue = 10005 - (9 * 1003) = 978 ///(dust)

The user receives 9 units of token B, though a precise ratio would yield approximately 9.97. The 0.97 remains trapped in the vault.

This issue affects not only fair reward distribution but also the accuracy of swap and liquidity operations.

Tools Used

Manual code review and symbolic math analysis.

Recommendations

Round-Up on Division

Adjust division operations to round up whenever a non-zero remainder exists:

let prod = reserve_b_u128.checked_mul(amount_a_u128).ok_or(AmmError::Overflow)?;
let (quotient, remainder) = (prod / reserve_a_u128, prod % reserve_a_u128);
let amount_b_u128 = if remainder == 0 {
quotient
} else {
quotient.checked_add(1).ok_or(AmmError::Overflow)?
};

Apply the same rounding logic in:

  • LP minting calculation during add_liquidity

  • Any similar proportional calculation using .checked_div

Optional Improvement

If rounding up is undesirable for certain operations, consider tracking dust explicitly in the contract and redistributing it periodically (e.g., to LPs or burned).


Unchecked truncation in financial calculations leads to systematic value loss and opens doors for dust harvesting attacks. The recommended solution is to round up all value-distributing operations, ensuring no residual tokens accumulate unaccounted in vaults. A protocol-wide review of arithmetic involving checked_div is advisable to ensure consistency and fairness.

Updates

Lead Judging Commences

0xtimefliez Lead Judge 11 days ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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