Core Contracts

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

Incorrect Fee Share Allocation in FeeCollector.sol Leading to Under-Distribution of Rewards and Overfunding of Treasury

Summary

The contract FeeCollector incorrectly initializes the fee types 6 (Buy/Sell Swap Tax) and 7 (NFT Royalty Fees) with shares summing to only 2000 basis points instead of the expected 10000 basis points (BASIS_POINTS). As a result, collected fees are under-allocated across all shares[0, 3] (veRAAC holders, token burn, repair fund and treasury) and eventually overfunded to the treasury via the remainder mechanism.

This default flaw will especially impact veRAAC holders financially when claiming accumulated rewards viaclaimRewards().

The problem persists until manual intervention, i.e. updateFeeType() is called where it validates fee shares total to 100%:

FeeCollector.sol#L225-L227

if (newFee.veRAACShare + newFee.burnShare + newFee.repairShare + newFee.treasuryShare != BASIS_POINTS) {
revert InvalidDistributionParams();
}

Vulnerability Details

The bug originates from the following incorrect fee type initialization in FeeCollector._initializeFeeTypes():

FeeCollector.sol#L379-L393

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

These shares add up to 2000 (500 + 500 + 1000) basis points instead of the expected 10000 basis points, as required/expected by the allocation logic of the for loop in _calculateDistribution():

FeeCollector.sol#L441-L445

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;

Given the following example:

  • totalFees = 7000 tokens

  • Fee types 0 to 5 each collected 1000 tokens.

  • Fee types 6 and 7 each collected 500 tokens.

Step 1. Weight calculation for fee types 6 and 7 taking truncation into consideration:

weight = (500 * 10000) / 7000 = 714

Step 2. Current incorrect allocation for Fee Type 6 taking truncation into consideration:

shares[0] += (714 * 500) / 10000 = 36 // veRAACShare
shares[1] += (714 * 500) / 10000 = 36 // burnShare
shares[2] += (714 * 1000) / 10000 = 71 // repairShare
shares[3] += (714 * 0) / 10000 = 0 // treasuryShare

Total allocated: 36 + 36 + 71 + 0 = 143 tokens
Expected allocation: 500 tokens

This leaves 357 tokens unallocated for fee type 6. The same issue occurs for fee type 7.

Step 3. Remainder (supposedly to handle dust due to preceding truncations) calculation:

FeeCollector.sol#L455

uint256 remainder = totalFees - (shares[0] + shares[1] + shares[2] + shares[3]);

remainder = 7000 - (6000 + 143 + 143) = 714 tokens

Step 4. Adding to treasury:

FeeCollector.sol#L456

if (remainder > 0) shares[3] += remainder;

The 714 tokens are added to shares[3] (the treasury), causing an overfunding and an incorrect distribution.

Impact

Stakeholder Under-Allocation:

  • Only 143 tokens out of 500 are distributed for fee types 6 and 7.

  • Among all parties, this results in stakeholders receiving far less than expected, impacting their earnings and incentives.

Treasury Overfunding:

  • The remainder (714 tokens) is added to the treasury, leading to unintended excess funds.

  • Over time, this imbalance could grow and create financial discrepancies that harm stakeholder trust and require manual reconciliation.

Risk of Cumulative Financial Damage:

  • The cumulative impact could be significant over time, depriving stakeholders of rewards and overfunding the treasury.

Toot Used

Manual

Recommended Mitigation

Consider refactoring the code lines below:

FeeCollector.sol#L379-L393

// Buy/Sell Swap Tax (2% total)
feeTypes[6] = FeeType({
- veRAACShare: 500, // 0.5%
+ veRAACShare: 2500, // 0.5% scaled to 25%
- burnShare: 500, // 0.5%
+ burnShare: 2500, // 0.5% scaled to 25%
- repairShare: 1000, // 1.0%
+ repairShare: 5000, // 1.0% scaled to 50%
treasuryShare: 0
});
// NFT Royalty Fees (2% total)
feeTypes[7] = FeeType({
- veRAACShare: 500, // 0.5%
+ veRAACShare: 2500, // 0.5% scaled to 25%
burnShare: 0,
- repairShare: 1000, // 1.0%
+ repairShare: 5000, // 1.0% scaled to 50%
- treasuryShare: 500 // 0.5%
+ treasuryShare: 2500 // 0.5% scaled to 25%
});
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

FeeCollector distributes too much to treasury when fee amounts are small relative to total due to precision loss in (feeAmount * BASIS_POINTS) / totalFees

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

FeeCollector distributes too much to treasury when fee amounts are small relative to total due to precision loss in (feeAmount * BASIS_POINTS) / totalFees

Support

FAQs

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