Core Contracts

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

Incorrect Accounting of rToken Deposits in StabilityPool Due to Scaling

Summary

The StabilityPool contract incorrectly tracks user deposits of rToken by using the unscaled amount, while the underlying RToken's transfer logic scales the actual transferred value based on accrued interest. This discrepancy leads to incorrect accounting of user deposits and potential fund insolvency.

Vulnerability Details

The RToken's _update function scales transferred amounts by the normalized income (interest accrued). When a user deposits rTokens into the StabilityPool, the actual underlying transferred is scaled down if interest has accrued. However, the StabilityPool records the deposit using the unscaled amount parameter. This mismatch causes the pool to overstate user deposits, leading to inflated withdrawal entitlements and potential insolvency when users withdraw more than the pool holds.

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update();
@>> rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount);
@>> userDeposits[msg.sender] += amount;
_mintRAACRewards();
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}

POC

  1. Initial State:

    • Assume RToken's normalized income is 1.1e27 (10% interest accrued).

    • User deposits 1000 rTokens into StabilityPool.

  2. Deposit Execution:

    • rToken.safeTransferFrom transfers 1000 rTokens. RToken's _update scales this to: 1000 / 1.1e27 * 1e27 ≈ 909.09.

    • StabilityPool records userDeposits[user] += 1000.

  3. Withdrawal Attempt:

    • User requests to withdraw 1000 rTokens worth. StabilityPool calculates rcrvUSDAmount = calculateRcrvUSDAmount(deCRVUSDAmount) using unscaled 1000, allowing withdrawal of 909.09 rToken * 1.1 ≈ 1000 rToken.

    • However, the pool only holds 909.09 rToken. Subsequent withdrawals will fail due to insufficient funds.

  • RToken Transfer Scaling:

    function _update(address from, address to, uint256 amount) internal override {
    uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
    super._update(from, to, scaledAmount);
    }
  • StabilityPool Deposit Recording:

    function deposit(uint256 amount) external {
    rToken.safeTransferFrom(msg.sender, address(this), amount);
    userDeposits[msg.sender] += amount; // Uses unscaled amount
    }

Impact

  • Users can withdraw more tokens than the pool holds, leading to insolvency.

  • Early withdrawers gain excess rTokens, diluting later users' funds.

Tools Used

Manual Review

Recommendation

Modify the StabilityPool to track scaled rToken amounts:

function deposit(uint256 amount) external {
rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 scaledAmount = amount.rayDiv(lendingPool.getNormalizedIncome());
userDeposits[msg.sender] += scaledAmount;
}

Update calculateDeCRVUSDAmount and calculateRcrvUSDAmount to use scaled values for accurate accounting.

Updates

Lead Judging Commences

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

StabilityPool's userDeposits mapping doesn't update with DEToken transfers or interest accrual, and this combined with RToken transfers causes fund loss and permanent lockup

Support

FAQs

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

Give us feedback!