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

Borrower pays more interest that he should be

Summary

Math done in _calculateInterest function calculates the interest based on loan debt amount not on the acutal amount of tokens received by borrower.

Vulnerability Details

Borrower calls borrow with the desired parameters (poolId, debt, collateral). Function sets debt as the debt value provided by the borrrower in the input (debt: debt).

Loan memory loan = Loan({
lender: pool.lender,
borrower: msg.sender,
loanToken: pool.loanToken,
collateralToken: pool.collateralToken,
debt: debt,
collateral: collateral,
interestRate: pool.interestRate,
startTimestamp: block.timestamp,
auctionStartTimestamp: type(uint256).max,
auctionLength: pool.auctionLength
});

The function calculates the fee based on debt and borrowerFee. The amount of tokens transfered to user is equal to debt - fees. debt - fees is the actual token amount borrowed by the user, not the debt that the borrower wanted to borrow.

// calculate the fees
uint256 fees = (debt * borrowerFee) / 10000;
// transfer fees
IERC20(loan.loanToken).transfer(feeReceiver, fees);
// transfer the loan tokens from the pool to the borrower
IERC20(loan.loanToken).transfer(msg.sender, debt - fees);
// transfer the collateral tokens from the borrower to the contract
IERC20(loan.collateralToken).transferFrom(
msg.sender,
address(this),
collateral
);

When it comes to repaying the debt the interest is calculated based on l.debt not the amount that the borrower received. It means that the user is paying interest on the full amount of debt that he did not received.

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;
}

To add up _calculateInterest is called in 4 other functions which also make them faulty of this calculation.

  • getLoanDebt

// calculate the accrued interest
(uint256 interest, uint256 fees) = _calculateInterest(loan);
debt = loan.debt + interest + fees;
  • giveLoan

// calculate the interest
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
uint256 totalDebt = loan.debt + lenderInterest + protocolInterest;
  • buyLoan

// calculate the interest
(uint256 lenderInterest, uint256 protocolInterest) = _calculateInterest(
loan
);
// reject if the pool is not big enough
uint256 totalDebt = loan.debt + lenderInterest + protocolInterest;
  • refinance

// calculate the interest
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
uint256 debtToPay = loan.debt + lenderInterest + protocolInterest;

Impact

As the result borrower ends up paying more interest than he should be.

Tools Used

Manual Analysis, VScode

Recommendations

I think the solution to this situation could be sending the full amount of tokens to to user and charge the borrowerFee when the borrower repays the loan. To ensure that the borrowerFee will be paid, the extra amount of collateral can be transfered from the borrower while borrowing the tokens.
Another solution might be refactoring _calculateInterest function to calculate interest based on the actual amount of tokens received by the borrower.

Support

FAQs

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