Core Contracts

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

Critical Vulnerabilities in DebtToken's Mint and Burn Implementation

Summary

Several critical vulnerabilities have been identified in the DebtToken contract's mint and burn functions. These issues stem from incorrect interest calculations, inconsistent scaling operations, and improper balance management. The vulnerabilities could lead to significant economic implications for the lending protocol and its users.

Vulnerability Details

1. Incorrect Interest Accrual Calculation

// In DebtToken.sol
function mint(...) {
// ... other code ...
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
uint256 amountToMint = amount + balanceIncrease; // @audit-critical incorrect calculation
_mint(onBehalfOf, amountToMint.toUint128());
}

The mint function incorrectly adds the raw balanceIncrease to the unscaled amount. This fundamentally breaks the interest accrual mechanism as it:

  • Mixes scaled and unscaled values

  • Results in excessive debt token minting

  • Disrupts the protocol's interest accounting

2. Inconsistent Index Usage in Burn Function

function burn(...) {
// ... other code ...
uint256 balanceIncrease = 0;
if (_userState[from].index != 0 && _userState[from].index < index) {
uint256 borrowIndex = ILendingPool(_reservePool).getNormalizedDebt();
balanceIncrease = userBalance.rayMul(borrowIndex) - userBalance.rayMul(_userState[from].index);
amount = amount; // @audit-critical redundant and suspicious
}
if(amount > userBalance){ // @audit-critical wrong comparison
amount = userBalance;
}
uint256 amountScaled = amount.rayDiv(index);
}

Critical issues in the burn function include:

  • Using a different index (borrowIndex) than the mint function

  • Redundant self-assignment of amount

  • Comparing unscaled amounts before applying the index

3. Improper State Updates

function mint(...) {
uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0; // @audit-high unreliable check
// ... interest calculation ...
_userState[onBehalfOf].index = index.toUint128();
}

The contract's state management has several flaws:

  • Unreliable first mint detection

  • Potential integer overflow in index conversion

  • Missing validation for index values

Impact

1. Economic Implications

  • Interest Calculation Errors: Users may accrue incorrect amounts of debt

  • Token Supply Manipulation: Excessive minting of debt tokens

  • Protocol Insolvency Risk: Incorrect debt accounting could lead to protocol-wide issues

2. Technical Risks

  • State Inconsistency: Mismatched scaled/unscaled values

  • Precision Loss: Potential truncation in numerical operations

  • Integration Failures: Dependent contracts may malfunction

3. User Impact

  • Incorrect Debt Positions: Users may have wrong debt amounts

  • Failed Repayments: Burns might fail or process incorrectly

  • Economic Losses: Users might pay incorrect interest amounts

Tools Used

  • Manual code review

Recommendations

1. Implement Correct Interest Accrual

function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
if (user == address(0) || onBehalfOf == address(0)) revert InvalidAddress();
if (amount == 0) return (false, 0, totalSupply());
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
uint256 scaledBalance = balanceOf(onBehalfOf);
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index - _userState[onBehalfOf].index);
}
uint256 scaledTotal = amountScaled + balanceIncrease.rayDiv(index);
_mint(onBehalfOf, scaledTotal.toUint128());
_userState[onBehalfOf].index = index.toUint128();
emit ScaledMint(user, onBehalfOf, scaledTotal, balanceIncrease, index);
return (scaledBalance == 0, scaledTotal, totalSupply());
}

2. Standardize Burn Operations

function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
if (from == address(0)) revert InvalidAddress();
if (amount == 0) return (0, totalSupply(), 0, 0);
uint256 amountScaled = amount.rayDiv(index);
uint256 scaledBalance = balanceOf(from);
if (amountScaled > scaledBalance) {
amountScaled = scaledBalance;
amount = amountScaled.rayMul(index);
}
uint256 balanceIncrease = 0;
if (_userState[from].index != 0 && _userState[from].index < index) {
balanceIncrease = scaledBalance.rayMul(index - _userState[from].index);
}
_userState[from].index = index.toUint128();
_burn(from, amountScaled.toUint128());
emit ScaledBurn(from, amountScaled, balanceIncrease, index);
return (amountScaled, totalSupply(), amount, balanceIncrease);
}

3. Add Safety Mechanisms

modifier validIndex(uint256 index) {
require(index >= WadRayMath.RAY, "DebtToken: invalid index");
_;
}
modifier validAmount(uint256 amount) {
require(amount > 0, "DebtToken: invalid amount");
require(amount <= type(uint128).max, "DebtToken: amount overflow");
_;
}

4. Implement Proper Events

event ScaledMint(
address indexed user,
address indexed onBehalfOf,
uint256 scaledAmount,
uint256 balanceIncrease,
uint256 index
);
event ScaledBurn(
address indexed user,
uint256 scaledAmount,
uint256 balanceIncrease,
uint256 index
);

These recommendations should be implemented along with comprehensive testing and formal verification to ensure the security and reliability of the debt token implementation.

Updates

Lead Judging Commences

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

DebtToken::burn calculates balanceIncrease (interest) but never applies it, allowing borrowers to repay loans without paying accrued interest

Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.

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

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

DebtToken::burn calculates balanceIncrease (interest) but never applies it, allowing borrowers to repay loans without paying accrued interest

Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.

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.

Give us feedback!