Core Contracts

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

Retroactive Fee Parameter Updates

Summary

*Fees collected under specific parameters must be distributed using those same parameters.*The updateFeeType() function allows changing fee distribution percentages after fees are collected but before distribution. This enables malicious actors to:

  1. Collect fees under favorable terms (e.g., 80% to users).

  2. Update feeType to divert funds (e.g., 80% to treasury).

  3. Execute distribution with new parameters

Vulnerability Details

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);
}
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;
}
/**
* @dev Calculates total fees collected across all fee types
* @return total Total fees collected
*/
function _calculateTotalFees() internal view returns (uint256) {
return collectedFees.protocolFees +
collectedFees.lendingFees +
collectedFees.performanceFees +
collectedFees.insuranceFees +
collectedFees.mintRedeemFees +
collectedFees.vaultFees +
collectedFees.swapTaxes +
collectedFees.nftRoyalties;
}
function collectFee(uint256 amount, uint8 feeType) external override nonReentrant whenNotPaused returns (bool) {
if (amount == 0 || amount > MAX_FEE_AMOUNT) revert InvalidFeeAmount();
if (feeType > 7) revert InvalidFeeType();
// Transfer tokens from sender
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// Update collected fees
_updateCollectedFees(amount, feeType);
emit FeeCollected(feeType, amount);
return true;
}

There is the parameter mutability after collection of Fees. Fees collected under one set of distribution rules can be distributed using different rules if parameters are updated before distribution.

**consider the scenario below:**
collectFee(100, 0) (FeeType 0: 80% to users)
Stores 100 in protocolFees
updateFeeType(0, {veRAACShare: 20%})
distributeCollectedFees()
Distributes 20% to users (not 80%)

Another Example:

function _updateCollectedFees(uint256 amount, uint8 feeType) internal {
// Only tracks AMOUNT, not the fee parameters
if (feeType == 0) collectedFees.protocolFees += amount;
}
function _calculateDistribution(...) {
// Uses CURRENT feeTypes for historical fees
FeeType memory feeType = feeTypes[i]; // ← Problem here
shares[0] += (weight * feeType.veRAACShare) / BASIS_POINTS;
}
function updateFeeType(...) external {
// Changes apply IMMEDIATELY to all un-distributed fees
feeTypes[feeType] = newFee; // No delay or snapshot
}
  1. Protocol collects $10M in fees with 80% user rewards.

  2. Malicious admin changes to 20% user rewards.

  3. $8M diverted to treasury instead of users.

Impact

Treasury/repair fund can steal user rewards retroactively. Users cannot rely on fee distribution rules at collection time.

Tools Used

Foundry

Recommendations

Store fee parameters at collection time using a snapshot mechanism.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!