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
- 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.
- u256 library: Use a wider integer type for the intermediate product.
- 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
Summary
distribute_feesinsrc/math.rshas 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 nearu64::MAXwith a 5x junior multiplier.Root Cause
The function uses a quotient+remainder decomposition when
total_fee * junior_weightoverflows u128. If the remainder multiplication (r * junior_weight) ALSO overflows u128, the fallbackunwrap_or(total_fee as u128)at ~line 269 returns the entire fee aspart2. 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.6total_fee * junior_weight ≈ 2^143.6→ overflows u128 → enters fallbackq = total_fee / total_weight = 0(sincetotal_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)u64::MAX(100%), senior gets 0u64::MAX * 5/6 ≈ 83.3%, senior should get≈ 16.7%Practical Impact
Near-zero. Triggering requires ALL of:
junior_balanceandsenior_balancenearu64::MAX(~18.4 quintillion)total_feenearu64::MAXjunior_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
junior_weight / total_weightbefore multiplying byr. Since the ratio is alwaysmult / (mult + 10000), this trivially fits in u128.randjunior_weightby a common factor before multiplying.Severity
LOW — mathematically incorrect but practically unreachable. Documented as a known limitation for completeness.
🤖 Generated with Claude Code