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());