Core Contracts

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

Users potentially withdraw reserve tokens without burning corresponding RTokens

Summary

The burn() function of the RToken contract fails to perform a validation to ensure that amountScaled is greater than zero before proceeding with the burn operation. This allows for withdrawal where users end up with underlying tokens but no Rtokens are burnt in exchange.

Vulnerability Details

The reserve.liquidityIndex represents the cumulative interest earned by liquidity providers in the lending protocol.

Now, the protocol overides the normal _update() function as follows in RToken contract:

function _update(address from, address to, uint256 amount) internal override {
// Scale amount by normalized income for all operations (mint, burn, transfer)
>> uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
super._update(from, to, scaledAmount);
}

This function is designed to first scale the amount into scaledAmount via the rayDiv function based on the current liquidityIndex.

However, notice that _update() is an internal function invoked in functions such as _mint() and _burn() which here are used to mint and burn RTokens to/from users respectively.

Now during deposit, a user specifies the amount of reserve tokens they wish to supply. This amount effectively propagates to RToken.mint() as amountToMint which is handled as follows:

// @audit-info Amount is scaled via rayDiv() then validated that is not 0
uint256 amountScaled = amountToMint.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
---SNIP---
// @audit-info User gets minted the amountScaled
_mint(onBehalfOf, amountToMint.toUint128());

As seen above, the amountToMint is first scaled via the rayDiv(index) as done by the _update(). The function further ensures that this amountScaled is not 0 to ensure that a user does not end up with zero minted RTokens when they have supplied real amount of underlying tokens.

During withdrawal however, the amountScaled is calculated incorrectly much less validated.

When withdrawing, a user specifies the amount of reserve tokens they want to withdraw and this amount is propagated downstream to RToken.burn():

// @audit-issue Incorrect scaling
uint256 amountScaled = amount.rayMul(index);
// @audit-issue Missing validation of amountScaled
// @audit burning done here
_burn(from, amount.toUint128());
// @audit Funds sent to user
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}

Now, the amount is scaled via the rayMul(index) when in reality, during burning, the amount will be scaled in _update() via the rayDiv(index). Furthermore, this function fails to ensure that the amountScaled is greater than 0 but simply proceeds to transfer underlying tokens to the user.

Impact

Since the amountScaled is not validated, the amount burned can be 0 yet the user gets real underlying tokens in exchange. This results in incorrect withdrawals as users end up with real funds from the protocol without incurring the necessary token burns.

Tools Used

Manual Review

Recommendations

Perfrom a zero validation in burn():

- uint256 amountScaled = amount.rayMul(index);
+ uint256 amountScaled = amount.rayDiv(index);
+ // @audit validated against 0
+ if (amountScaled == 0) revert InvalidAmount();
Updates

Lead Judging Commences

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

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

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

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

Support

FAQs

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

Give us feedback!