Core Contracts

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

Incorrect Lock Amount Accounting Due to RAAC Token Tax Mechanism

Summary

The veRAACToken.lock() function fails to account for RAAC token's tax mechanism when calculating lock amounts. Since RAAC implements both swap and burn taxes, the actual amount received by the veRAACToken contract will be less than the input amount, leading to incorrect accounting in lock positions and potentially affecting users' ability to withdraw their full recorded amounts.

Vulnerability Details

The issue stems from the veRAACToken contract recording pre-tax amounts rather than actual received tokens when creating locks:

function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
// Transfer happens first, but tax is applied
@> raacToken.safeTransferFrom(msg.sender, address(this), amount);
// These all use the pre-tax amount
@> _lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
(int128 bias, int128 slope) = _votingState.calculateAndUpdatePower(
msg.sender,
amount,
unlockTime
);
}

The RAAC token applies tax on transfers:

function _update(address from, address to, uint256 amount) internal virtual override {
uint256 baseTax = swapTaxRate + burnTaxRate; // 1.5% total tax
uint256 totalTax = amount.percentMul(baseTax);
// Actual received amount is (amount - totalTax)
super._update(from, to, amount - totalTax);
}

This creates a discrepancy in the withdrawal function:

function withdraw() external nonReentrant {
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
@> uint256 amount = userLock.amount; // @audit This is the pre-tax amount
// ... state updates ...
// @audit Will succeed if contract has enough total balance
// @audit Even if actual received amount was less
raacToken.safeTransfer(msg.sender, amount);
}

The key issue is that while users can withdraw their recorded amounts as long as the contract has sufficient total balance, this gradual depletion of the contract's balance could affect users withdrawing later, as the contract's actual token balance is less than the sum of all recorded locks.

PoC

  1. Alice has 10,000 RAAC tokens

  2. Alice calls veRAACToken.lock(10,000, 365 days)

  3. Due to 1.5% tax:

    • Contract receives 9,850 RAAC

    • System records lock of 10,000 RAAC

  4. When withdrawing:

    • Alice can withdraw 10,000 RAAC if she's early

    • This depletes more from the contract than was actually locked

    • Affects the availability of funds for other users' withdrawals

Impact

  • Incorrect voting power calculation leading to inflated governance weight

  • System promises withdrawals of pre-tax amounts while holding post-tax amounts

  • Early withdrawers may receive more tokens than actually locked

  • Later withdrawers might receive less than their recorded amount

  • Inaccurate protocol accounting

Tools Used

Manual code review

Recommendations

Calculate and use actual received amount (use balance differences):

function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
uint256 balanceBefore = raacToken.balanceOf(address(this));
raacToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 actualAmount = raacToken.balanceOf(address(this)) - balanceBefore;
_lockState.createLock(msg.sender, actualAmount, duration);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

[INVALID] FoT RAAC breaks veRAACToken

Support

FAQs

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

Give us feedback!