Skip to content
This repository has been archived by the owner on Nov 3, 2024. It is now read-only.

Trumpero - clearBadDebt function does not accrue earnings from each maturity. #162

Closed
sherlock-admin4 opened this issue May 4, 2024 · 0 comments
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue

Comments

@sherlock-admin4
Copy link

sherlock-admin4 commented May 4, 2024

Trumpero

medium

clearBadDebt function does not accrue earnings from each maturity.

Summary

During the operation of a maturity, Pool.accrueEarnings() is triggered to transfer the backupEarnings from unassignedEarnings of the maturity to the floating pool, which is dripped over time. However, the clearBadDebt() function does not trigger Pool.accrueEarnings() to collect earnings for the backup supply. This results in a loss of the remaining earnings if a maturity ends with a clearBadDebt() call.

Vulnerability Detail

In the FixedLib library, the accrueEarnings() function is used to collect backup earnings from a specific maturity (fixed pool) to the floating pool. These earnings are dripped from the unassignedEarnings of the maturity over time. Pool.accrueEarnings() is called whenever an operation of the maturity (such as deposit, withdrawal, borrowing, or repayment) occurs
https://github.com/sherlock-audit/2024-04-interest-rate-model/blob/main/protocol/contracts/utils/FixedLib.sol#L84-L99

Market.clearBadDebt() is a function called by Auditor.handleBadDebt() to clear all the debt of a borrower when this borrower has no collateral. It clears all debt from each maturity that this account has borrowed from. However, it does not trigger accrueEarnings() for each fixed pool.

function clearBadDebt(address borrower) external {
...
while (packedMaturities != 0) {
  if (packedMaturities & 1 != 0) {
    FixedLib.Position storage position = fixedBorrowPositions[maturity][borrower];
    uint256 badDebt = position.principal + position.fee;
    if (accumulator >= badDebt) {
      RewardsController memRewardsController = rewardsController;
      if (address(memRewardsController) != address(0)) memRewardsController.handleBorrow(borrower);
      accumulator -= badDebt;
      totalBadDebt += badDebt;
      floatingBackupBorrowed -= fixedPools[maturity].repay(position.principal);
      delete fixedBorrowPositions[maturity][borrower];
      account.fixedBorrows = account.fixedBorrows.clearMaturity(maturity);

      emit RepayAtMaturity(maturity, msg.sender, borrower, badDebt, badDebt);
    }
  }
  packedMaturities >>= 1;
  maturity += FixedLib.INTERVAL;
}

An issue will occur when a maturity ends and clearBadDebt() is the last operation of this maturity, but the unassignedEarnings() of that maturity have not been fully accrued. This means that although the earnings have been completely dripped because the maturity has ended, they will never be collected into the floating pool because clearBadDebt() does not trigger Pool.accrueEarnings() for that maturity.
Therefore, in this case, floating assets will incur a loss of earnings from that maturity, since the remaining unassignedEarnings of this maturity will still be greater than 0 but will never be accrued. There is no mitigation to claim it since there is no debt remaining in this maturity.

Here is the test function for a PoC:

function testClearBadDebtLastOperation() external {
    //setup market
    marketWETH.setMaxFuturePools(3);
    irm = MockInterestRateModel(address(new MockBorrowRate(0.1e18)));
    market.setInterestRateModel(InterestRateModel(address(irm)));
    marketWETH.setInterestRateModel(InterestRateModel(address(irm)));

    //deposit collateral and borrow at high price
    daiPriceFeed.setPrice(50_000_000_000_000e18);
    market.deposit(0.0000000001 ether, address(this));
    auditor.enterMarket(market);

    marketWETH.deposit(1_000 ether, BOB);
    marketWETH.borrowAtMaturity(FixedLib.INTERVAL, 10 ether, type(uint256).max, address(this), address(this));
    (, , uint256 accruedEarnings, ) = marketWETH.fixedPools(FixedLib.INTERVAL);
    console.log(accruedEarnings);

    //maturity end
    vm.warp(FixedLib.INTERVAL + 1);

    //collateral value downs to 0 and bad debt happens
    daiPriceFeed.setPrice(1);
    auditor.handleBadDebt(address(this));
    (, , accruedEarnings, ) = marketWETH.fixedPools(FixedLib.INTERVAL);
    console.log(accruedEarnings);
    assertGt(accruedEarnings, 0);
    }

Please put this function into Market.t.sol test file and run the command:

forge test -vv --match-test testClearBadDebtLastOperation

Impact

When clearBadDebt() is the last operation of a maturity after it ends, the remaining unassignedEarnings in this maturity will never be accrued. Therefore, the floating pool will lose significant funds accrued from maturities

Code Snippet

https://github.com/sherlock-audit/2024-04-interest-rate-model/blob/main/protocol/contracts/Market.sol#L629-L644

Tool used

Manual Review

Recommendation

The clearBadDebt() function should trigger Pool.accrueEarnings() for each maturity when clearing debt as follows:

..
while (packedMaturities != 0) {
    if (packedMaturities & 1 != 0) {
        FixedLib.Pool storage pool = fixedPools[maturity];
        floatingAssets += pool.accrueEarnings(maturity);
        
        ...
    }
    ...
}
      

Duplicate of #130

@github-actions github-actions bot closed this as completed May 8, 2024
@github-actions github-actions bot added Medium A valid Medium severity issue Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label labels May 8, 2024
@sherlock-admin3 sherlock-admin3 changed the title Tiny Mulberry Tapir - clearBadDebt function does not accrue earnings from each maturity. Trumpero - clearBadDebt function does not accrue earnings from each maturity. May 17, 2024
@sherlock-admin3 sherlock-admin3 added the Reward A payout will be made for this issue label May 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue
Projects
None yet
Development

No branches or pull requests

2 participants