Core Contracts

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

Treasury Withdrawals Bypass Governance Quorum Controls

Summary

The Treasury contract allows withdrawals that exceed the governance quorum-based limits, his breaks a core safety mechanism designed to protect protocol assets through governance oversight. A governance participant can initiate withdrawals that bypass the intended quorum-based safety checks.

The Treasury is meant to be the protocol's secure vault, with withdrawals gated by governance oversight, yet here we have a way to bypass those controls entirely.

Vulnerability Details

The Treasury contract serves as the protocol's secure vault, managing substantial assets including RAAC tokens, lending fees, and real estate yields. This oversight in its withdrawal mechanism creates an interesting scenario where the carefully designed governance controls can be completely sidestepped. quorum()

// 🏛️ Governance.sol"
function quorum() public view override returns (uint256) {
// 📊 Calculates democratic power needed (4% of total)
return (_veToken.getTotalVotingPower() * quorumNumerator) / QUORUM_DENOMINATOR;
}

Imagine a bank vault with two doors, one requiring multiple board member signatures (the governance system) and another surprisingly unlocked side entrance (the Treasury's direct withdrawal function). While the governance system meticulously tracks voting power and enforces a 4% quorum through veRAACToken, the Treasury's withdraw() function operates without these checks.

// 🏦 Treasury.sol - The "Vault"
function withdraw(
address token,
uint256 amount,
address recipient
) external override nonReentrant onlyRole(MANAGER_ROLE) {
// ✅ Checks basic requirements
if (token == address(0)) revert InvalidAddress();
if (recipient == address(0)) revert InvalidRecipient();
if (_balances[token] < amount) revert InsufficientBalance();
// 🚨 Missing: require(amount <= governance.quorum())
// 💸 Transfers funds without governance limits
_balances[token] -= amount;
_totalValue -= amount;
IERC20(token).transfer(recipient, amount);
emit Withdrawn(token, amount, recipient);
}

The disconnect between these functions creates the vulnerability, while Governance calculates quorum requirements, the Treasury's withdraw function doesn't consult these limits before executing transfers.

The consequences are precise and measurable. With a Treasury holding 1M RAAC tokens, the governance system expects major withdrawals to require support from holders representing at least 40,000 veRAACToken voting power. However, the current implementation allows direct withdrawals of the full 1M RAAC, bypassing all governance oversight.

While the Treasury operates in isolation. This creates a systemic risk to the protocol's real estate asset management and lending operations, as treasury funds back these core protocol functions.

To demonstrate the issue, consider how the Treasury interacts with the protocol's other components:

  • The FeeCollector directs protocol revenues here

  • The LendingPool relies on these reserves for stability

  • The RWAGauge and RAACGauge systems depend on treasury-funded emissions

This effectively undermines the entire governance layer's authority over protocol assets.

Impact

Let's say the Treasury holds 1M RAAC tokens:

  • Governance quorum: 4% = 40,000 RAAC

  • Current protection: None

  • Potential loss: 1M RAAC (100%)

Recommendations

The currectn implementation

// 🏛️ Governance.sol
function quorum() public view returns (uint256) {
// ⚖️ Democratic threshold calculation
return (_veToken.getTotalVotingPower() * quorumNumerator) / QUORUM_DENOMINATOR;
}
// 🏦 Treasury.sol
function withdraw(
address token,
uint256 amount,
address recipient
) external nonReentrant onlyRole(MANAGER_ROLE) {
// 🔒 Access control and basic validations
if (token == address(0)) revert InvalidAddress();
if (recipient == address(0)) revert InvalidRecipient();
if (_balances[token] < amount) revert InsufficientBalance();
// 💰 State updates and transfer
_balances[token] -= amount;
_totalValue -= amount;
IERC20(token).transfer(recipient, amount);
}

The fix.

// 🏦 Treasury.sol
function withdraw(
address token,
uint256 amount,
address recipient
) external nonReentrant onlyRole(MANAGER_ROLE) {
// 🔐 Governance quorum check
require(amount <= (getTotalValue() * governance.quorum()) / QUORUM_DENOMINATOR,
"Exceeds governance limits");
// ...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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