Core Contracts

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

An attacker can permanently DoS the Treasury contract

Author Revealed upon completion

Summary

The Treasury contract allows anyone to deposit tokens, and it tracks both the per-token balances and the overall total token amount ("total value"). An attacker can call the deposit function with a malicious contract as token address and pass any amount to "fill" the variable uint256 private _totalValue. The malicious contract can implement techniques to force revert on withdrawals. Any subsequent deposit will overflow and thus revert, leading to a permanent DoS state for the Treasury contract.

Vulnerability Details

In Treasury.sol:L46, the deposit function is callable by anyone and accepts an address and a uint256 as parameters. An attacker can call the function with a malicious contract address and an amount that "fills" the remaining uint256 so that it will end up as the maximum possible value for that data type, i.e.

amount = type(uint256).max - _totalValue

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);
}

Subsequent deposits will overflow and revert, leading to a DoS state.

Withdrawals can be blocked trivially, since they use a different function (transferFrom in deposit vs. transfer in withdrawal) - the attacker can simply revert on all transfers. Even using the same type of transfer function would not solve the issue as the attacker can simply implement a counter for example that would allow the first transfer to go through, and force revert on all future transfers.

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);
}

Impact

The Treasury contract will suffer a permanent DoS state. Previously deposited tokens can be rescued, however, all future deposits will revert.

Tools Used

  • Manual review

Recommendations

There are multiple mitigation strategies. The best approach would be to implement access control on the deposit, so that only whitelisted addresses can call the function.

Alternatively, implementing a whitelist functionality for the token addresses is a viable option too if the deposited token addresses are known in advance.

Example for the role based access control:

function deposit(address token, uint256 amount) external override nonReentrant onlyRole(MANAGER_ROLE) {
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);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Treasury::deposit increments _totalValue regardless of the token, be it malicious, different decimals, FoT etc.

Support

FAQs

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