20,000 USDC
View results
Submission Details
Severity: medium

`seizeLoan()` and `buyLoan()` functions contradict at block.number when block.number and loan.auctionStartTimestamp + loan.auctionLength are equal

Summary

Vulnerability Details

When block.number equals loan.auctionStartTimestamp + loan.auctionLength, the lender can call the following seizeLoan() function to seize the loan.

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];
}
}

However, at such block.number, another lender can call the following buyLoan() function to take over the loan.

function buyLoan(uint256 loanId, bytes32 poolId) public {
// 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 AuctionEnded();
// calculate the current interest rate
uint256 timeElapsed = block.timestamp - loan.auctionStartTimestamp;
uint256 currentAuctionRate = (MAX_INTEREST_RATE * timeElapsed) /
loan.auctionLength;

Therefore, at such block.number when block.number and loan.auctionStartTimestamp + loan.auctionLength are equal, contradicting situations occur.
For example,another lender, who finds the auction rate very appealing, would call the buyLoan() function to frontrun the seizeLoan() transaction of the original lender, who would like to seize the loan. The original lender fails at owning assets that could be owned . Because of these asset and opportunity losses, disputes among the original lender and other lender who auctions will occur.

Impact

At such block.number when block.number and loan.auctionStartTimestamp + loan.auctionLength are equal, contradicting situations occur

Tools Used

Vscode

Recommendations

The seizeLoan() function can be updated to check block.timestamp <=
loan.auctionStartTimestamp + loan.auctionLength instead of block.timestamp <=
loan.auctionStartTimestamp + loan.auctionLength for the if statement. Alternatively, the buyLoan() and Core.refinanceAuction functions can be updated to check block.timestamp >= loan.auctionStartTimestamp + loan.auctionLength instead of block.timestamp > loan.auctionStartTimestamp + loan.auctionLength for the if statement.

Support

FAQs

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