Core Contracts

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

Incorrect balance used in `DebtToken::mint()` leads to double scaling of user debt

Summary

The DebtToken::mint() function contains a bug that causes double scaling of user debt when minting new tokens. This occurs due to using DebtToken::balanceOf() instead of super.balanceOf() for interest calculations, resulting in users receiving more debt tokens than they should.

Vulnerability Details

In the DebtToken::mint() function, when calculating interest for a new mint operation, the code incorrectly uses DebtToken::balanceOf() instead of super.balanceOf(). This causes two issues:

  1. Wrong balance is used for interest calculation

  2. Interest is effectively double counted due to adding balanceIncrease to the mint amount

Here's a breakdown of what happens:

With Bug (Current):

  1. First deposit = 1000 tokens

    • scaledBalance = 1000 (at index 1.0)

  2. Index increases to 1.1

    • balance = 1100 (using balanceOf)

  3. Second deposit = 1000 tokens

    • balanceIncrease = 1100 * 1.1 - 1100 = 110

    • amountToMint = 1000 + 110 = 1110

    • Final balance = 2210

Correct Calculation:

  1. First deposit = 1000 tokens

    • scaledBalance = 1000 (at index 1.0)

  2. Second deposit = 1000 tokens

    • scaledBalance = 1000/1.1 = 909.09 (at index 1.1)

  3. Total scaledBalance = 1909.09

  4. Final balance = 1909.09 * 1.1 = 2100

Proof of Concept

Create a file DebtTokenTest.t.sol under /test/foundry/ with the following code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import {DebtToken} from "../../contracts/core/tokens/DebtToken.sol";
import {WadRayMath} from "../../contracts/libraries/math/WadRayMath.sol";
import {MockLendingPoolDebtToken} from "../../contracts/mocks/core/pools/MockLendingPoolDebtToken.sol";
contract DebtTokenTest is Test {
using WadRayMath for uint256;
DebtToken public debtToken;
MockLendingPoolDebtToken public mockLendingPool;
address public user = address(1);
uint256 constant RAY = 1e27;
function setUp() public {
mockLendingPool = new MockLendingPoolDebtToken();
mockLendingPool.setNormalizedDebt(RAY);
debtToken = new DebtToken("Debt Token", "DT", address(this));
debtToken.setReservePool(address(mockLendingPool));
vm.deal(address(mockLendingPool), 100 ether);
}
function testDoubleScalingMint() public {
// Initial mint with index = 1.0
uint256 initialIndex = RAY; // 1.0 in RAY
uint256 mintAmount = 1000e18; // 1000 tokens
vm.startPrank(address(mockLendingPool));
debtToken.mint(user, user, mintAmount, initialIndex);
vm.stopPrank();
// First check - should be 1000 tokens (no interest yet)
uint256 normalBalance1 = debtToken.balanceOf(user);
uint256 scaledBalance1 = debtToken.scaledBalanceOf(user);
console.log("Initial normal balance:", normalBalance1);
console.log("Initial scaled balance:", scaledBalance1);
assertEq(normalBalance1, mintAmount, "Initial balance should be 1000");
// Increase index to 1.1 (10% interest)
uint256 newIndex = 1.1e27;
mockLendingPool.setNormalizedDebt(newIndex);
// Second mint at new index
vm.startPrank(address(mockLendingPool));
debtToken.mint(user, user, mintAmount, newIndex);
vm.stopPrank();
// Check final balances
uint256 normalBalance2 = debtToken.balanceOf(user);
uint256 scaledBalance2 = debtToken.scaledBalanceOf(user);
console.log("Final normal balance:", normalBalance2);
console.log("Final scaled balance:", scaledBalance2);
uint256 buggyTotal = 2210e18;
assertEq(normalBalance2, buggyTotal, "Balance should be 2210 (double scaling + extra interest)");
}
}

Impact

This bug has severe implications for the protocol:

  1. Users have higher debt than they should (5.2% increase in the example above)

  2. Protocol's accounting is incorrect

  3. Could lead to unfair liquidations due to inflated debt positions

  4. Affects the overall stability and reliability of the lending protocol

The likelihood is HIGH because it occurs on every mint operation.
The impact is HIGH because the error compounds with each mint operation.

Tools Used

  • Manual review

  • Foundry

Recommendations

Fix the DebtToken::mint() function:

  • Use super.balanceOf() instead of balanceOf() because balanceOf() returns the non-scaled balance:

- uint256 scaledBalance = balanceOf(onBehalfOf);
+ uint256 scaledBalance = super.balanceOf(onBehalfOf);
  • Remove the addition of balanceIncrease to amountToMint:

- uint256 amountToMint = amount + balanceIncrease;
+ uint256 amountToMint = amount;

In reality scaledBalance is used only for the balanceIncrease calculation that we don't need because the interest accrual is inherent within the index based solution the protocol is using.
But we still have to correct it because it is used in the return value of DebtToken::mint() to check if it is the firs mint.

return (scaledBalance == 0, amountToMint, totalSupply());

Appendix: Running Foundry Tests

The POC uses Foundry in a Hardhat project. To reproduce:

  • Install Foundry using their installation script:

curl -L https://foundry.paradigm.xyz | bash
  • Install the hardhat-foundry plugin:

npm i --save-dev @nomicfoundation/hardhat-foundry
  • Add require("@nomicfoundation/hardhat-foundry"); to the top of your hardhat.config.js file.

For detailed setup instructions, see Hardhat + Foundry Integration Guide.

Updates

Lead Judging Commences

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

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

inallhonesty Lead Judge 3 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.