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

LoanTooLarge on refinance

Summary

Refinance LoanTooLarge incorrect for the same pool

Vulnerability Details

Refinance can be used to pay down part of what is owed by a borrower to the same pool. However, this is sometimes not allowed due to the order of operations in the refinace method. For instance, if a borrower takes out a loan for the entire pool balance, then calls refinance on this loan to the same pool this will revert as the pool balance is 0.

Simple test that fails with LoanTooLarge

function test_refinance_fail() public {
vm.startPrank(lender1);
Pool memory p = Pool({
lender: lender1,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 10*10**18,
poolBalance: 100*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);
(,,,,uint256 poolBalance,,,,) = lender.pools(poolId);
assertEq(poolBalance, 100*10**18);
assertEq(loanToken.balanceOf(address(borrower)), 0);
vm.startPrank(borrower);
//without approval, the transfer returns false and no tokens are transfered for collateral.
collateralToken.approve(address(lender), 100*10**18);
Borrow memory b = Borrow({
poolId: poolId,
debt: 100*10**18,
collateral: 100*10**18
});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);
vm.startPrank(borrower);
Refinance memory r = Refinance({
loanId: 0,
poolId: keccak256(
abi.encode(
address(lender1),
address(loanToken),
address(collateralToken)
)
),
debt: 90*10**18,
collateral: 100*10**18
});
Refinance[] memory rs = new Refinance[](1);
rs[0] = r;
lender.refinance(rs);
}

Impact

Low

Some users will not be able to refinance to a smaller amount in the same pool when the pool balance is less than the debt of the loan .

Tools Used

Woke fuzz tests

Recommendations

Change the order of operations to update the old pool balance first, then check the inputs and revert if necessary.

uint256 debt = refinances[i].debt;
uint256 collateral = refinances[i].collateral;
// get the loan info
Loan memory loan = loans[loanId];
// validate the loan
if (msg.sender != loan.borrower) revert Unauthorized();
// calculate the interest
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
uint256 debtToPay = loan.debt + lenderInterest + protocolInterest;
// update the old lenders pool
_updatePoolBalance(
oldPoolId,
pools[oldPoolId].poolBalance + loan.debt + lenderInterest
);
pools[oldPoolId].outstandingLoans -= loan.debt;
// get the pool info
Pool memory pool = pools[poolId];
// validate the new loan
if (pool.loanToken != loan.loanToken) revert TokenMismatch();
if (pool.collateralToken != loan.collateralToken)
revert TokenMismatch();
if (pool.poolBalance < debt) revert LoanTooLarge();
if (debt < pool.minLoanSize) revert LoanTooSmall();
uint256 loanRatio = (debt * 10 ** 18) / collateral;
if (loanRatio > pool.maxLoanRatio) revert RatioTooHigh();

Support

FAQs

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

Give us feedback!