Summary
Fee shares are distributed incorrectly for Buy/Sell Swap Tax and NFT Royalty Fees types which leads to loss of funds.
Vulnerability Details
Fee shares are distributed incorrectly for Buy/Sell Swap Tax and NFT Royalty Fees in the _initializeFeeTypes() function of FeeCollector.sol.
feeTypes[6] = FeeType({
veRAACShare: 500,
burnShare: 500,
repairShare: 1000,
treasuryShare: 0
});
feeTypes[7] = FeeType({
veRAACShare: 500,
burnShare: 0,
repairShare: 1000,
treasuryShare: 500
});
Here, in BPS, 500 is equal to 5% not 0.5% and 1000 is equal to 10% not 1%. Thus it is incorrectly initialized.
Impact
These above share types are used for fee calculation and distribution and for Buy/Sell Swap Tax and NFT Royalty Fees fee types, more amount of shares will be distributed, burned, transferred to repair fund or treasury than intended.
Shares are calculated for distribution here:
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;
}
And the distribution is processed here:
function _processDistributions(uint256 totalFees, uint256[4] memory shares) internal {
uint256 contractBalance = raacToken.balanceOf(address(this));
if (contractBalance < totalFees) revert InsufficientBalance();
if (shares[0] > 0) {
uint256 totalVeRAACSupply = veRAACToken.getTotalVotingPower();
if (totalVeRAACSupply > 0) {
TimeWeightedAverage.createPeriod(
distributionPeriod,
block.timestamp + 1,
7 days,
shares[0],
totalVeRAACSupply
);
totalDistributed += shares[0];
} else {
shares[3] += shares[0];
}
}
if (shares[1] > 0) raacToken.burn(shares[1]);
if (shares[2] > 0) raacToken.safeTransfer(repairFund, shares[2]);
if (shares[3] > 0) raacToken.safeTransfer(treasury, shares[3]);
}
Due to this issue, more amount of fees will be distributed and less amount is sent to treasury than intended as:
uint256 remainder = totalFees - (shares[0] + shares[1] + shares[2] + shares[3]);
if (remainder > 0) shares[3] += remainder;
if (shares[3] > 0) raacToken.safeTransfer(treasury, shares[3]);
As remaining fees with fee types not having 100% bps distribution is sent back to the treasury. Thus, loss of funds for the protocol and high severity.
Tools Used
Manual Analysis
Recommendations
Change 500 to 50 i.e 0.5% and 1000 to 100 i.e 1% for accurate shares calculation.