Core Contracts

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

Fee shares for fee type 6 and 7 do not total up to the expected 10000 basis points

Summary

In the constructor of FeeCollector.sol, _initializeFeeTypes()is called. However, for fee types 6 & 7, the values set for the respective fee shares do not amount to 10000 basis points. This results in disproportionate distribution of collected fees.

Vulnerability Details

Initializing fee type values

In _initializeFeeTypes(), it sets the default fee types according to protocol rules, which is denoted in basis points. Focusing on fee type 6 and 7:

// 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%
});

As seen above, feeTypes[6] totals up to 500 + 500 + 1000 = 2000. feeTypes[7] totals up to 500 + 1000 + 500 = 2000.

Distributing the Fees

Assume distributor role now distributes the collected fees via distributeCollectedFees().

function distributeCollectedFees() external override nonReentrant whenNotPaused {
if (!hasRole(DISTRIBUTOR_ROLE, msg.sender)) revert UnauthorizedCaller();
uint256 totalFees = _calculateTotalFees();
if (totalFees == 0) revert InsufficientBalance();
uint256[4] memory shares = _calculateDistribution(totalFees);
_processDistributions(totalFees, shares);
delete collectedFees;
emit FeeDistributed(shares[0], shares[1], shares[2], shares[3]);
}
  • _calculateTotalFees()will add together all fee amounts in the respective fee types in CollectedFeesstruct.

  • Now, _calculateDistribution()is called.

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

In the code snippet above from _calculateDistribution(), it is part of the for loop to check through every fee type and distribute the collected fee amount for the particular fee type to 4 types of stakeholders, denoted by shares[].

Simulating the scenario whereby the loop is checking for fee type 6, and feeAmount = 10_000e18. totalFees = 100_000e18:

  1. weight = (10000e18 * 10000) / 100000e18 = 1000e18

  2. shares[0] += (1000e18 * 500) / 10000 = 50e18

  3. shares [1] += (1000e18 * 500) / 10000 = 50e18

  4. shares[2] += (1000e18 * 1000) / 10000 = 100e18

  5. shares[3] += 0

As seen above, amount distributed to the various shares do not total up to the feeAmount of 10000e18. There is 9800e18 remainder undistributed for fee type 6. Hence, referring to line 15 in the code snippet above, all remainder accumulated across every fee will be directed to shares[3], which is to the treasury.

Impact

At _initializeFeeTypes(), it is evident that fee type 6 and 7 are not set correctly as it does not total up to 10000 basis points, and no basis points are allocated to treasury, showing that having the remainder flow to the treasury is not intentional.

These erroneous values that have been set will result in disproportionate distribution of fees, whereby a large portion of the collected fees for fee type 6 and 7 will be distributed to treasury respectively. veRAAC holders will get much lower amounts of distributed RAAC rewards.

Although fee manager admin can update the fee shares via updateFeeType(), there is still possibility that the distributor role calls distributeCollectedFees()with the erroneous fee shares set in the constructor.

Tools Used

Manual

Recommendations

When initializing the default fee types, ensure that the fee shares total up to 10000.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 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.