Core Contracts

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

`emergencyWithdraw` in FeeCollector will stuck rewards

Summary

If the admin calls emergencyWithdraw, it will transfer all funds to the treasury. However, this will lead to stuck funds because they are not deposited using the deposit function in the Treasury contract, making them non-withdrawable.

FeeCollector Contract

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); // audit wrong
} else {
balance = IERC20(token).balanceOf(address(this));
SafeERC20.safeTransfer(IERC20(token), treasury, balance);
}
emit EmergencyWithdrawal(token, balance);
}

Treasury Contract

/**
* @notice Deposits tokens into the treasury
* @dev Requires approval for token transfer
* @param token Address of token to deposit
* @param amount Amount of tokens to deposit
*/
function deposit(address token, uint256 amount) external override nonReentrant {
if (token == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();
IERC20(token).transferFrom(msg.sender, address(this), amount);
_balances[token] += amount;
_totalValue += amount;
emit Deposited(token, amount);
} // ok
/**
* @notice Withdraws tokens from the treasury
* @dev Only callable by accounts with MANAGER_ROLE
* @param token Address of token to withdraw
* @param amount Amount of tokens to withdraw
* @param recipient Address to receive the tokens
*/
function withdraw(
address token,
uint256 amount,
address recipient
) external override nonReentrant onlyRole(MANAGER_ROLE) {
if (token == address(0)) revert InvalidAddress();
if (recipient == address(0)) revert InvalidRecipient();
if (_balances[token] < amount) revert InsufficientBalance();
_balances[token] -= amount;
_totalValue -= amount;
IERC20(token).transfer(recipient, amount);
emit Withdrawn(token, amount, recipient);
}

Vulnerability Details

Impact

  • Funds transferred via emergencyWithdraw will not be accounted for in the Treasury contract's _balances, preventing them from being withdrawn.

  • This results in a permanent loss of funds unless a manual override is implemented.

Proof of Concept

  1. Admin calls emergencyWithdraw transferring tokens to the Treasury.

  2. Tokens are sent but not recorded in _balances[token] in the Treasury contract.

  3. Any attempt to withdraw these funds will fail due to InsufficientBalance.

Tools Used

Manual review.

Recommendations

Update the FeeCollector contract to use the deposit function, instead of direct transfer.

Updates

Lead Judging Commences

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

FeeCollector::_processDistributions and emergencyWithdraw directly transfer funds to Treasury where they get permanently stuck

Support

FAQs

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