sherlock-audit / 2023-12-arcadia-judging

19 stars 15 forks source link

0xRich_forEver - M-1: Liquidation Reverts Due to Zero Debt Token Mint #163

Closed sherlock-admin2 closed 9 months ago

sherlock-admin2 commented 9 months ago

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 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.

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 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.

sherlock-admin2 commented 9 months ago

1 comment(s) were left on this issue during the judging contest.

takarez commented:

invalid

nevillehuang commented 9 months ago

Invalid, no explicit numerical example/PoC required by sherlock to support issue