Core Contracts

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

Denial of Service and Incorrect Reward Emission Due to RAAC Token Transfer Taxes

Summary

Two functions in the StabilityPool contract are affected by the tax mechanism implemented in the RAACToken. In the depositRAACFromPool function, the contract expects the received amount to exactly match the transferred amount; however, since the RAACToken charges taxes on transfers, the actual post-transfer balance is lower than expected, leading to a revert and causing a potential denial-of-service (DoS). Similarly, the withdraw function calculates and emits RAAC reward amounts without accounting for the tax deduction, resulting in inaccurate reward reporting. Although these issues do not directly lead to fund loss, they can disrupt normal operations and user experience, and thus are classified as medium severity.

Vulnerability Details

Issue 1: Denial of Service in depositRAACFromPool

  • Function Code:

    function depositRAACFromPool(uint256 amount) external onlyLiquidityPool validAmount(amount) {
    uint256 preBalance = raacToken.balanceOf(address(this));
    raacToken.safeTransferFrom(msg.sender, address(this), amount);
    uint256 postBalance = raacToken.balanceOf(address(this));
    // @info: dos because raacToken charges some taxes on transfers
    if (postBalance != preBalance + amount) revert InvalidTransfer();
    // TODO: Logic for distributing to managers based on allocation
    emit RAACDepositedFromPool(msg.sender, amount);
    }
  • Problem:
    The function enforces an equality check on the expected balance increase (preBalance + amount). Since the RAACToken charges taxes on transfers (as shown in its _update function), the actual received amount will be less than amount. This strict equality leads to a revert, effectively blocking deposits and causing a DoS condition.

Issue 2: Incorrect RAAC Rewards Emission in withdraw

  • Function Code:

    function withdraw(uint256 deCRVUSDAmount) external nonReentrant whenNotPaused validAmount(deCRVUSDAmount) {
    _update();
    if (deToken.balanceOf(msg.sender) < deCRVUSDAmount) revert InsufficientBalance();
    uint256 rcrvUSDAmount = calculateRcrvUSDAmount(deCRVUSDAmount);
    uint256 raacRewards = calculateRaacRewards(msg.sender);
    if (userDeposits[msg.sender] < rcrvUSDAmount) revert InsufficientBalance();
    userDeposits[msg.sender] -= rcrvUSDAmount;
    if (userDeposits[msg.sender] == 0) {
    delete userDeposits[msg.sender];
    }
    deToken.burn(msg.sender, deCRVUSDAmount);
    rToken.safeTransfer(msg.sender, rcrvUSDAmount);
    if (raacRewards > 0) {
    raacToken.safeTransfer(msg.sender, raacRewards);
    }
    // @info: emitting invalid raacRewards amount because raacToken takes some taxes on transfers
    emit Withdraw(msg.sender, rcrvUSDAmount, deCRVUSDAmount, raacRewards);
    }
  • Problem:
    The withdraw function calculates and emits the RAAC rewards without adjusting for the tax deducted during the transfer. Consequently, the actual amount received by the user is lower than the emitted raacRewards, which can mislead users and third-party monitoring systems regarding the accurate reward distribution.

Underlying RAAC Token Tax Mechanism

The RAACToken’s _update function applies taxes on transfers:

function _update(address from, address to, uint256 amount) internal virtual override {
// Charges taxes on transfers
uint256 baseTax = swapTaxRate + burnTaxRate;
// Skip tax for whitelisted addresses or when fee collector disabled
if (
baseTax == 0 || from == address(0) || to == address(0) || whitelistAddress[from] || whitelistAddress[to]
|| feeCollector == address(0)
) {
super._update(from, to, amount);
return;
}
uint256 totalTax = amount.percentMul(baseTax);
uint256 burnAmount = totalTax * burnTaxRate / baseTax;
super._update(from, feeCollector, totalTax - burnAmount);
super._update(from, address(0), burnAmount);
super._update(from, to, amount - totalTax);
}

Because taxes are deducted (i.e., amount - totalTax is transferred), the expected balances in StabilityPool functions become inconsistent with the actual values.

Impact

  • Denial of Service (DoS):
    The strict balance check in depositRAACFromPool may cause deposits to revert if taxes are applied, effectively preventing the liquidity pool from depositing RAAC tokens.

  • Inaccurate Reward Reporting:
    In the withdraw function, the RAAC rewards emitted do not reflect the net amount received by users, leading to potential confusion and misreporting.

  • Systemic Disruption:
    Over time, these issues can degrade user experience, hinder liquidity operations, and lead to discrepancies in financial reporting within the protocol.

Tools Used

  • Foundry

  • Manual Review

Recommendations

1. Adjust Deposit Check in depositRAACFromPool

  • Current Code:

    if (postBalance != preBalance + amount) revert InvalidTransfer();
  • Recommended Change:
    Instead of requiring an exact match, account for the tax deduction. One approach is to allow a tolerance, for example, ensuring that the postBalance is at least equal to preBalance + (amount - expectedTax) if the expected tax can be estimated, or simply check for a minimum increase:

- if (postBalance != preBalance + amount) revert InvalidTransfer();
+ if (postBalance < preBalance + amount) revert InvalidTransfer();

Alternatively, you could integrate tax logic to compute the expected net amount.

2. Correct RAAC Reward Emission in withdraw

  • Current Behavior:
    The withdraw function transfers RAAC rewards and emits the nominal reward amount without accounting for taxes.

  • Recommended Change:
    Compute the actual net amount received by checking the contract's RAAC token balance before and after the transfer, then emit that actual value:

    uint256 preRewardBalance = raacToken.balanceOf(msg.sender);
    if (raacRewards > 0) {
    raacToken.safeTransfer(msg.sender, raacRewards);
    }
    uint256 postRewardBalance = raacToken.balanceOf(msg.sender);
    uint256 netRewards = postRewardBalance - preRewardBalance;
    emit Withdraw(msg.sender, rcrvUSDAmount, deCRVUSDAmount, netRewards);

    This ensures the event reflects the actual rewards received by the user.

Updates

Lead Judging Commences

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

Support

FAQs

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

Give us feedback!