Core Contracts

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

Incorrect token scaling in RToken's `burn` function blocks interest withdrawals

Summary

The RToken implementation prevents users from withdrawing accrued interest due to a mismatch between RToken amounts and underlying crvUSD value in the burn function. While users can withdraw their original deposit amount, they cannot access any earned interest.

Vulnerability Details

RToken handles token amounts in its burn function:

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); // calculated but never used
_userState[from].index = index.toUint128(); // duplicate line
_burn(from, amount.toUint128()); // tries to burn crvUSD amount as RTokens
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
emit Burn(from, receiverOfUnderlying, amount, index);
return (amount, totalSupply(), amount);
}

Example scenario:

User deposits 1000 crvUSD using the deposit function in LendingPool. The same amount parameter will be passed to the _mint function in RToken:

function mint(
address caller,
address onBehalfOf,
uint256 amountToMint,
uint256 index
) {
// ...
_mint(onBehalfOf, amountToMint.toUint128()); // user gets 1000 RTokens
// ...
}

After interest accrues (index = 1.1 * 1e27), balanceOf will return 1000.rayMul(1.1 * 1e27) = 1100 (1100 crvUSD value):

function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account); // shows 1000 RTokens
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}

User should be able to withdraw 1100 crvUSD. User calls withdraw in LendingPool with 1100 for amount. 1100 is passed to the withdraw function in ReserveLibrary, which calls burn with amount = 1100:

_burn(from, amount.toUint128());

User tries to withdraw full 1100 crvUSD balance, but _burn will revert because the user has only 1000 RTokens.

Impact

High: Users can only withdraw their original deposit amount. All accrued interest becomes inaccessible. The problem compounds over time as more interest accrues.

Recommendations

Modify the burn function to properly scale the burn amount (i.e., 1100 / 1.1 = 1000 RTokens to burn):

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);
+ uint256 rTokenAmount = amount.rayDiv(index); // scale down to RToken amount
- _userState[from].index = index.toUint128();
- _burn(from, amount.toUint128());
+ _burn(from, rTokenAmount.toUint128()); // burns 1000 RTokens
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
- emit Burn(from, receiverOfUnderlying, amount, index);
+ emit Burn(from, receiverOfUnderlying, rTokenAmount, index);
- return (amount, totalSupply(), amount);
+ return (rTokenAmount, totalSupply(), amount);
}
Updates

Lead Judging Commences

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

RToken::burn returns incorrect underlying asset amount (amount instead of amountScaled), leading to wrong interest rate calculations

RToken::burn incorrectly calculates amountScaled using rayMul instead of rayDiv, causing incorrect token burn amounts and breaking the interest accrual mechanism

RToken::burn incorrectly burns amount (asset units) instead of amountScaled (token units), breaking token economics and interest-accrual mechanism

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

RToken::burn returns incorrect underlying asset amount (amount instead of amountScaled), leading to wrong interest rate calculations

RToken::burn incorrectly calculates amountScaled using rayMul instead of rayDiv, causing incorrect token burn amounts and breaking the interest accrual mechanism

RToken::burn incorrectly burns amount (asset units) instead of amountScaled (token units), breaking token economics and interest-accrual mechanism

Support

FAQs

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

Give us feedback!