Core Contracts

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

`burn` function in RToken contract doesn't burn the correct amount of RTokens, which leads to loss of funds for users during withdrawals.

Summary

burn function in RToken contract is defined as follows:

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;
}
// @audit HIGH: wrong calculation of amountScaled, should be divided by the index and used to burn RToken.
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 amount passed as argument is the amount to burn in underlying assets (= amount to withdraw).

The issue arises because this amount is used directly for burning :

_burn(from, amount.toUint128());

This is incorrect, as only the scaled amount in RToken units should be burned :

_burn(from, amountScaled);

while amount of underlying assets is transferred back to users with:

if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}

amountScaled is also problematic, as it uses amount and multiplies it by the index. This is incorrect as amount should instead be divided by the index:

// @audit incorrect multiplication of amount (underlying asset unit) to get the RToken amount
uint256 amountScaled = amount.rayMul(index);

Impact

The impact of this issue is high as it leads to incorrect computation that will ultimately:

  • burn amount of RToken

  • transfer amount of underlying asset to the user

This means the user will loose funds as he will redeem 1:1 while each RToken is worth more underlying assets over time.

Tools Used

Manual review.

Recommendations

Make sure to correctly compute amountScaled and use when burning RTokens:

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;
}
// @audit: divide by index to get the amount of RTokens to burn
uint256 amountScaled = amount.rayDiv(index);
_userState[from].index = index.toUint128();
// @audit: use amountScaled for burning
_burn(from, amountScaled);
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
emit Burn(from, receiverOfUnderlying, amount, index);
// @audit return amoutScaled as first return value
return (amountScaled, totalSupply(), amount);
}
Updates

Lead Judging Commences

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

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 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!