-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Sponsor ConfirmedThe sponsor acknowledged this issue is validThe sponsor acknowledged this issue is validWill FixThe sponsor confirmed this issue will be fixedThe sponsor confirmed this issue will be fixed
Description
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.
- In Lender contract: Asset's paused state is checked on borrow, but not checked on liquidation
- In Vault contract:
whenNotPausedmodifier is applied tomintandborrowfunctions, but not applied toburnandrepayfunctions - In
BorrowLogiclibrary, realized interests are specially handled 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
- Since the asset is paused,
realizedInterest = 0 - 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
AssetPausederror
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
Labels
Sponsor ConfirmedThe sponsor acknowledged this issue is validThe sponsor acknowledged this issue is validWill FixThe sponsor confirmed this issue will be fixedThe sponsor confirmed this issue will be fixed