Summary
FeeCollector#_calculateDistribution calculates distribution shares for different stakeholders. But these distribution shares can be miscalculated due to precision loss.
Vulnerability 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;
}
For each feeAmount, shares[] are added percent of totalFees, not pure shares. But this flaws can shares set 0 in some cases leading to distribution logic incorrect.
For example, assume: feeAmount = 1e18, totalFees = 100000e18
feeTypes[7] = FeeType({
veRAACShare: 50,
burnShare: 0,
repairShare: 100,
treasuryShare: 50
});
In case of feeTypes[7], weight = 50 * 10000 / 100000 = 0. so in this case all of shares[] += 0 and at the end of this function this feeAmount is added to the treasuryShare. The shares except treasuryShare are miscalculated.
Impact
Distribution logic is broken.
veRAACShares can be 0 leading to stake holders receive fewer rewards.
Tools Used
manual
Recommendations
Calculates shares for each case instead of sum up total percentage of each shares.
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] += (feeAmount * feeType.veRAACShare) / BASIS_POINTS;
+ shares[1] += (feeAmount * feeType.burnShare) / BASIS_POINTS;
+ shares[2] += (feeAmount * feeType.repairShare) / BASIS_POINTS;
+ shares[3] += (feeAmount * 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;
}