<!-- canonical: https://0xsimao.com/findings/cap-view-liquidatable-bonus-agent -->

# `ViewLogic::maxLiquidatable()` doesn't take the bonus into account, making the agent liquidatable again

Medium · Sherlock · Stablecoin issuer · 10th July 2025

Finding M-10 of the Cap competition.

- Protocol: https://audits.sherlock.xyz/contests/990
- Codebase: https://github.com/0xsimao/2025-07-cap/tree/2bd34fa369d36af8ecc377090d3292ea74ccc669
- Source: https://github.com/sherlock-audit/2025-07-cap-judging/issues/638

---

### Summary

[ViewLogic::maxLiquidatable()](https://github.com/sherlock-audit/2025-07-cap/blob/main/cap-contracts/contracts/lendingPool/libraries/ViewLogic.sol#L91-L93) computes the maximum debt to liquidate such that the resulting target health is exactly the desired:
```solidity
maxLiquidatableAmount = (($.targetHealth * totalDebt) - (totalDelegation * liquidationThreshold)) * decPow
    / (($.targetHealth - liquidationThreshold) * assetPrice);
```
However, this is inaccurate due to the [bonus](https://github.com/sherlock-audit/2025-07-cap/blob/main/cap-contracts/contracts/lendingPool/libraries/ViewLogic.sol#L192), which affects the final health as it slashes more than the debt is repays.

Thus, it will trigger more than 1 liquidations in certain conditions and lead to heavy losses for the agent.

### Root Cause

In `ViewLogic.sol:91`, it doesn't include the bonus.

### Internal Pre-conditions

None

### External Pre-conditions

None

### Attack Path

1. Agent is liquidated twice in a row due to the `ViewLogic::maxLiquidatable()` calculation not taking into account the bonus.

### Impact

Agent suffers more losses than supposed.

### PoC

_No response_

### Mitigation

Include the bonus in the debt to repay so the health factor equals the target.

---

Related findings:

- [DoSed liquidations as `PrizeVault::liquidatableBalanceOf()` does not take into account the `mintLimit` when the token out is the asset](https://0xsimao.com/findings/pooltogether-the-prize-layer-for-defi-liquidations-prize-liquidatable-mint): PoolTogether Prize Layer
- [Price formula in `TpdaLiquidationPair._computePrice()` does not account for a jump in liquidatable balance](https://0xsimao.com/findings/pooltogether-the-prize-layer-for-defi-price-tpda-liquidation-compute): PoolTogether Prize Layer
- [RebalanceLogic::rebalanceCallback() doesn't update lastTotalAssets when rebalancing from morpho to morpho](https://0xsimao.com/findings/yieldoor-ii-rebalance-callback-rebalancing-morpho): Yieldoor LoopedVault
- [ERC4626YieldBackend.withdrawSurplus() uses convertToAssets() which doesn't account for fees](https://0xsimao.com/findings/superfluid-erc4626-yield-withdraw-fees): Superfluid Yield Backends
