Core Contracts

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

FeeCollector is not depositing tokens to Treasury correctly

Summary

Druing _processDistributions the FeeCollector, 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 _processDistributions(uint256 totalFees, uint256[4] memory shares) internal {
uint256 contractBalance = raacToken.balanceOf(address(this));
if (contractBalance < totalFees) revert InsufficientBalance();
if (shares[0] > 0) {
uint256 totalVeRAACSupply = veRAACToken.getTotalVotingPower();
if (totalVeRAACSupply > 0) {
TimeWeightedAverage.createPeriod(
distributionPeriod,
block.timestamp + 1,
7 days,
shares[0],
totalVeRAACSupply
);
totalDistributed += shares[0];
} else {
shares[3] += shares[0]; // Add to treasury if no veRAAC holders
}
}
if (shares[1] > 0) raacToken.burn(shares[1]);
if (shares[2] > 0) raacToken.safeTransfer(repairFund, shares[2]);
if (shares[3] > 0) raacToken.safeTransfer(treasury, shares[3]); // audit why don't deposit, tucked funds
}

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 _processDistributions 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. DISTRIBUTOR_ROLE calls distributeCollectedFees 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 4 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.