Core Contracts

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

Fee Accounting Desynchronization in Multi-Type Collection

Summary

The fee collection validation fails to properly track total fees when collecting multiple fee types. This breaks the core accounting of protocol revenue and could lead to incorrect distribution of fees to stakeholders.

The FeeCollector contract allows collecting different types of fees (protocol, lending, performance etc). When a fee collection occurs, the contract should properly track the total amount across all fee types. However, the sum of collected fees can become inconsistent with the individual fee type amounts.

When fees are collected, the total after collection equals the total before plus the new amount. Looking at the implementation in FeeCollector.sol, we can see the fee accounting happens in _updateCollectedFees() which updates individual fee type balances, but there's no validation that these updates maintain consistency with the total.

This are the variables involved:

  • collectedFees mapping tracking amounts per fee type

  • totalFees calculated by summing all fee types

  • amount parameter passed to collectFee()

In this sequence: collectFee() → _updateCollectedFees() → fee type balance update → total calculation

Vulnerability Details

The FeeCollector contract manages a fee distribution system where different protocol activities generate distinct fee types from lending operations to NFT royalties. When examining how fees flow through the system, I discovered an accounting discrepancy that could significantly impact veRAACToken holder rewards. /FeeCollector.sol#collectFee()

function collectFee(uint256 amount, uint8 feeType) external override nonReentrant whenNotPaused returns (bool) {
// 🔍 Input Validation
if (amount == 0 || amount > MAX_FEE_AMOUNT) revert InvalidFeeAmount();
if (feeType > 7) revert InvalidFeeType();
// 💸 Token Transfer
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// 📊 State Update - Critical Point
_updateCollectedFees(amount, feeType); // 🚩 Missing total validation
// 📝 Event Emission
emit FeeCollected(feeType, amount);
return true;
}

Begins in the collectFee() function, where the protocol receives fees from various sources. The contract tracks eight different fee types, each with carefully calibrated distribution ratios between veRAACToken holders, the treasury, and the repair fund. For example, protocol fees allocate 80% to veRAACToken holders and 20% to the treasury, while swap taxes split 50-50 between burning and the repair fund.

The critical moment occurs during fee collection. When a new fee arrives, the contract updates individual fee type balances through _updateCollectedFees() but doesn't validate that these updates maintain consistency with the total. This creates a situation where the sum of individual fee types could diverge from _calculateTotalFees(), directly affecting reward calculations.

To make this concrete: Imagine the protocol collects 1000 RAAC in protocol fees and 500 RAAC in lending fees. The individual balances would show 1500 RAAC total, but without atomic validation, the calculated total used for distributions could differ. This means veRAACToken holders expecting 1200 RAAC (80% of protocol fees) plus 350 RAAC (70% of lending fees) might receive incorrect rewards.

Impact

The _updateCollectedFees() function updates individual fee types atomically, but there's no guarantee that these updates maintain consistency with the total calculation. could lead to:

  1. Incorrect reward distributions to veRAACToken holders

  2. Mismatched treasury allocations

  3. Compromised protocol accounting accuracy

The FeeCollector's fee accounting system shows an interesting edge case in how it handles multi-type fee collection. Here's why this matters for RAAC protocol's core financial integrity. _initializeFeeTypes()

// Current Implementation
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%
});
// ... other fee types
}

The protocol thoughtfully segments different fee types with precise allocation rules. This aligns with the whitepaper's section on fee distribution (particularly for RWA yield direction).

This is where things get interesting _calculateTotalFees()

function _calculateTotalFees() internal view returns (uint256) {
return collectedFees.protocolFees +
collectedFees.lendingFees +
// ...
}

The total calculation assumes perfect synchronization with individual updates. This is why this deserves attention.

This pattern resembles classic accounting systems where individual ledger entries must reconcile with the master balance.

Recommendations

// 🔒 Fee Collection with Atomic Validation
function collectFee(uint256 amount, uint8 feeType) external override nonReentrant whenNotPaused returns (bool) {
// 🔍 Input Validation
if (amount == 0 || amount > MAX_FEE_AMOUNT) revert InvalidFeeAmount();
if (feeType > 7) revert InvalidFeeType();
// ⚖️ Track Pre-Update Total
uint256 totalBefore = _calculateTotalFees();
// 💸 Token Transfer
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// 📊 State Update with Validation
_updateCollectedFees(amount, feeType);
require(_calculateTotalFees() == totalBefore + amount, "Fee accounting error");
// 📝 Event Emission
emit FeeCollected(feeType, amount);
return true;
}

We maintain all existing security features (nonReentrant, pausable) while adding crucial total validation. The key addition is the atomic check between pre and post-update totals, ensuring mathematical consistency across all fee operations directly addresses the core accounting vulnerability while preserving the protocol's fee management system.

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!