Core Contracts

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

Treasury Balance Desynchronization Through Unvalidated Deposits

Summary

The Treasury contract's deposit function fails to properly validate and track token balances. This breaks fund conservation invariants, potentially leading to accounting discrepancies in the protocol's treasury where token balances are tracked. When a user deposits tokens, the contract updates both individual token balances and total value tracking. However, these updates can become inconsistent with actual token holdings. the internal balance tracking can become desynchronized from actual token holdings

The Treasury contract tracks deposits through two key mechanisms: /Treasury.sol#L25-L27

mapping(address => uint256) private _balances; // Per-token tracking
uint256 private _totalValue; // Aggregate value

The contract blindly trusts that the transfer succeeded and updates internal accounting without verification.

The code assumed ERC20.transferFrom() would revert on failure, but:

  1. Some tokens return false instead of reverting

  2. The contract doesn't use SafeERC20

  3. The balance tracking lacks reconciliation checks

Vulnerability Details

The Treasury contract serves as the backbone of RAAC's financial infrastructure, managing protocol fees and revenue distribution. An attacker can exploit the deposit mechanism to create phantom balances, causing the entire reward distribution system to operate on false assumptions. /Treasury.sol#deposit

function deposit(address token, uint256 amount) external override nonReentrant {
// πŸ” Basic input validation
if (token == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();
// 🚨 Critical vulnerability point
IERC20(token).transferFrom(msg.sender, address(this), amount);
// πŸ’Ύ State updates without transfer verification
_balances[token] += amount; // πŸ“Š Internal balance tracking
_totalValue += amount; // πŸ’° Total value tracking
// πŸ“‘ Event emission
emit Deposited(token, amount);
}

The main vulnerability lies in the unverified token transfer followed by unconditional state updates. This creates a potential desynchronization between actual token holdings and internal accounting.

We can see the function interacts with both FeeCollector.sol and the broader RAAC protocol's economic system, making its accounting accuracy crucial for protocol stability.

this is how the attact: An attacker interacts with the Treasury's deposit function, which blindly trusts token transfers without verifying actual balance changes. When the transfer fails silently, the Treasury still increments its internal accounting. This creates a divergence between reported and actual balances.

The consequences ripple through the protocol's core mechanisms:

// Real impact on fee distribution
uint256 reportedBalance = treasury.getBalance(token); // Shows 1000 RAAC
uint256 actualBalance = IERC20(token).balanceOf(address(treasury)); // Only 100 RAAC

This discrepancy affects the FeeCollector's distribution calculations, the GaugeController's boost parameters, and ultimately the veRAACToken holders' rewards.

Impact

The Treasury contract, designed to be the secure vault for protocol fees and revenue sharing (as outlined in the RAAC whitepaper section), can have its internal accounting completely desynchronized from reality.

// In Treasury.sol
function deposit(address token, uint256 amount) external {
// 🚩 No balance verification
IERC20(token).transferFrom(msg.sender, address(this), amount);
_balances[token] += amount; // πŸ’₯ Blind trust in transfer success
_totalValue += amount; // πŸ”₯ Potential inflation of total value
}

This interacts dangerously with the FeeCollector's distribution system: /FeeCollector.sol#distributeCollectedFees

// In FeeCollector.sol
function distributeCollectedFees() external {
// Relies on Treasury's reported balances
uint256 totalFees = _calculateTotalFees();
// Could distribute more than actually exists!
}

The RAAC protocol's entire fee distribution system (FeeCollector.sol) relies on accurate Treasury accounting. With this bug:

  • veRAACToken holders could receive incorrect reward calculations

  • The GaugeController's boost calculations become unreliable

  • The protocol's revenue sharing mechanism (80/20 split) breaks down

Recommendations

function deposit(address token, uint256 amount) external override nonReentrant {
// πŸ” Input validation
if (token == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();
// βš–οΈ Track initial balance for verification
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
// πŸ”„ Execute transfer with SafeERC20
SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), amount);
// βœ… Verify actual balance change
uint256 balanceAfter = IERC20(token).balanceOf(address(this));
require(balanceAfter - balanceBefore == amount, "Treasury: Balance mismatch");
// πŸ“Š Update internal accounting
_balances[token] += amount;
_totalValue += amount;
// πŸ“‘ Event emission
emit Deposited(token, amount);
}
Updates

Lead Judging Commences

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

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

Treasury::deposit increments _balances[token] with amount, not taking FoT or rebasing into account

Support

FAQs

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

Give us feedback!