Core Contracts

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

Using `balanceOf()` instead of `super.balanceOf()` in `DebtToken::mint()` leads to incorrect debt increase calculation due to double scaling

Summary

In the DebtToken::mint() function, balanceOf(onBehalfOf) is used to get the user's balance, which returns an already scaled balance. This scaled balance is then scaled again when calculating balanceIncrease, resulting in an inflated debt amount.

Vulnerability Details

The balanceOf() function in DebtToken overrides the standard ERC20 implementation to return a scaled balance by multiplying the raw balance with the normalized debt index:

function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}

In the mint() function, this scaled balance is used:

uint256 scaledBalance = balanceOf(onBehalfOf);

The balance is then scaled again when calculating balanceIncrease:

balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);

This double scaling results in a much larger balanceIncrease than intended, leading to users accumulating more debt than they should.

Impact

The incorrect calculation of balanceIncrease leads to users being charged more interest than they should be, directly causing financial loss. This affects every mint operation where users have existing debt.

Tools Used

Manual Review

Proof of Concept

Add the following test case to the test/unit/core/tokens/DebtToken.test.js file:

import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs.js";
// ... other code
it("balanceIncrease inflated due to double scaling", async function () {
const mintAmount = ethers.parseEther("100");
const index = RAY; // Initial index
await debtToken.connect(mockLendingPoolSigner).mint(user1.address, user1.address, mintAmount, index);
const userBalanceAfterFirstMint = await debtToken.balanceOf(user1.address);
expect(userBalanceAfterFirstMint).to.equal(mintAmount);
console.log("User1 balance after mint:", userBalanceAfterFirstMint.toString()); // 100,000000000000000000
const newIndex = RAY * 11n / 10n; // 1.1 RAY = 10% increase
// Setting new normalized debt
await mockLendingPool.setNormalizedDebt(newIndex);
const userBalanceAfterInterest = await debtToken.balanceOf(user1.address);
const expectedBalanceAfterInterest = mintAmount * newIndex / RAY;
expect(userBalanceAfterInterest).to.equal(expectedBalanceAfterInterest);
console.log("User1 balance after interest:", userBalanceAfterInterest.toString()); // 110,000000000000000000
// Increase User1 debt by the same initial amount
const actualBalanceIncrease = userBalanceAfterInterest - userBalanceAfterFirstMint;
const calculatedBalanceIncrease = mintAmount * newIndex / RAY - mintAmount;
expect(actualBalanceIncrease).to.equal(calculatedBalanceIncrease);
// The following fails
// AssertionError: Error in "Mint" event: Error in the 4th argument assertion:
// actual 11000000000000000000 -- which is inflated due to double scaling
// expected 10000000000000000000 -- the correct value
await expect(debtToken.connect(mockLendingPoolSigner).mint(user1.address, user1.address, mintAmount, newIndex))
.to.emit(debtToken, "Mint")
.withArgs(anyValue, anyValue, anyValue, calculatedBalanceIncrease, newIndex);
});

Recommendations

Use super.balanceOf() to get the unscaled balance before performing the scaling calculations:

// ... existing code ...
- uint256 scaledBalance = balanceOf(onBehalfOf);
+ uint256 scaledBalance = super.balanceOf(onBehalfOf);
// ... existing code ...
Updates

Lead Judging Commences

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

DebtToken::mint miscalculates debt by applying interest twice, inflating borrow amounts and risking premature liquidations

Support

FAQs

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