Core Contracts

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

Incorrect scaling in RToken::burn function leads to interest accounting errors

Summary

The RToken's burn function returns incorrect scaled amounts, causing reserve accounting errors in the protocol. The function calculates but doesn't use the proper scaled amount, instead returning the unscaled amount which breaks interest accounting mechanisms.

Vulnerability Details

The vulnerability exists in the RToken's burn function:

function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
// ...
uint256 amountScaled = amount.rayMul(index); // @audit calculated but unused
_burn(from, amount.toUint128()); // @audit uses unscaled amount
// Returns unscaled amount instead of scaled amount
return (amount, totalSupply(), amount);
}

The ReserveLibrary::withdraw expects the first return value to be the scaled amount for proper accounting:

(uint256 burnedScaledAmount, uint256 newTotalSupply, uint256 amountUnderlying) =
IRToken(reserve.reserveRTokenAddress).burn(
recipient,
recipient,
amount,
reserve.liquidityIndex
);

The mismatch occurs because:

  • The burn function calculates amountScaled but never uses it

  • Returns the unscaled amount instead of the scaled amount

  • ReserveLibrary uses this incorrect value for reserve accounting

Impact

High severity due to:

  • Incorrect reserve accounting leading to protocol insolvency risks

  • Interest accrual calculations become inaccurate

  • Users receive incorrect token amounts during withdrawals

  • Protocol's economic model breaks down over time as interest accrues

Proof of Concept

it("causes reserve accounting errors in ReserveLibrary", async function () {
// 1. Setup ReserveLibrary mock
const ReserveLibraryMock = await ethers.getContractFactory("ReserveLibraryMock");
const reserveLibrary = await ReserveLibraryMock.deploy();
await reserveLibrary.setRToken(rToken.address);
// 2. Initial deposit
await reserveLibrary.deposit(INITIAL_DEPOSIT);
// 3. Simulate interest accrual
const newIndex = RAY * 2n;
await reserveLibrary.setLiquidityIndex(newIndex);
// 4. Withdraw and check reserve accounting
const withdrawAmount = INITIAL_DEPOSIT / 2n;
await reserveLibrary.withdraw(withdrawAmount);
// 5. Verify reserve state is incorrect
const reserveData = await reserveLibrary.getReserveData();
// Should be INITIAL_DEPOSIT - (withdrawAmount/2) due to interest
const expectedLiquidity = INITIAL_DEPOSIT - (withdrawAmount / 2n);
// Actually is INITIAL_DEPOSIT - withdrawAmount (wrong!)
expect(reserveData.totalLiquidity).to.not.equal(expectedLiquidity,
"Reserve accounting is incorrect due to wrong scaled amount");
});

Tools Used

  • Manual code review

  • Unit tests with Hardhat

Recommendations

Update the burn function to return the correct scaled amount:

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);
if(amount > userBalance) {
amount = userBalance;
}
uint256 amountScaled = amount.rayDiv(index); // Convert to scaled amount
_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 (amountScaled, totalSupply(), amount); // Return scaled amount first
}
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

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

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

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.