Summary
Inside FeeCollector contract the fees are initilized so that all of the shares sum up to 10000 BPS. However two shares are initialized differently which leads to situation where most of the fees are sent to Treasury.
Vulnerability Details
When we look at _initializeFeeTypes we can see that last two fees do not add up to 10000 BPS. As a result 98% of swap fee will be sent to Treasury (when 0 is expected) and 98,5% NFT Royalty Fees will be sent Treasury (when 0,5% is expected).
function _initializeFeeTypes() internal {
feeTypes[0] = FeeType({
veRAACShare: 8000,
burnShare: 0,
repairShare: 0,
treasuryShare: 2000
});
feeTypes[1] = FeeType({
veRAACShare: 7000,
burnShare: 0,
repairShare: 0,
treasuryShare: 3000
});
feeTypes[2] = FeeType({
veRAACShare: 6000,
burnShare: 0,
repairShare: 0,
treasuryShare: 4000
});
feeTypes[3] = FeeType({
veRAACShare: 5000,
burnShare: 0,
repairShare: 2000,
treasuryShare: 3000
});
feeTypes[4] = FeeType({
veRAACShare: 6000,
burnShare: 0,
repairShare: 2000,
treasuryShare: 2000
});
feeTypes[5] = FeeType({
veRAACShare: 7000,
burnShare: 0,
repairShare: 0,
treasuryShare: 3000
});
feeTypes[6] = FeeType({
veRAACShare: 500,
burnShare: 500,
repairShare: 1000,
treasuryShare: 0
});
feeTypes[7] = FeeType({
veRAACShare: 500,
burnShare: 0,
repairShare: 1000,
treasuryShare: 500
});
}
The tokens are added to the Treasury share in _calculateDistribution function.
After the shares are calculate there is remainder calculations which are later added to the treasuryShare.
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;
}
As a result the Treasury will receive most of the share even though it is expected that the Treasury should receive much less.
Impact
Treasury will receive much higher share of swap fee and NFT royalty fee.
Tools Used
Manual Review, Hardhat
Recommendations
Fix _initializeFeeTypes function so that all of the shares add up to 10000 BPS and the full share amount is distributed among the receivers using expected share values.