hats-finance / Tapioca--Lending-Engine--0x5bee198f5b060eecd86b299fdbea6b0c07c728dd

Other
0 stars 0 forks source link

repayment and liquidation will not work when `USDO` is paused #23

Open hats-bug-reporter[bot] opened 1 month ago

hats-bug-reporter[bot] commented 1 month ago

Github username: @Audinarey Twitter username: audinarey Submission hash (on-chain): 0x54044826f2e0e9a845127b83173919d5a9ba05a2c9803e1185a20835aae8b41e Severity: high

Description: Description\ Users call [BBBorrow::repay(...)]() to repay their loans. Also Liquidators call BBLiquidation::liquidateBadDebt(...) to liquidate bad debt and BBLiquidation::_liquidateUser(...). However, due to the implementation of the USDO::burn(...) function, users call to repay will revert when USDO is paused

The repay call flow on a high level is

-> BBBorrow::repay(...) --> BBLendingCommon::_repay(...) .... ---> USDO::burn(...)

The _liquidateUser and liquidateBadDebt call flow on a high level is

-> BBBorrow::liquidateBadDebt(...) / BBBorrow::_liquidateUser(...) .... ---> USDO::burn(...)

As shown below. USDO::burn(...) will revert when USDO is paused

File: BBLendingCommon.sol
108:     function _repay(address from, address to, uint256 part, bool checkAllowance) internal returns (uint256 amount) {
109:         if (part > userBorrowPart[to]) {
110:             part = userBorrowPart[to];
...
124: 
125:         // @dev sub `part` of totalBorrow
126:         (totalBorrow, amount) = totalBorrow.sub(part, true);
127:         userBorrowPart[to] -= part;
128: 
129:         // @dev amount includes the opening & accrued fees
130:         yieldBox.withdraw(assetId, from, address(this), amount, 0);
131: 
132:         // @dev burn USDO
133:  @>     IUsdo(address(asset)).burn(address(this), amount);
134. 
135. 
. 

File: BBLiquidation.sol
54:     function liquidateBadDebt(
55:         address user,
...
61:     ) external onlyOwner {
62:         _tryUpdateOracleRate();
63: 
...
94: 
95:         // burn debt amount from `from`
96:  @>     IUsdo(address(asset)).burn(from, borrowAmount);

261:     function _liquidateUser(
262:         address user,
...
268:     ) private {
269:         uint256 callerReward = _getCallerReward(user, _exchangeRate);
270: 
...
284: 
285:  @>     IUsdo(address(asset)).burn(address(this), borrowAmount);

File: Usdo.sol

260: @>  function burn(address _from, uint256 _amount) external whenNotPaused {
261:         if (!allowedBurner[_getChainId()][msg.sender]) {
262:             revert Usdo_NotAuthorized();
263:         }
264:         _burn(_from, _amount);
265:     }

Attack Scenario\

Attachments

  1. Impact

    This can lead to

    • cascade of bad loans in the protocol
    • loss of funds for well meaning users who are prevented from repaying their loans due to system desing
  2. Revised Code File (Optional)

    • Remove the whenNotPaused modifier from the USDO::burn(...) function as shown below
File: Usdo.sol
255:     
...
260:     function burn(address _from, uint256 _amount) external {
261:         if (!allowedBurner[_getChainId()][msg.sender]) {
262:             revert Usdo_NotAuthorized();
263:         }
264:         _burn(_from, _amount);
265:     }
cryptotechmaker commented 1 month ago

It's intended