Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

Unfair Fee Share Distribution Leads to Unintended Increasing of Treasury Shares Over Time Due to the Rounding Error

Summary

The fee distribution mechanism in the FeeCollector introduces an unintended accumulation of fee shares into the treasury due to rounding errors due to the division calculations. When the collected fees are split among different stakeholders, the rounding effect disproportionately favors the treasury, leading to a gradual increase in its share over time.

Vulnerability Details

When there are accumulated fees inside the FeeCollector contract, the distributor role calls the function distributeCollectedFees(). This function calls the _calculateDistribution() to calculate the shareholders' accurate values. Let's look at the _calculateDistribution() with details:

function _calculateDistribution(uint256 totalFees) internal view returns (uint256[4] memory shares) {
uint256 totalCollected;
for (uint8 i = 0; i < 8; i++) {
uint256 feeAmount = _getFeeAmountByType(i);
if (feeAmount == 0) continue;
FeeType memory feeType = feeTypes[i];
totalCollected += feeAmount;
uint256 weight = (feeAmount * BASIS_POINTS) / totalFees;
shares[0] += (weight * feeType.veRAACShare) / BASIS_POINTS;
shares[1] += (weight * feeType.burnShare) / BASIS_POINTS;
shares[2] += (weight * feeType.repairShare) / BASIS_POINTS;
shares[3] += (weight * feeType.treasuryShare) / BASIS_POINTS;
}
if (totalCollected != totalFees) revert InvalidFeeAmount();
shares[0] = (totalFees * shares[0]) / BASIS_POINTS;
shares[1] = (totalFees * shares[1]) / BASIS_POINTS;
shares[2] = (totalFees * shares[2]) / BASIS_POINTS;
shares[3] = (totalFees * shares[3]) / BASIS_POINTS;
uint256 remainder = totalFees - (shares[0] + shares[1] + shares[2] + shares[3]);
if (remainder > 0) shares[3] += remainder;
}

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/collectors/FeeCollector.sol#L431-L457

First of all, the global weight is calculated by multiplying the feeAmount (the amount of fee that is accumulated from one of the eight fee categories) then dividing by the totalFees. This weight is then used for calculating the individual shareholders' fee shares. Finally, the calculated shares are multiplied by the totalFees and divided by the BASIS_POINTS. Here due to the multiple priority of division over multiplication, a huge precision loss occurs.

This rounding error depends on the share factor of each 4 shareholders and also the accumulated fees and its amount may become great which makes an unfair treasury share mintings as the remainder, which calculates the remainder of the shares, escalates. As the remainder becomes high, the protocol sends mints them for the treasury shares.

uint256 remainder = totalFees - (shares[0] + shares[1] + shares[2] + shares[3]);
if (remainder > 0) shares[3] += remainder;

As a consequence, the shares[3]which is the treasury shares, increase unintendedly.

Impact

Other stakeholders (veRAACShare, burnShare, and repairShare) receive slightly less than their intended share, reducing their expected rewards.
While each transaction might introduce a small rounding error, continuous operations over thousands of transactions significantly magnify the impact, potentially distorting the protocol’s economic model.

POC

If we run this function, we can see the differences:

function calculateShares(
uint feeAmount,
uint totalFees,
uint veRAACShare
) public pure returns(uint act, uint acc) {
uint BASIS_POINTS = 10000;
uint256 weight = (feeAmount * BASIS_POINTS) / totalFees;
act = (weight * veRAACShare) / BASIS_POINTS;
act = (totalFees * act) / BASIS_POINTS;
acc = (feeAmount * BASIS_POINTS * veRAACShare * totalFees) / (BASIS_POINTS * BASIS_POINTS * totalFees);
}

For testing purposes, we suppose the feeAmount to be 2.4e15, totalFees to be 7e18, and veRAACShare to be 5000 (a scenario where the insurance fee weight for distribution is being calculated inside the for loop). The result for this function would be:

0:
uint256: act 700000000000000
1:
uint256: acc 1200000000000000

This shows a 71% precision loss which is significant and can lead to wrong share calculation and a large remainder which will then be added to the treasury shares. Furthermore, an unfair treasury share distribution occurs here.

Tools Used

Manual

Recommendations

Consider modifying the share calculation mechanism with improving the precision to prevent such unfair fee distribution. A fixed-point math or WadMath libraries can be used here if it is not intended to change the calculations.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

FeeCollector distributes too much to treasury when fee amounts are small relative to total due to precision loss in (feeAmount * BASIS_POINTS) / totalFees

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

FeeCollector distributes too much to treasury when fee amounts are small relative to total due to precision loss in (feeAmount * BASIS_POINTS) / totalFees

Support

FAQs

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

Give us feedback!