Core Contracts

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

Emergency Withdrawals in `FeeCollector` will break Fee Distribution Logic

Summary

When doing emergency withdraw via emergencyWithdraw(), we send ALL the raacToken to Treasury.

However, these tokens are tracked using collectedFees.
And we do NOT delete collectedFees state variable during emergency withdrawal, which leads to wrong accounting, and fee distribution will always FAIL, as the contract balance will NEVER exceed the collectedFee balance.

Vulnerability Details

FeeCollector contract takes care of fee collection and distribution of fees.

Fee which is collected by the FeeCollector is in the form of raacToken. Whenever someone deposits fee(for any feeType), it is updated in collectedFees struct for tracking & distribution.

This collected fee is later sent to treasury, repairFund, etc while processing distribution(via distributeCollectedFees()).

Then, we delete this state variable collectedFees which simply resets all the different feeType values to zero.
So, the whole process starts all over again, and this is how collection and distribution of fee is carried out.

If there is an emergency situation, all the raacTokens in the FeeCollector can be sent to the treasury contract via emergencyWithdraw()

#emergencyWithdraw()

function emergencyWithdraw(address token) external override whenPaused {
if (!hasRole(EMERGENCY_ROLE, msg.sender)) revert UnauthorizedCaller();
if (token == address(0)) revert InvalidAddress();
uint256 balance;
if (token == address(raacToken)) {
balance = raacToken.balanceOf(address(this));
raacToken.safeTransfer(treasury, balance);
} else

Notice that we are sending all our raacTokens to treasury contract.

However, there lies an issue here i.e. We do NOT delete state variable collectedFees.

This results in incorrect accounting which becomes a problem later on.

When the distribution of fee is carried out, there is a validation check which verifies whether our contract has sufficient balance(Raac Tokens) to pay the fee otherwise it will revert.
#_processDistributions()

function _processDistributions(uint256 totalFees, uint256[4] memory shares) internal {
uint256 contractBalance = raacToken.balanceOf(address(this));
if (contractBalance < totalFees) revert InsufficientBalance();
// totalFees = sum of all feeTypes(collectedFees)
}

After withdrawing all raac tokens via emergency withdraw, the collection of fee will start again.

However, this time the collectedFees will start with the balanceOf(address(this)) value prior to withdrawal.
But, now the balance of raac tokens in contract is ZERO.

So, after collecting new fees, whenever the above function is invoked(via distribution), it will ALWAYS lead to a REVERT because the collectedFee will NEVER match balanceOf the contract.

Although, we can still withdraw these tokens via emergency, but this will break the fee distribution functionality.

Impact

Contract becomes inoperable after emergency withdrawals since fee distributions will always fail.

Users cannot receive their entitled fees.

Tools Used

Manual

Recommendations

Consider adding this in emergencyWithdraw()

function emergencyWithdraw(address token) external override whenPaused {
if (token == address(raacToken)) {
balance = raacToken.balanceOf(address(this));
raacToken.safeTransfer(treasury, balance);
+ delete collectedFees;
}
Updates

Lead Judging Commences

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

FeeCollector::emergencyWithdraw sends all tokens to treasury without resetting collectedFees, breaking rewards and future distributions

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

FeeCollector::emergencyWithdraw sends all tokens to treasury without resetting collectedFees, breaking rewards and future distributions

Support

FAQs

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

Give us feedback!