Core Contracts

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

Missing Interest Accrual in RToken Burn Function Leading to Loss of User Funds

Summary

The burn function in the RToken contract fails to account for accrued interest on a user's existing balance when burning tokens. This omission results in users losing the interest they've earned between their last interaction and the token burn, leading to a direct financial loss.

Vulnerability Details

The burn function in the RToken contract does not calculate or account for the balanceIncrease (accrued interest) on a user's existing balance before burning tokens. This is in contrast to the mint function, which correctly calculates and applies the balanceIncrease for interest accrual. The missing logic in the burn function means that users do not receive the interest they've earned on their deposited assets when they withdraw or burn tokens.

function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
if (amount == 0) {
return (0, totalSupply(), 0);
}
uint256 userBalance = balanceOf(from);
_userState[from].index = index.toUint128();
if(amount > userBalance){
amount = userBalance;
}
uint256 amountScaled = amount.rayMul(index);
_userState[from].index = index.toUint128();
_burn(from, amount.toUint128());
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
emit Burn(from, receiverOfUnderlying, amount, index);
return (amount, totalSupply(), amount);
}

The burn function should calculate the balanceIncrease when the user's last index is less than the current index. This ensures that the user's balance is adjusted by the accrued interest before burning the tokens. The corrected code should look like this:

uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
amount = amount + balanceIncrease
}

POC

  1. Initial Deposit:

    • Alice deposits 100 crvUSD when the liquidity index is 1.0 RAY.

    • She receives 100 RToken (scaled balance: 100 / 1.0 = 100).

  2. Interest Accrual:

    • Over time, the liquidity index increases to 1.05 RAY due to interest.

    • Alice's balance is now 100 * 1.05 = 105 crvUSD (but scaled balance remains 100).

  3. Token Burn:

    • Alice burns 100 RToken.

    • Expected Behavior: Alice should receive 100 * 1.05 = 105 crvUSD (scaled to 100 / 1.05 ≈ 95.238). Her balance should be 105 crvUSD.

    • Actual Behavior: The code burns only 100 RToken (scaled to 95.238), resulting in Alice receiving only 100 crvUSD. The 5 crvUSD interest from her initial deposit is lost.

  4. Result:

    • Alice's accrued interest (5 crvUSD) is not distributed, causing a loss of funds.

Impact

Users lose the interest accrued on their existing deposits when burning tokens, violating the protocol's interest distribution mechanism.

Tools Used

Manual review

Recommendations

Modify the burn function to include the balanceIncrease calculation and adjust the amount accordingly:

function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
if (amount == 0) {
return (0, totalSupply(), 0);
}
uint256 userBalance = balanceOf(from);
_userState[from].index = index.toUint128();
if(amount > userBalance){
amount = userBalance;
}
// Calculate balanceIncrease for interest accrual
+ uint256 balanceIncrease = 0;
+ if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
+ balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
+ amount = amount + balanceIncrease
+ }
uint256 amountScaled = amount.rayMul(index);
_userState[from].index = index.toUint128();
_burn(from, amount.toUint128());
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount + balanceIncrease);
}
emit Burn(from, receiverOfUnderlying, amount + balanceIncrease, index);
return (amount + balanceIncrease, totalSupply(), amount + balanceIncrease);
}
Updates

Lead Judging Commences

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

RToken::burn transfers original deposit amount (amount) to users instead of amount plus interest (amountScaled), causing loss of all accrued interest on withdrawals

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

RToken::burn transfers original deposit amount (amount) to users instead of amount plus interest (amountScaled), causing loss of all accrued interest on withdrawals

Support

FAQs

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

Give us feedback!