20,000 USDC
View results
Submission Details
Severity: medium
Valid

Setting borrower fees to 0 permits the borrow functionality to be completely DoS

Summary

If the protocol decides for whatever reason to set the protocol borrowing fee to 0 (example to encourage users to use the protocol), then a malicious actor can DoS all borrow operations by:

  1. front-running any loan operation, fully borrowing all available pool tokens

  2. normal user transaction would revert as there are no more tokens to borrow in the pool

  3. malicious actor would also back-run the repaying of all his debt to the pool

By doing this, borrowing from any pool can be blocked. Also, the cost required to perform this attack is very low (no interest/fee to be paid, only gas cost required) and the attack results in the DoS of one of the most crucial feature of the protocol, i.e. borrow.

Vulnerability Details

When initiation a borrow, a borrower fee is deducted from the overall debt to be encored and sent to the protocol.

// calculate the fees
uint256 fees = (debt * borrowerFee) / 10000;
// transfer fees
IERC20(loan.loanToken).transfer(feeReceiver, fees);

Also, when repaying a debt, the a lender fee is paid plus an extra interest to the pool:

// transfer the loan tokens from the borrower to the pool
IERC20(loan.loanToken).transferFrom(
msg.sender,
address(this),
loan.debt + lenderInterest
);
// transfer the protocol fee to the fee receiver
IERC20(loan.loanToken).transferFrom(
msg.sender,
feeReceiver,
protocolInterest
);

where the value for lenderInterest and protocolInterest is calculate using the _calculateInterest function as such:

function _calculateInterest(
Loan memory l
) internal view returns (uint256 interest, uint256 fees) {
uint256 timeElapsed = block.timestamp - l.startTimestamp;
interest = (l.interestRate * l.debt * timeElapsed) / 10000 / 365 days;
fees = (lenderFee * interest) / 10000;
interest -= fees;
}

From the above implementations we see that:

  • borrowing fees are 0 if the variable borrowerFee is 0

    fees = (debt * borrowerFee) / 10000;
    = (debt * 0) / 10000;
    = 0;
  • if done in the same transaction, when repaying, fees are calculated based on timeElapsed which is 0 resulting in both lenderInterest and protocolInterest being 0:

    uint256 timeElapsed = block.timestamp - l.startTimestamp;
    = block.timestamp - block.timestamp
    = 0
    interest = (l.interestRate * l.debt * timeElapsed) / 10000 / 365 days;
    = (l.interestRate * l.debt * 0) / 10000 / 365 days;
    = 0;
    fees = (lenderFee * interest) / 10000;
    = (lenderFee * 0) / 10000;
    = 0;

as such, no fees are deducted in a complete borrow + repay cycle.

This behavior can be abused in the following way, a theoretical POC:

  • eve (protocol owner) just launched the protocol and wants to encourage adoption so he drops the borrowerFee to 0

  • alice wants to borrow using the protocol so she initiates a transaction

  • bob, who wants eve's project to fail, front-runs alice's borrow with his own and borrows all available pool balance

  • alice's borrow will now revert due to an underflow on updating pool balance (because there is no more available balance to be used)

  • bob also launched a back-run call that repays his loan completely

Impact

Protocol borrow operation is completely blocked under certain conditions.

Tools Used

Manual analysis and similar issues in other audits.

Recommend Mitigation

Modify the _calculateInterest to attribute a default protocol fee as to make this attack economically unsustainable.

The simplest alternative is to not allow the setting of borrower fees to 0. However this brings some limitations, as protocol may want to weaver any fees at one point but could not because of this situation.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.