<!-- canonical: https://0xsimao.com/findings/cap-cannot-repay-liquidate-paused -->

# Cannot repay or liquidate on paused asset

Medium · Sherlock · Stablecoin issuer · 10th July 2025

Finding M-4 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/180

---

### Summary

Although the protocol allows repayment on paused asset, it cannot be done because Lender contract tries an unnecessary (and prohibited due to pause) borrow from Vault contract.

### Root Cause

First, let's see why repayment on paused asset is an intended protocol design.

- In Lender contract: Asset's paused state [is checked on borrow](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/lendingPool/libraries/ValidationLogic.sol#L62), but __not__ checked [on liquidation](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/lendingPool/libraries/ValidationLogic.sol#L87-L96)
- In Vault contract: `whenNotPaused` modifier is applied to [`mint`](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/vault/libraries/VaultLogic.sol#L118) and [`borrow`](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/vault/libraries/VaultLogic.sol#L188) functions, but __not__ applied to [`burn`](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/vault/libraries/VaultLogic.sol#L138-L140) and [`repay`](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/vault/libraries/VaultLogic.sol#L202-L204) functions
- In `BorrowLogic` library, realized interests are [specially](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol#L243) [handled](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol#L262-L264) if the asset is paused.

So for the paused assets, the protocol wants to:

- Disallow minting or borrowing
- Allow repaying or liquidating
- Avoid/delay collecting interest

However, repayment on paused assets (and consequently liquidations) will not be processed because Lender contract unncessarily tries to borrow from Vault contract:

- Prior to repayment, lender contract [realizes restaker interest](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol#L104)
- Since the asset is paused, [`realizedInterest = 0`](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol#L262-L264)
- Since there is no realization, lender contract doesn't need to borrow from Vault contract to repay the interest. However, it tries to borrow 0 from Vault contract:
- Because the asset is paused, Vault contract will [revert with `AssetPaused` error](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/vault/libraries/VaultLogic.sol#L188)

[File: cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol](https://github.com/sherlock-audit/2025-07-cap/blob/2bd34fa369d36af8ecc377090d3292ea74ccc669/cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol#L201-L220)
```solidity
201:    function realizeRestakerInterest(ILender.LenderStorage storage $, address _agent, address _asset)
202:         public
203:         returns (uint256 realizedInterest)
204:     {
205:         ILender.ReserveData storage reserve = $.reservesData[_asset];
206:         uint256 unrealizedInterest;
207:         (realizedInterest, unrealizedInterest) = maxRestakerRealization($, _agent, _asset);
208:         reserve.lastRealizationTime[_agent] = block.timestamp;
209: 
210:         if (realizedInterest == 0 && unrealizedInterest == 0) return 0;
211: 
212:         reserve.debt += realizedInterest;
213:         reserve.unrealizedInterest[_agent] += unrealizedInterest;
214:         reserve.totalUnrealizedInterest += unrealizedInterest;
215: 
216:         IDebtToken(reserve.debtToken).mint(_agent, realizedInterest + unrealizedInterest);
217:@>       IVault(reserve.vault).borrow(_asset, realizedInterest, $.delegation);
218:         IDelegation($.delegation).distributeRewards(_agent, _asset);
219:         emit RealizeInterest(_asset, realizedInterest, $.delegation);
220:     }
```

### Internal Pre-conditions

An asset is paused

### External Pre-conditions

n/a

### Attack Path

n/a

### Impact

The protocol cannot get debt repaid from agents on paused assets.

### PoC

```solidity
pragma solidity ^0.8.28;

import { IOracle } from "../contracts/interfaces/IOracle.sol";
import { TestDeployer } from "./deploy/TestDeployer.sol";
import { console } from "forge-std/console.sol";

contract POC is TestDeployer {
    address user_agent;

    function setUp() public {
        _deployCapTestEnvironment();

        _initTestVaultLiquidity(usdVault);
        _initSymbioticVaultsLiquidity(env);

        user_agent = _getRandomAgent();

        vm.startPrank(env.symbiotic.users.vault_admin);
        _symbioticVaultDelegateToAgent(symbioticWethVault, env.symbiotic.networkAdapter, user_agent, 100e18);
        vm.stopPrank();
    }

    function test_submissionValidity() public {
        _setAssetOraclePrice(address(weth), 2000e8);
        vm.startPrank(user_agent);
        lender.borrow(address(usdc), 11000e6, user_agent);
        vm.stopPrank();

        vm.startPrank(env.users.access_control_admin);
        accessControl.grantAccess(lender.pauseAsset.selector, address(lender), env.users.vault_config_admin);
        vm.stopPrank();

        vm.startPrank(env.users.vault_config_admin);
        cUSD.pauseAsset(address(usdc));
        lender.pauseAsset(address(usdc), true);
        vm.stopPrank();

        vm.startPrank(user_agent);
        _timeTravel(90 days);
        usdc.approve(address(lender), 5000e6);

        vm.expectRevert(abi.encodeWithSignature("AssetPaused(address)", address(usdc)));
        lender.repay(address(usdc), 5000e6, user_agent);
        vm.stopPrank();
    }
}

```

### Mitigation

```diff
diff --git a/cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol b/cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol
index 3c2b60a..e36f043 100644
--- a/cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol
+++ b/cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol
@@ -214,8 +214,10 @@ library BorrowLogic {
         reserve.totalUnrealizedInterest += unrealizedInterest;
 
         IDebtToken(reserve.debtToken).mint(_agent, realizedInterest + unrealizedInterest);
-        IVault(reserve.vault).borrow(_asset, realizedInterest, $.delegation);
-        IDelegation($.delegation).distributeRewards(_agent, _asset);
+        if (realizedInterest > 0) {
+            IVault(reserve.vault).borrow(_asset, realizedInterest, $.delegation);
+            IDelegation($.delegation).distributeRewards(_agent, _asset);
+        }
         emit RealizeInterest(_asset, realizedInterest, $.delegation);
     }
 

```

After applying the patch, run the POC again to see if repayment on paused asset is possible.

---

Related findings:

- [repay(), liquidate() and liquidateWLp() receive shares as argument, which may revert if from approval to tx settled blocks have passed](https://0xsimao.com/findings/init-capital-ii-repay-liquidate-shares-approval): INIT Capital
