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

Lender.sol#borrow() - 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

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:

// Setting up the borrower so he can repay the loan at the end. Used to showcase the effect of the issue.
function testLenderCanIncreasePoolInterestRateBeforeBigBorrow() public {
// Setting up the borrower so he can repay the loan at the end. Used to showcase the effect of the issue.
loanToken.mint(address(borrower), 100000*10**18);
vm.prank(borrower);
loanToken.approve(address(lender), 1000000*10**18);
// Lender (malicous) creates a pool with very low/no interest. For simplicity sake he trades his tokens in a 1:1 ratio
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,
// lucrative interest rate
interestRate: 100,
outstandingLoans: 0
});
bytes32 createdPoolId = lender.setPool(newPool);
// Lender sees Borrower's tx and front runs, maximizing his interestRate
lender.updateInterestRate(createdPoolId, 10000);
vm.stopPrank();
// Borrower now takes out a loan, still thinking he is taking out a loan with low interest.
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();
// Time passes.
vm.warp(block.timestamp + 365 days);
// Borrower decides to repay his loan. Repaying with a huge interest
vm.startPrank(borrower);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.repay(loanIds);
vm.stopPrank();
// Lender 10x's his pool balance, because he front ran the borrower.
(,,,,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 {
/// @notice the pool ID to borrow from
bytes32 poolId;
/// @notice the amount to borrow
uint256 debt;
/// @notice the amount of collateral to put up
uint256 collateral;
// Maximum interest rate
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;
// get the pool info
Pool memory pool = pools[poolId];
// make sure the pool exists
// Maximum interest rate the borrower is willing to take out a loan
if (borrows[i].maxInterestRate <= pool.interestRate) revert();
...

Support

FAQs

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