Core Contracts

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

RToken::burn returns wrong values

Summary

The burn function in RToken is responsible for burning tokens and transferring the underlying asset to the user. However, it returns incorrect values:

  1. Returns the underlying amount instead of the scaled amount.

  2. Returns amount twice instead of the expected amountScaled.

This leads to incorrect calculations in ReserveLibrary.withdraw, which expects:

  • burnedScaledAmount

  • newTotalSupply

  • amountUnderlying

Instead, the function returns:

  • amount (underlying)

  • totalSupply()

  • amount (underlying) (incorrect)

Vulnerability Details

/**
* @notice Burns RToken from a user and transfers underlying asset
* @param from The address from which tokens are burned
* @param receiverOfUnderlying The address receiving the underlying asset
* @param amount The amount to burn (in underlying asset units)
* @param index The liquidity index at the time of burning
* @return A tuple containing:
* - uint256: The amount of scaled tokens burned
* - uint256: The new total supply after burning
* - uint256: The amount of underlying asset transferred
*/
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 function should return the scaled amount of tokens burned (amountScaled) instead of the raw amount.

First value (amount) should be amountScaled

Last value (amount) should be amount

return (amount, totalSupply(), amount);

ReserveLibrary.withdraw expects the first returned value to be burnedScaledAmount.

  • Returning the wrong values leads to incorrect interest and supply calculations.

  • The lending protocol may incorrectly account for liquidity changes, leading to inconsistencies

function withdraw(
ReserveData storage reserve,
ReserveRateData storage rateData,
uint256 amount,
address recipient
) internal returns (uint256 amountWithdrawn, uint256 amountScaled, uint256 amountUnderlying) {
if (amount < 1) revert InvalidAmount();
// Update the reserve interests
updateReserveInterests(reserve, rateData);
// Burn RToken from the recipient - will send underlying asset to the recipient
(uint256 burnedScaledAmount, uint256 newTotalSupply, uint256 amountUnderlying) = IRToken(reserve.reserveRTokenAddress).burn(
recipient, // from
recipient, // receiverOfUnderlying
amount, // amount
reserve.liquidityIndex // index
);
@> amountWithdrawn = burnedScaledAmount;
// Update the total liquidity and interest rates
updateInterestRatesAndLiquidity(reserve, rateData, 0, amountUnderlying);
emit Withdraw(recipient, amountUnderlying, burnedScaledAmount);
return (amountUnderlying, burnedScaledAmount, amountUnderlying);
}

Impact

The system expects burnedScaledAmount, but receives amountUnderlying, leading to incorrect tracking of supply.

Users will receive fewer tokens than expected

Tools Used

Manual Review

Recommendations

Modify the return statement to correctly return burnedScaledAmount, newTotalSupply, and amountUnderlying.

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);
+ return (amountScaled, totalSupply(), amount);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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

inallhonesty Lead Judge 4 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

Support

FAQs

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