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

Lenders can force borrowers to compound their owed interest, resulting in excess fees

Summary

When a borrower creates a loan, they are accepting a specific interestRate per year, which they are required to pay. An issue arises due to the giveLoan function which allows a lender of a loan to 'give a loan' which is already using their pool back to their pool again. When this happens, the debt base for the user increases, meaning that when this happens the user will need to pay the same interest rate on a larger debt base. This effectively means the lender is able to force the borrower to compound their interest payments, ultimately resulting in the borrower owing significantly more in interest than what is specified by interestRate. Effectively, the lender can force the borrower to pay more.

Note: technically the lender will need to increase their pool's interestRate by 1 wei and decrease their pool's auctionLength by 1 wei each time, but this is trivial.

Vulnerability Details

In the giveLoan function, there is nothing stopping the lender from giving a loan back to themselves. If we step through the function, we see that this causes the following state changes:

function giveLoan(
uint256[] calldata loanIds,
bytes32[] calldata poolIds
) external {
for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
bytes32 poolId = poolIds[i];
// get the loan info
Loan memory loan = loans[loanId];
...
// calculate the interest
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
uint256 totalDebt = loan.debt + lenderInterest + protocolInterest;
...
// update the loan with the new info
loans[loanId].lender = pool.lender;
loans[loanId].interestRate = pool.interestRate; // @ interest rate stays the same
loans[loanId].startTimestamp = block.timestamp;
loans[loanId].auctionStartTimestamp = type(uint256).max;
loans[loanId].debt = totalDebt; // @ debt base increases
...
}
}

As I have outlined with a comment, the debt base for the user changes on this line: loans[loanId].debt = totalDebt;, where totalDebt = loan.debt + lenderInterest + protocolInterest;. The debt base effectively increases by the old debt + total interest (including protocol interest).

Let's now see how much more in interest a borrower will need to pay to the lender if the lender is malicious and calls giveLoan once a day for an entire year (in this example the interest rate is 50%, and the lenderFee is 10%):

  • normal interest: (1+(0.5*0.9)-1) = 0.45 or 45% return for the lender

  • compounded rate: (((1 + 0.5/365)**365)-1)*0.9 = 0.583 or >58% return for the lender

As shown, the lender in this case is able to force the borrower to pay over 13% more than the amount they specified for the entire year. This results directly in losses for the borrower.

Impact

Borrower is forced to pay excess fees, meaning a directly loss for them.

Tools Used

Manual review

Recommendations

Consider either putting a timelock on how often a lender can call giveLoan, or prevent giveLoan from being able to be used to transfer a loan from the same pool to itself.

Support

FAQs

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