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

Borrower may end up paying non-agreed interests using the `refinance()` function

Summary

The borrower may end up paying non-agreed interests in the refinance() function.

Vulnerability Details

The refinance() function helps to the borrower to transfer his debt to another pool. The borrower sends the next data:

struct Refinance {
/// @notice the loan ID to refinance
uint256 loanId;
/// @notice the pool ID to refinance to
bytes32 poolId;
/// @notice the new desired debt amount
uint256 debt;
/// @notice the new desired collateral amount
uint256 collateral;
}

The problem is that the borrower can be frontrunned by a malicious lender changing the pool interest or a legitimate lender can change his pool interests while the refinance() function is executing making the borrower to take non-agreed interest by chance. Please see the next scenario:

  1. Borrower calls the refinance() function because he wants to transfer his debt to a pool which has a 0.01% interest rate.

  2. Legitimate lender just change his pool interests to 0.3% using the updateInterestRate() function. This function is executed before the step1 because the lender pay more gas.

  3. The refinance() transaction is now executed but the pool interest has increased, now the borrower end up with a different pool interest (0.3%).

Impact

The borrower may end up paying more pool interests via a malicious lender frontrun or lender changing interest rate by chance while the borrower refinance is executing.

Tools used

Manual review

Recommendations

Add a validation in the refinance() function which helps the borrower to specify the interest he is allowed to pay:

struct Refinance {
/// @notice the loan ID to refinance
uint256 loanId;
/// @notice the pool ID to refinance to
bytes32 poolId;
/// @notice the new desired debt amount
uint256 debt;
/// @notice the new desired collateral amount
uint256 collateral;
++ /// @notice pool interest the borrower is allowed to pay
++ uint256 poolInterest;
}
function refinance(Refinance[] calldata refinances) public {
for (uint256 i = 0; i < refinances.length; i++) {
uint256 loanId = refinances[i].loanId;
bytes32 poolId = refinances[i].poolId;
bytes32 oldPoolId = keccak256(
abi.encode(
loans[loanId].lender,
loans[loanId].loanToken,
loans[loanId].collateralToken
)
);
uint256 debt = refinances[i].debt;
uint256 collateral = refinances[i].collateral;
++ uint256 poolInterest = refinances[i].poolInterest;
// get the loan info
Loan memory loan = loans[loanId];
// validate the loan
if (msg.sender != loan.borrower) revert Unauthorized();
// get the pool info
Pool memory pool = pools[poolId];
++ if (pool.interestRate != poolInterest) revert();

Support

FAQs

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

Give us feedback!