Skip to content

math: distribute_fees double-overflow fallback gives 100% to junior at extreme u64 values #120

@0x-SquidSol

Description

@0x-SquidSol

Summary

distribute_fees in src/math.rs has a double-overflow fallback path that incorrectly allocates 100% of fees to the junior tranche (0% to senior) when both tranche balances and the fee amount are near u64::MAX with a 5x junior multiplier.

Root Cause

The function uses a quotient+remainder decomposition when total_fee * junior_weight overflows u128. If the remainder multiplication (r * junior_weight) ALSO overflows u128, the fallback unwrap_or(total_fee as u128) at ~line 269 returns the entire fee as part2. After the .min(total_fee) clamp, junior receives 100% of fees.

Arithmetic Proof

With junior_balance = u64::MAX, senior_balance = u64::MAX, junior_fee_mult_bps = 50000, total_fee = u64::MAX:

  • junior_weight = u64::MAX * 50000 ≈ 2^79.6
  • total_fee * junior_weight ≈ 2^143.6 → overflows u128 → enters fallback
  • q = total_fee / total_weight = 0 (since total_weight ≈ 2^80 > total_fee ≈ 2^64)
  • r = total_fee (entire fee is the remainder)
  • r * junior_weight ≈ 2^143.6 → overflows u128 → unwrap_or(total_fee)
  • Result: junior gets u64::MAX (100%), senior gets 0
  • Correct: junior should get u64::MAX * 5/6 ≈ 83.3%, senior should get ≈ 16.7%

Practical Impact

Near-zero. Triggering requires ALL of:

  • Both junior_balance and senior_balance near u64::MAX (~18.4 quintillion)
  • total_fee near u64::MAX
  • junior_fee_mult_bps = 50000 (5x, the maximum)

No real Solana token has balances approaching these values. This is a theoretical correctness issue, not a practical exploit.

Possible Fixes

  1. GCD-based fraction reduction: Reduce junior_weight / total_weight before multiplying by r. Since the ratio is always mult / (mult + 10000), this trivially fits in u128.
  2. u256 library: Use a wider integer type for the intermediate product.
  3. Shift both sides: Divide both r and junior_weight by a common factor before multiplying.

Severity

LOW — mathematically incorrect but practically unreachable. Documented as a known limitation for completeness.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions