Core Contracts

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

Incorrect amount of RToken is burnt as lender withdraws asset token.

Summary

The burn function in the RToken contract has a critical scaling issue where it burns raw underlying asset amounts instead of scaled amounts, while also transferring the same raw amount of underlying assets. This breaks the interest-bearing token mechanism and leads to incorrect token accounting.

Vulnerability Details

The protocol uses a liquidity index that grows over time to represent accumulated interest that means when users deposit crvUSD, they receive RTokens that are scaled by dividing by the current liquidity index and when users burn RTokens, the amount should be scaled back up by multiplying by the current index.

Before we dive deep into the issue, here is a short recap of the flow as the lender withdraws his asset tokens;

LendingPool.withdraw(amountOfCRVUSD) -> ReserveLibrary.withdraw -> RToken.burn

The Current Implementation of RToken::burn:

function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
// ...
uint256 amountScaled = amount.rayMul(index);
// @audit burns raw amount of crvUSD asset tokens
_burn(from, amount.toUint128());
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
// ...
}

The issue lies in how amounts are handled:

  • The function receives amount which represents underlying crvUSD tokens

  • It calculates amountScaled but doesn't use it

  • It burns the raw amount of RTokens

  • It transfers the raw amount of crvUSD

Why This Is Wrong:
Let's say:

  • Initial deposit: 100 crvUSD when index = 1.0

  • Later, index grows to 1.1 (10% interest)

  • User's 100 RTokens should now be worth 110 crvUSD

When burning 110 crvUSD worth:

  • Should burn 100 RTokens (110/1.1)

  • Should transfer 110 crvUSD

Current implementation:

  • Burns 110 RTokens (too much)

  • Transfers 110 crvUSD

This breaks the fundamental accounting of the interest-bearing token system.

PoC

  1. Alice deposits 100 crvUSD when index = 1.0

    • Receives 100 RTokens

  2. Index grows to 1.1 (10% interest)

    • Alice's 100 RTokens should be worth 110 crvUSD

  3. Alice burns for 110 crvUSD

    • Current: Burns 110 RTokens (wrong)

    • Should: Burn 100 RTokens (110/1.1)

  4. Result: System burns more RTokens than it should, breaking the interest-bearing mechanism

Impact

  • Incorrect burning of RTokens leads to accounting errors in the protocol

  • Users lose their interest-bearing position value

  • System's total supply becomes incorrect

  • Breaks the core mechanism of the interest-bearing token system

Tools Used

Manual code review

Recommendations

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 amountScaled = amount.rayDiv(index);
_userState[from].index = index.toUint128();
_burn(from, amountScaled.toUint128()); // @audit fixed
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, 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!