M-1: Liquidation Reverts Due to Zero Debt Token Mint
Summary
The lending pool liquidation process, where the minting of zero debt tokens during a borrow operation can cause the liquidation to revert erroneously.
Vulnerability Detail
A vulnerability exists in the lending pool's borrow function, which indirectly leads to the liquidation process reverting due to the minting of zero debt tokens. This occurs when the LendingPool.sol::borrow function calls the DebtToken.sol::_deposit,which in turn calls the previewDeposit function defined in ERC4626.sol. The previewDepositfunction uses convertToShare to determine the number of shares (debt tokens) to mint for the borrowed amount. However, due to the rounding down behavior of convertToShares, a small borrowed amount can result in zero shares being minted.
When a borrower with a small debt amount attempts to initiate liquidation, the LendingPool.sol::startLiquidation function checks the borrower's debt using maxWithdraw, which relies on the number of debt tokens they hold. If the borrower received zero debt tokens due to the rounding down issue, maxWithdrawwill return zero, indicating no debt. As a result, the LendingPool.sol::startLiquidationfunction reverts:
if (startDebt == 0) revert LendingPoolErrors.IsNotAnAccountWithDebt();
even though the borrower has an outstanding debt. This prevents the liquidation process from proceeding and leaves the debt unresolved, potentially leading to bad debt in the lending pool.
Code Snippet
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Total supply of shares in the pool.
// If there are no shares, return the asset amount directly.
// Otherwise, calculate the number of shares by rounding down the division.
//@audit-> This can result in zero shares
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
POC
A borrower takes out a small loan from the lending pool, triggering the borrow function.
The borrow function calls _deposit to mint debt tokens corresponding to the borrowed amount.
_deposit calls previewDeposit from ERC4626.sol, which uses convertToShares to calculate the number of shares to mint.
Due to the small loan amount and rounding down in convertToShares, the borrower receives zero debt tokens.
The borrower later becomes undercollateralized and attempts to initiate liquidation by calling startLiquidation.
startLiquidation calls maxWithdraw, which returns zero since the borrower has zero debt tokens.
The liquidation process reverts with LendingPoolErrors.IsNotAnAccountWithDebt, as it incorrectly assumes the borrower has no debt.
The lending pool is left with an unresolved debt, as the liquidation cannot proceed.
Tool used
Manual Review
Recommendation
Implement a minimum borrow amount that ensures at least one debt token is minted.
Use a mulDivUp function instead of mulDivDown in convertToShares to round up the number of shares.
0xRich_forEver
medium
M-1: Liquidation Reverts Due to Zero Debt Token Mint
Summary
The lending pool liquidation process, where the minting of zero debt tokens during a borrow operation can cause the liquidation to revert erroneously.
Vulnerability Detail
A vulnerability exists in the lending pool's borrow function, which indirectly leads to the liquidation process reverting due to the minting of zero debt tokens. This occurs when the
LendingPool.sol::borrow
function calls theDebtToken.sol::_deposit
,which in turn calls thepreviewDeposit
function defined inERC4626.sol
. ThepreviewDeposit
function usesconvertToShare
to determine the number of shares (debt tokens) to mint for the borrowed amount. However, due to the rounding down behavior ofconvertToShares
, a small borrowed amount can result in zero shares being minted.https://github.com/transmissions11/solmate/blob/e0e9ff05d8aa5c7c48465511f85a6efdf5d5c30d/src/mixins/ERC4626.sol#L127
Impact
https://github.com/sherlock-audit/2023-12-arcadia/blob/main/lending-v2/src/LendingPool.sol#L872
When a borrower with a small debt amount attempts to initiate liquidation, the
LendingPool.sol::startLiquidation
function checks the borrower's debt usingmaxWithdraw
, which relies on the number of debt tokens they hold. If the borrower received zero debt tokens due to the rounding down issue,maxWithdraw
will return zero, indicating no debt. As a result, theLendingPool.sol::startLiquidation
function reverts:even though the borrower has an outstanding debt. This prevents the liquidation process from proceeding and leaves the debt unresolved, potentially leading to bad debt in the lending pool.
Code Snippet
POC
A borrower takes out a small loan from the lending pool, triggering the borrow function. The borrow function calls
_deposit
to mint debt tokens corresponding to the borrowed amount._deposit
callspreviewDeposit
fromERC4626.sol
, which usesconvertToShares
to calculate the number of shares to mint. Due to the small loan amount and rounding down inconvertToShares
, the borrower receives zero debt tokens. The borrower later becomes undercollateralized and attempts to initiate liquidation by callingstartLiquidation
.startLiquidation
callsmaxWithdraw
, which returns zero since the borrower has zero debt tokens. The liquidation process reverts with LendingPoolErrors.IsNotAnAccountWithDebt, as it incorrectly assumes the borrower has no debt. The lending pool is left with an unresolved debt, as the liquidation cannot proceed.Tool used
Manual Review
Recommendation
Implement a minimum borrow amount that ensures at least one debt token is minted. Use a
mulDivUp
function instead ofmulDivDown
inconvertToShares
to round up the number of shares.