Core Contracts

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

It is not possible to withdraw collected fees from the Treasury contract

Summary

It is not possible to withdraw collected fees from the Treasury contract due to an incorrect deposit mechanism. Fees in FeeCollector::_distributecollectedFees are transferred directly via the ERC20 transfer() function instead of using the Treasury's deposit() function, causing the Treasury’s internal accounting to remain unchanged. As a result, any attempt to withdraw fees from the Treasury contract will revert.


Vulnerability Details

  • The FeeCollector contract is responsible for collecting fees and distributing them to the Treasury.

  • In the _distributeCollectedFees() function, fees are sent to the Treasury contract using the reward token's ERC20 transfer() function.

  • However, the Treasury contract relies on its deposit() function to update balances[token] when receiving funds. This balance is then chedked in the withdraw()function.

  • Since transfer() does not trigger deposit(), the Treasury’s balance remains unchanged, even though it holds the transferred tokens.

  • When attempting to withdraw funds, the Treasury contract checks its internal balance, which has not been updated, leading to an underflow and transaction failure.


Impact

High severity.

The Treasury contract's withdraw() function is effectively blocked, so collected fees cannot be accessed and are permanently locked in the contract.


Tools Used

Manual code review


Recommendations

In FeeCollector::_processDistributions, use the Treasury’s deposit() function instead of direct ERC20 transfer() to ensure proper balance tracking.

solidity
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 here also the treasury's internal accounting is not used
+ if (shares[3] > 0) {
+ raacToken.approve(treasury, shares[3]);
+ treasury.deposit(address(raacToken), shares[3]);
}

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.