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
function mint(...) {
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;
_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(...) {
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;
}
if(amount > userBalance){
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;
_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
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.