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

Inability to refinance within the same pool due to exceeding the pool balance

Summary

The refinance function in the Lender contract allows a borrower to refinance a loan to a new offer. However, when refinancing within the same pool, the pool balance may not be sufficient as the balance is checked against the full debt, not the additional debt.

Vulnerability Details

With the refinance function, a borrower can refinance the loan to a new offer (e.g., increasing or decreasing the debt). The loan can potentially be refinanced and assigned to a new pool, or the loan can be refinanced within the same pool. In either case, the pool balance is checked in line 616 to ensure the pool balance is sufficient to cover the total debt.

However, when refinancing within the same pool, the pool balance may not be sufficient as the balance is checked against the full debt, not the additional debt.

For instance, given a pool with an initial balance of 100 ether ETH. A borrower borrows 90 ether (pools balance is 10 ether after creating the loan) and, after a while, attempts to refinance and increase the debt by an additional 10 ether ETH, to a total debt of 100 ether ETH.

At the time of the refinance, the pool's balance is 10 ether ETH and checked if it's smaller than the loan's new debt in line 616. However, as debt is the total debt (90 ether + 10 ether = 100 ether), the check 10 ether < 100 ether evaluates to true and thus reverts with the LoanTooLarge error, reverting the refinance attempt.

Lender.sol#L616

591: function refinance(Refinance[] calldata refinances) public {
592: for (uint256 i = 0; i < refinances.length; i++) {
593: uint256 loanId = refinances[i].loanId;
594: bytes32 poolId = refinances[i].poolId;
595: bytes32 oldPoolId = keccak256(
596: abi.encode(
597: loans[loanId].lender,
598: loans[loanId].loanToken,
599: loans[loanId].collateralToken
600: )
601: );
602: uint256 debt = refinances[i].debt;
603: uint256 collateral = refinances[i].collateral;
604:
605: // get the loan info
606: Loan memory loan = loans[loanId];
607: // validate the loan
608: if (msg.sender != loan.borrower) revert Unauthorized();
609:
610: // get the pool info
611: Pool memory pool = pools[poolId];
612: // validate the new loan
613: if (pool.loanToken != loan.loanToken) revert TokenMismatch();
614: if (pool.collateralToken != loan.collateralToken)
615: revert TokenMismatch();
616: @> if (pool.poolBalance < debt) revert LoanTooLarge(); // @audit-info pool balance is potentially exceeded and reverts
617: if (debt < pool.minLoanSize) revert LoanTooSmall();
618: uint256 loanRatio = (debt * 10 ** 18) / collateral;
... // [...]

Impact

Refinancing a loan (i.e., increasing the debt) within the same pool potentially fails, leading to unutilized pool balance.

Tools Used

Manual Review

Recommendations

Consider removing the if statement checking the pool balance in line 616. In case the debt is larger than the current pool balance, updating the pool balance with the _updatePoolBalance function in line 636 would revert anyway.

Alternatively, consider only checking the pool balance for the additional debt in line 616.

Support

FAQs

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