<!-- canonical: https://0xsimao.com/findings/yieldoor-contradiction-high-leverage-liquidation -->

# Contradiction between high-leverage and liquidation check of position

Crit/High · Sherlock · CLMM · 24th February 2025

Finding H-5 of the Yieldoor competition.

- Protocol: https://audits.sherlock.xyz/contests/791
- Report: /reports/yieldoor
- Codebase: https://github.com/0xsimao/2025-02-yieldoor/tree/b5a0f779dce4236b02665606adb610099451a51a
- Source: https://github.com/sherlock-audit/2025-02-yieldoor-judging/issues/357

---

### Summary

Market can allow leverage higher than 2x, and this is design choice.
But the check `owedAmount > totalDenom` in `isLiquidateable` function doesn't allow leverage higher than 2x.

### Root Cause

[`positionLeverage`](https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/Leverager.sol#L464-L467) is calculated and checked in `_checkWithinlimits` function when open leverage.

```solidity
    function _checkWithinlimits(Position memory up) internal {
        ...
@>      uint256 positionLeverage = (up.initCollateralValue + up.borrowedAmount) * 1e18 / up.initCollateralValue;
        require(positionLeverage <= vp.maxTimesLeverage && positionLeverage <= maxLevTimes, "too high x leverage");
        ...
    }
```

Opening leverage also checks whether the position is `liquidateable`.
[`isLiquidateable`](https://github.com/sherlock-audit/2025-02-yieldoor/blob/main/yieldoor/src/Leverager.sol#L411) checks if `owedAmount > totalDenom` and returns `true` (indicating liquidation).
```solidity
    function isLiquidateable(uint256 _id) public view returns (bool liquidateable) {
        ...

        uint256 totalValueUSD = _calculateTokenValues(pos.token0, pos.token1, userBal0, userBal1, price);
        uint256 bPrice = IPriceFeed(pricefeed).getPrice(pos.denomination);
        uint256 totalDenom = totalValueUSD * (10 ** ERC20(pos.denomination).decimals()) / bPrice;

        uint256 bIndex = ILendingPool(lendingPool).getCurrentBorrowingIndex(pos.denomination);
        uint256 owedAmount = pos.borrowedAmount * bIndex / pos.borrowedIndex;

        /// here we make a calculation what would be the necessary collateral
        /// if we had the same borrowed amount, but at max leverage. Check docs for better explanation why.
        uint256 base = owedAmount * 1e18 / (vp.maxTimesLeverage - 1e18);
        base = base < pos.initCollateralValue ? base : pos.initCollateralValue;

@>      if (owedAmount > totalDenom || totalDenom - owedAmount < vp.minCollateralPct * base / 1e18) return true;
        else return false;
    }
```

When open leverage, `owedAmount` is equal to `borrowedAmount` and `totalDenom` is equal to `initCollateralValue`.
To be not liquidateable, `owedAmount <= totalDenom` should be satisfied, and this means that `positionLeverage` is always smaller than 2x.

Root cause is **contradiction between high-leverage and liquidation check of position**.
This means that the define of  `positionLeverage` could be incorrect or liquidation check could be incorrect.

### Internal Pre-conditions

No response

### External Pre-conditions

No response

### Attack Path

No response

### Impact

Market doesn't allow leverage higher than 2x

### PoC

_No response_

### Mitigation

Update the `positionLeverage` check logic or  `isLiquidateable` function logic.
