Core Contracts

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

`_initializeFeeTypes` in FeeCollector contract doesn't set `feeTypes[6]` and `feeTypes[7]` properly, leading to loss of fees for veRACC holders and repair fund.

Summary

FeeCollector contract defines 7 FeeType structs, handling fee shares proportion between veRAAC holders, burn, repair fund and treasury for various types of fees (protocol fees, lending fees, performance fees, insurance fees, mint/redeem fees, vault fees, swap taxes and NFT royalties.

The sum of all shares for a specific FeeType should be equal to 10_000 (i.e., 100% in basis points).

The problem arises because _initializeFeeTypesdoesnt set properly feeTypes[6] and feeTypes[7]:

function _initializeFeeTypes() internal {
// Protocol Fees: 80% to veRAAC holders, 20% to treasury
feeTypes[0] = FeeType({
veRAACShare: 8000, // 80%
burnShare: 0,
repairShare: 0,
treasuryShare: 2000 // 20%
});
// Lending Fees: Interest income distribution
feeTypes[1] = FeeType({
veRAACShare: 7000, // 70%
burnShare: 0,
repairShare: 0,
treasuryShare: 3000 // 30%
});
// Performance Fees: 20% from yield products
feeTypes[2] = FeeType({
veRAACShare: 6000, // 60%
burnShare: 0,
repairShare: 0,
treasuryShare: 4000 // 40%
});
// Insurance Fees: 3% from NFT loans
feeTypes[3] = FeeType({
veRAACShare: 5000, // 50%
burnShare: 0,
repairShare: 2000, // 20%
treasuryShare: 3000 // 30%
});
// Mint/Redeem Fees
feeTypes[4] = FeeType({
veRAACShare: 6000, // 60%
burnShare: 0,
repairShare: 2000, // 20%
treasuryShare: 2000 // 20%
});
// Vault Fees
feeTypes[5] = FeeType({
veRAACShare: 7000, // 70%
burnShare: 0,
repairShare: 0,
treasuryShare: 3000 // 30%
});
// Buy/Sell Swap Tax (2% total)
feeTypes[6] = FeeType({
veRAACShare: 500, // 0.5%
burnShare: 500, // 0.5%
repairShare: 1000, // 1.0%
treasuryShare: 0
});
// NFT Royalty Fees (2% total)
feeTypes[7] = FeeType({
veRAACShare: 500, // 0.5%
burnShare: 0,
repairShare: 1000, // 1.0%
treasuryShare: 500 // 0.5%
});
}

feeTypes from index 0 to 5 are correctly set, but index 6 and 7 have a total shares equal to 2000, which is not correct.

The "2% total" refers to the fee percentage on swap tx and NFT transfers, like 3% for insurance fees for example. The sum of all shares should not be 2000 (which is not even 2% but 20% in basis points) but 10 000 instead.

Vulnerability Details

_initializeFeeTypes is called in constructor, hence setting feeTypes[6] and feeTypes[7]to incorrect values.

There is an updateFeeType public function that can only be called by the fee manager. This function confirms that all shares for a fee type should be 100%:

function updateFeeType(uint8 feeType, FeeType calldata newFee) external override {
if (!hasRole(FEE_MANAGER_ROLE, msg.sender)) revert UnauthorizedCaller();
if (feeType > 7) revert InvalidFeeType();
// Validate fee shares total to 100%
if (newFee.veRAACShare + newFee.burnShare + newFee.repairShare + newFee.treasuryShare != BASIS_POINTS) {
revert InvalidDistributionParams();
}
feeTypes[feeType] = newFee;
emit FeeTypeUpdated(feeType, newFee);
}

As specified in the comment, fee shares total should be 100%, i.e., 10 000.

This means that as long as updateFeeType is not called by the fee manager to restore correct fee type share values for feeTypes[6] and feeTypes[7], there is a loss of fees for veRAAC holders and repaid funds, and less fees burned, but more fees for the treasury.

Indeed, _calculateDistribution internal function adds remainder to the share for the treasury:

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;
}

shares[3]correspond to the sum of the 8 fee types shares for the treasury. Because feeTypes[6]and feeTypes[7] don't have 100% (10_000) total fee shares, the whole shares[0] + shares[1] + shares[2] + shares[3]will be less than totalFees. This means remainder is a positive non null value that is ultimately added to the treasury share.

Impact

The impact of this issue is high, as it leads to incorrect calculation of fees and loss of:

  • fees for veRAAC holders

  • fees for repair fund.

  • fees for burning

In the same time, the treasury fees are increased, because the remainder will include the missing fees for previous purpose

Tools Used

Manual review.

Recommendations

Correctly set all FeeType in constructor so that fee shares total is 100% or 10 000 for each FeeType, or make sure to call updateFeeType function right after deployment.

Updates

Lead Judging Commences

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

Fee shares for fee type 6 and 7 inside FeeCollector do not total up to the expected 10000 basis points, this leads to update problems, moreover they are 10x the specifications

Support

FAQs

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