20,000 USDC
View results
Submission Details
Severity: high

Lack of the validation to check whether or not the loan (`loanId`) was not already bought, which lead to reverting the transaction of the Lender#`seizeLoan()`

Summary

Due to lack of the validation to check whether or not the loan (loanId) was not already bought by a buyer via the Lender#buyLoan(), the transaction of the Lender#seizeLoan() would be reverted in the for-loop if the loan (loanId), which is already bought by a buyer via the Lender#buyLoan().

Vulnerability Details

A lender sieze a loan via the the Lender#seizeLoan() after a failed refinance auction.
Within the Lender#seizeLoan(), the operation would be executed for each loanId in the for-loop like this:
https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Lender.sol#L548-L586

/// @notice sieze a loan after a failed refinance auction
/// can be called by anyone
/// @param loanIds the ids of the loans to sieze
function seizeLoan(uint256[] calldata loanIds) public {
for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
// get the loan info
Loan memory loan = loans[loanId];
// validate the loan
if (loan.auctionStartTimestamp == type(uint256).max)
revert AuctionNotStarted();
if (
block.timestamp <
loan.auctionStartTimestamp + loan.auctionLength
) revert AuctionNotEnded();
// calculate the fee
uint256 govFee = (borrowerFee * loan.collateral) / 10000;
// transfer the protocol fee to governance
IERC20(loan.collateralToken).transfer(feeReceiver, govFee);
// transfer the collateral tokens from the contract to the lender
IERC20(loan.collateralToken).transfer(
loan.lender,
loan.collateral - govFee
);
bytes32 poolId = keccak256(
abi.encode(loan.lender, loan.loanToken, loan.collateralToken)
);
// update the pool outstanding loans
pools[poolId].outstandingLoans -= loan.debt;
emit LoanSiezed(
loan.borrower,
loan.lender,
loanId,
loan.collateral
);
// delete the loan
delete loans[loanId];
}
}

A lender sieze a loan via the the Lender#seizeLoan() when there was no buyer who bought via the Lender#buyLoan() during the refinance auction and the refinance auction was finished.
So, it is supposed to be checked both below:

  • whether or not the loan (loanId) was not already bought by a buyer via the Lender#buyLoan()

  • whether or not the refinance auction was already finished

However, within the Lender#seizeLoan(), there is no validation to check whether or not the loan (loanId) was not already bought by a buyer via the Lender#buyLoan().

This lead to a bad situation that the transaction of the Lender#seizeLoan() would be reverted in the for-loop if the loan (loanId), which is already bought by a buyer via the Lender#buyLoan(). Because this loan (loanId), which is already bought, would be reverted at the line of condition (Lender.sol#L554-L559).

Impact

This vulnerability lead to a bad situation that the transaction of the Lender#seizeLoan() would be reverted in the for-loop if the loan (loanId), which is already bought by a buyer via the Lender#buyLoan(). Because this loan (loanId), which is already bought, would be reverted at the line of condition (Lender.sol#L554-L559).

Tools Used

  • Foundry

Recommendations

Within the Lender#seizeLoan(), consider adding a validation to check whether or not each loan (loanId) in the for-loop was already bought via the Lender#buyLoan()

Support

FAQs

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