Skip to content

Big Sepia Puma - Cannot repay or liquidate on paused asset #180

@sherlock-admin3

Description

@sherlock-admin3

Big Sepia Puma

Medium

Cannot repay or liquidate on paused asset

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.

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:

File: cap-contracts/contracts/lendingPool/libraries/BorrowLogic.sol

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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Sponsor ConfirmedThe sponsor acknowledged this issue is validWill FixThe sponsor confirmed this issue will be fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions