Summary
The LendingPool
keeps an internal representation of the user scaled debt balance, which is updated during borrows and repays.
The issue is that that value forgets to factor in the debt increase due to the debt index.
Vulnerability details
In DebtToken::mint
, the user debt balance is increased by the new amount borrowed + the increase due to the borrowing rate (index):
File: contracts/core/tokens/DebtToken.sol
136: function mint(
137: address user,
138: address onBehalfOf,
139: uint256 amount,
140: uint256 index
...:
...:
...:
153: uint256 balanceIncrease = 0;
154: if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
155: balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index); <@(1) compute increase in debt due to index
156: }
157:
158: _userState[onBehalfOf].index = index.toUint128();
159:
160: uint256 amountToMint = amount + balanceIncrease; <@(2) add the balance increase to the debt to mint
161:
162: _mint(onBehalfOf, amountToMint.toUint128()); <@(3) mint it
163:
164: emit Transfer(address(0), onBehalfOf, amountToMint);
165: emit Mint(user, onBehalfOf, amountToMint, balanceIncrease, index);
166:
167: return (scaledBalance == 0, amountToMint, totalSupply()); <@(4) returns `amountToMint`
168: }
But then in LendingPool::borrow
, the userScaledDebtBalance
is only increased by scaledAmount
which do not contain the debt increase due to the borrow index:
File: contracts/core/pools/LendingPool/LendingPool.sol
325: function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
...:
...:
...:
347:
348:
349: uint256 scaledAmount = amount.rayDiv(reserve.usageIndex);
350:
351:
352:
353: (bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) = IDebtToken(reserve.reserveDebtTokenAddress).mint(msg.sender, msg.sender, amount, reserve.usageIndex);
354:
355:
356: IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
357:
358: user.scaledDebtBalance += scaledAmount; <@(2) increase user debt by borrowed amount rather than `amountMinted`
Impact
Wrong user debt value stored in LedingPool
Recommended Mitigation Steps
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
//* ----------------- some code ----------------- *//
// Mint DebtTokens to the user (scaled amount)
(bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) = IDebtToken(reserve.reserveDebtTokenAddress).mint(msg.sender, msg.sender, amount, reserve.usageIndex);
// Transfer borrowed amount to user
IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
+ uint256 scaledAmountMinted = amountMinted.rayDiv(reserve.usageIndex)
- user.scaledDebtBalance += scaledAmount;
+ user.scaledDebtBalance += scaledAmountMinted;