Core Contracts

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

Treasury deposits can understate tax token balances causing permanent denial of withdrawals for RAAC token

Summary and Impact

Similar to the Fee collector, The Treasury contract fails to account for token transfer taxes when recording deposit amounts, leading to inflated balance records and potential permanent denial of withdrawals. This directly impacts the RAAC token integration since it implements both swap and burn tax rates.

When users deposit RAAC tokens into the Treasury, the contract records the full pre-tax amount rather than the actual received amount. This creates a discrepancy between the Treasury's recorded balances and actual token holdings, making it impossible to process valid withdrawals and potentially leading to permanent fund lockup.

Looking at the protocol documentation, we can see that RAAC is meant to have full composability across the system:

"RAAC is a protocol designed to bring real estate on-chain and deeply integrate it within on-chain finance rails for seamless accessibility, composability, stability and capital efficiency."

This balance tracking issue breaks this core principle by making RAAC tokens potentially unusable within the Treasury.

Vulnerability Details

The issue occurs in the Treasury's deposit function:

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; // Records full amount without considering tax
_totalValue += amount;
emit Deposited(token, amount);
}

The RAAC token implements tax rates:

uint256 public swapTaxRate = 100; // 1%
uint256 public burnTaxRate = 50; // 0.5%

Let's demonstrate how this breaks the system:

  1. User deposits 1000 RAAC tokens

  2. Due to 0.5% burn tax, Treasury actually receives 995 tokens

  3. Treasury records balance as 1000 tokens

  4. When someone tries to withdraw 995 tokens (the actual balance):

    • Treasury checks against recorded balance (1000) - passes

    • Transfer fails because actual balance is lower

    • Funds become permanently locked

Here's a test demonstrating this:

function testTreasuryBalanceMismatch() public {
uint256 depositAmount = 1000 ether;
// User deposits RAAC tokens
vm.startPrank(user);
raacToken.approve(address(treasury), depositAmount);
treasury.deposit(address(raacToken), depositAmount);
vm.stopPrank();
uint256 actualBalance = raacToken.balanceOf(address(treasury));
uint256 recordedBalance = treasury.getBalance(address(raacToken));
// Verify mismatch
assertEq(recordedBalance, depositAmount);
assertEq(actualBalance, depositAmount * 995 / 1000); // After 0.5% burn tax
// Try to withdraw actual balance - fails
vm.startPrank(manager);
vm.expectRevert();
treasury.withdraw(
address(raacToken),
actualBalance,
manager
);
vm.stopPrank();
}

Tools Used

  • Manual Review

  • Hardhat

Recommendations

The Treasury should track actual received tokens by measuring balance changes:

function deposit(address token, uint256 amount) external override nonReentrant {
if (token == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
IERC20(token).transferFrom(msg.sender, address(this), amount);
uint256 balanceAfter = IERC20(token).balanceOf(address(this));
uint256 actualAmount = balanceAfter - balanceBefore;
_balances[token] += actualAmount;
_totalValue += actualAmount;
emit Deposited(token, actualAmount);
}

This ensures accurate balance tracking regardless of any token tax mechanisms, maintaining the protocol's composability guarantees.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

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.