Summary
DebtToken::mint mints an amount of tokens equivalent to the borrowed amount plus any potential interest accumulated from previous borrow positions. Accumulated interest shouldn't be minted to the borrower's account;
Vulnerability Details
DebtToken, as well as RToken are interest-bearing tokens that represents a user's share in the underlying deposited/ borrowed assets.
These are rebasing tokens, which means they are growing in balance, not in value. The ratio at which they grow in balance is given by the pool's usageIndex and liquidityIndex respectively.
When an user borrows from LendingPool, DebtToken::mint is called and amount of underlying pool's asset is transferred to borrower:
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
...
(bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) = IDebtToken(reserve.reserveDebtTokenAddress).mint(msg.sender, msg.sender, amount, reserve.usageIndex);
IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
...
}
In DebtToken::mint the amountToMint is calculated as the sum of borrowed assets and the interest accumulated from the user's previous borrow positions.
The accumulated interest shouldn't be issued (minted) to user's balance; instead, it is already reflected in borrower's balance by the fact that scaledBalance is multiplied by the _reservePool).getNormalizedDebt() (debtIndex).
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);
bool isFirstMint = scaledBalance == 0;
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
@> balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
_userState[onBehalfOf].index = index.toUint128();
@> uint256 amountToMint = amount + balanceIncrease;
@> _mint(onBehalfOf, amountToMint.toUint128());
emit Transfer(address(0), onBehalfOf, amountToMint);
emit Mint(user, onBehalfOf, amountToMint, balanceIncrease, index);
return (scaledBalance == 0, amountToMint, totalSupply());
}
function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
@> return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}
Impact
More debt is issued to user's balance which will also accrue interest. Borrowers will have to repay more than principal borrowed and accumulated interest.
Tools Used
Recommendations
amount of assets borrowed should be passed to _mint function.
- _mint(onBehalfOf, amountToMint.toUint128());
+ _mint(onBehalfOf, amount.toUint128());