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

Refinancing and repaying loan can be done even after an auction was finished

Summary

Lenders of the Beedle protocol can choose to start a refinancing auction, at the end of which, if nobody choose to buy the loan from them, they can seize the loan collateral from the borrower. The borrower always has the option to refinance his loan to a better offer, even during the live auction. This is normal and good. The issue is that the borrower is allowed to refinance and repay his loan even after an auction was finished, before the lender seized his rightfully earned collateral.

Vulnerability Details

Lender can start an auction via Lender::startAuction which sets the auction start time:

// set the auction start timestamp
loans[loanId].auctionStartTimestamp = block.timestamp;

After this point, a new lender may take up the loan by calling Lender::buyLoan but only during the auction period

// validate the loan
if (loan.auctionStartTimestamp == type(uint256).max)
revert AuctionNotStarted();
if (block.timestamp > loan.auctionStartTimestamp + loan.auctionLength)
revert AuctionEnded();

When the auction is finished, the original lender can seize the collateral via Lender::seizeLoan

if (loan.auctionStartTimestamp == type(uint256).max)
revert AuctionNotStarted();
if (
block.timestamp <
loan.auctionStartTimestamp + loan.auctionLength
) revert AuctionNotEnded();

The issue lies with the ability for a borrower to refinance his loan even after the auction is done, as there are no checks in Lender::refinance with regards to auction logic and auction start time is reset, making Lender::seizeLoan revert

loans[loanId].auctionStartTimestamp = type(uint256).max;

and the same with Lender::repay.

Impact

Lender loses his entitled collateral if the defaulter lender refinances his loan after an auction has expired, by front-running him.

Tools Used

Manual analysis and past contests trauma.

Recommend Mitigation

In Lender::refinance add a check similar to that of Lender::buyLoan, for each loan, that, if an auction was started, it now myst be ended.
Example implementation:

diff --git a/src/Lender.sol b/src/Lender.sol
index d04d14f..7d42b4c 100644
--- a/src/Lender.sol
+++ b/src/Lender.sol
@@ -607,6 +607,9 @@ contract Lender is Ownable {
// validate the loan
if (msg.sender != loan.borrower) revert Unauthorized();
+ if (block.timestamp > loan.auctionStartTimestamp + loan.auctionLength)
+ revert AuctionEnded();
+
// get the pool info
Pool memory pool = pools[poolId];
// validate the new loan

Add the same check in Lender::repay.

Support

FAQs

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

Give us feedback!