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);
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;
Loan memory loan = loans[loanId];
if (msg.sender != loan.borrower) revert Unauthorized();
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
uint256 debtToPay = loan.debt + lenderInterest + protocolInterest;
_updatePoolBalance(
oldPoolId,
pools[oldPoolId].poolBalance + loan.debt + lenderInterest
);
pools[oldPoolId].outstandingLoans -= loan.debt;
Pool memory pool = pools[poolId];
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();