Core Contracts

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

Users Lose Accrued Yield Due to StabilityPool's Flawed RToken Tracking

Summary

The StabilityPool contract tracks user deposits using fixed amounts but fails to account for the yield-bearing nature of RTokens, which naturally increase in value over time. This mismatch between the recorded deposit amount and the actual growing RToken value leads to permanent locking of yield earned by users' deposits in the StabilityPool, effectively causing loss of funds for depositors.

Vulnerability Details

The vulnerability occurs in the interaction between the StabilityPool's deposits/withdrawals tracking mechanism and RToken's functionality

  1. when user deposits RToken to the stabilityPool The StabilityPool it records deposits using a fixed amount:

// StabilityPool.sol#L183
userDeposits[msg.sender] += amount;
  1. RToken is designed as a yield-bearing token that grows in value over time through its liquidity index:

// RToken.sol
contract RToken is ERC20, ERC20Permit, IRToken, Ownable {
function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}
}
  1. During withdrawals, the StabilityPool enforces a limit based on the original deposit amount, not accounting for yield growth:

// StabilityPool.sol#L203-204
if (userDeposits[msg.sender] < rcrvUSDAmount) revert InsufficientBalance();
userDeposits[msg.sender] -= rcrvUSDAmount;

The core issue is that while RTokens naturally increase in value through yield accrual (similar to Aave's aTokens), the StabilityPool's accounting system uses fixed amounts that don't grow with the yield. This creates a situation where:

  1. User deposits 100 RTokens

  2. Over time, those 100 RTokens grow to 120 RTokens due to yield

  3. User can only withdraw 100 RTokens because that's what's recorded in userDeposits

  4. The extra 20 RTokens from yield are permanently locked in the contract

This is not a matter of user error or misunderstanding - it's a fundamental flaw in how the StabilityPool tracks and manages yield-bearing tokens.

Impact

  • Users permanently lose access to yield earned on their RToken deposits in the StabilityPool

  • The locked yield accumulates in the stabilityPool with no mechanism for withdrawal

  • This creates a value leak from users to the protocol, making the StabilityPool economically inefficient and discouraging users from providing stability

  • The impact grows over time as more yield gets locked in the contract

Tools Used

  • Foundry

  • Manual Review

Recommendations

The StabilityPool should track user scaledBalance of RToken instead of absolute amounts at the deposit time :

contract StabilityPool {
function deposit(uint256 amount) external {
......
- userDeposits[msg.sender] += amount;
+ userDeposits[msg.sender] += amount.rayDiv(ILendingPool(liquidityPool).getNormalizedIncome());
......
}
function withdraw(uint256 rcrvUSDAmount) external {
- if (userDeposits[msg.sender] < rcrvUSDAmount) revert InsufficientBalance();
- userDeposits[msg.sender] -= rcrvUSDAmount;
+ if (userDeposits[msg.sender].rayMul(ILendingPool(liquidityPool).getNormalizedIncome()) < rcrvUSDAmount) revert InsufficientBalance();
+ userDeposits[msg.sender] -= rcrvUSDAmount.rayDiv(ILendingPool(liquidityPool).getNormalizedIncome());
}
}
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!