Summary
A malicious lender can front run a borrower and increase his interest rate, effectively misleading the borrower to borrow at interest rates that he wouldn't otherwise.
Vulnerability Details
A lender can easily front run a borrower and call updateInterestRate
.
Let's imagine the following:
Lender (malicous) creates a pool with a very low/no interestRate
to lure more borrowers to his pool.
Borrower sees the lucrative interestRate
and decides to take out a very big loan.
Lender sees Borrower's tx and front runs it, calling updateInterestRate
and increasing the interestRate
close to the maximum allowed.
Borrower takes out a loan at what he thought was very low interestRate
, but because he got front ran he borrowed at a very high interestRate
.
POC:
function testLenderCanIncreasePoolInterestRateBeforeBigBorrow() public {
loanToken.mint(address(borrower), 100000*10**18);
vm.prank(borrower);
loanToken.approve(address(lender), 1000000*10**18);
vm.startPrank(lender1);
Pool memory newPool = 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: 100,
outstandingLoans: 0
});
bytes32 createdPoolId = lender.setPool(newPool);
lender.updateInterestRate(createdPoolId, 10000);
vm.stopPrank();
vm.startPrank(borrower);
Borrow memory b = Borrow({
poolId: createdPoolId,
debt: 1000*10**18,
collateral: 1000*10**18
});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);
vm.stopPrank();
vm.warp(block.timestamp + 365 days);
vm.startPrank(borrower);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.repay(loanIds);
vm.stopPrank();
(,,,,uint256 updatedPoolBalance,,,,) = lender.pools(createdPoolId);
assertEq(updatedPoolBalance, 1900 * 10 ** 18);
}
Impact
Loss of funds for the borrower as he now has to pay of his loan with a huge interest rate.
Tools Used
Manual review
Foundry
Recommendations
Add maxInterestRate
in the Borrow
struct, this will be the maximum interest rate the borrower is willing to use, when he takes out a loan.
struct Borrow {
bytes32 poolId;
uint256 debt;
uint256 collateral;
uint256 maxInterestRate;
}
function borrow(Borrow[] calldata borrows) public {
for (uint256 i = 0; i < borrows.length; i++) {
bytes32 poolId = borrows[i].poolId;
uint256 debt = borrows[i].debt;
uint256 collateral = borrows[i].collateral;
Pool memory pool = pools[poolId];
if (borrows[i].maxInterestRate <= pool.interestRate) revert();
...