Summary
ReserveLibrary
tracks reserve.totalUsage
and reserve.totalLiquidity
totalLiquidity - amount of token assets, which were deposited, but not borrowed
totalUsage - amount of borrowed token assets
These two values are used to calculate utilizationRate, and base on this debt and income indexes are calculated.
More usage token in compare to liquidity = higher borrowing rates
Vulnerability Details
In all related function (borrow
, _repay
, finalizeLiquidation
) reserve.totalUsage should be assigned with a new amount of borrowed tokens, but instead it is assigned with totalSupply minus accrued debt, because both mint
and burn
function are using totalSupply which is dividing totalSupply by normalized debt.
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);
...
reserve.totalUsage = newTotalSupply;
}
function _repay(uint256 amount, address onBehalfOf) internal {
...
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
...
reserve.totalUsage = newTotalSupply;
}
function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
...
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) = IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
...
reserve.totalUsage = newTotalSupply;
}
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
...
if (amount == 0) {
return (false, 0, totalSupply());
}
...
return (scaledBalance == 0, amountToMint, totalSupply());
}
function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
if (amount == 0) {
return (0, totalSupply(), 0, 0);
}
...
return (amount, totalSupply(), amountScaled, balanceIncrease);
}
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
}
Impact
totalUsage
will be always smaller than it should be, so as mentioned in the beginning. Borrowing and liquidity rates will be lower than expected, because utilization utilizationRate will be smaller
Tools Used
Manual Review
Recommendations
Use amount of tokens not divided by debt index.