This report analyzes a precision loss issue in the FeeCollector::_calculateDistribution function, which is responsible for distributing protocol fees among multiple recipients (veRAAC, burn, repair, and treasury). The function suffers from cumulative rounding errors due to multiple integer divisions, leading to:
Users Receiving Less Than Expected – Rewards for veRAAC stakers, burn mechanisms, and repair funds are systematically reduced.
Unintended Treasury Accumulation – Truncated values create a remainder, which is always allocated to the treasury, leading to unfair distribution over time.
Potential Transaction Reverts – Precision loss may cause the total allocated fees to be slightly lower than totalFees, triggering a revert (InvalidFeeAmount()).
The `_calculateDistribution` function performs integer division three times, compounding precision loss at each step. Since Solidity truncates decimal values instead of rounding, small fractions of fees are lost in every calculation.
The more divisions, the greater the loss of precision.
The final shares distributed are less than the intended amount.
Users receive less rewards, weakening the incentive structure of the protocol.
_calculateDistributionTotal Fees Collected: 1,000,000 tokens
Fee Categories: veRAAC (30%), Burn (20%), Repair (25%), Treasury (25%)
BASIS_POINTS = 10,000
If calculated with perfect precision:
| Category | Expected Tokens |
|---|---|
| veRAAC | 300,000 |
| Burn | 200,000 |
| Repair | 250,000 |
| Treasury | 250,000 |
Example Calculation:
If feeAmount = 333,333 and totalFees = 1,000,000:
(333,333×10,000)÷1,000,000=3333.3(333,333 \times 10,000) \div 1,000,000 = 3333.3(333,333×10,000)÷1,000,000=3333.3
Solidity truncates this to 3333, losing 0.3.
This rounding loss occurs for each fee type (up to 8 times).
If each iteration loses 0.3 tokens, the total loss compounds.
Using weight = 3333 and veRAACShare = 30%:
(3333×30)÷10,000=999.9(3333 \times 30) \div 10,000 = 999.9(3333×30)÷10,000=999.9
Solidity truncates this to 999, losing 0.9 tokens per iteration.
Each category suffers small losses per iteration.
Across 8 iterations, hundreds of tokens are lost.
If shares[0] was expected to be 300,000, but precision loss reduced it to 299,800, then:
(1,000,000×299,800)÷10,000=29,980(1,000,000 \times 299,800) \div 10,000 = 29,980(1,000,000×299,800)÷10,000=29,980
Solidity truncates again, resulting in further token loss.
The veRAAC share is reduced below 300,000, unfairly lowering rewards.
The same happens for Burn and Repair categories.
Reduce scaling and Use 1e18 precision instead of BASIS_POINTS (10,000)
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.