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

During refinance() new Pool balance debt is subtracted twice

Summary

A borrower has the opportunity to move their loan to another pool under new lending conditions.

During refinancing interest from the loan is transferred to the old pool.

The debt is then transferred to the new pool however this subraction occurs twice, resulting in loss of funds to the Lenders pool.

Vulnerability Details

In refinance() the debt to the new pool is transferred at line 636

_updatePoolBalance(poolId, pools[poolId].poolBalance - debt);

The debt is subtracted again at line 696

pools[poolId].poolBalance -= debt;

Impact

Pool balances are reduced twice by the transferred debt. The larger the loan the greater the loss of funds to the new pool.

Code Snippet

https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Lender.sol#L636

https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Lender.sol#L698

Proof of Concept

function test_Refinance() public {
vm.startPrank(lender1);
Pool memory p1 = Pool({
lender: lender1,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100 * 10 ** 18,
poolBalance: POOL_LOAN_TOKEN_BALANCE,
maxLoanRatio: 2 * 10 ** 18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
Pool memory p2 = Pool({
lender: lender2,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100 * 10 ** 18,
poolBalance: POOL_B_LOAN_TOKEN_BALANCE,
maxLoanRatio: 2 * 10 ** 18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
bytes32 poolIdOne = lender.setPool(p1);
vm.startPrank(lender2);
bytes32 poolIdTwo = lender.setPool(p2);
bytes32[] memory poolIds = new bytes32[](3);
poolIds[0] = poolIdOne;
poolIds[1] = poolIdTwo;
uint256[] memory loansIds = new uint256[](1);
loansIds[0] = 0;
vm.startPrank(borrower);
Borrow memory b = Borrow({poolId: poolIdOne, debt: LOAN_AMOUNT, collateral: 1000 * 10 ** 18});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);
Refinance memory r =
Refinance({loanId: 0, poolId: poolIdTwo, debt: 1000 * 10 ** 18, collateral: 1000 * 10 ** 18});
Refinance[] memory refinances = new Refinance[](1);
refinances[0] = r;
vm.warp(10 days);
// New Pool balance before refinancing
(,,,, uint256 poolBalance,,,,) = lender.pools(poolIdTwo);
assertEq(poolBalance, (POOL_B_LOAN_TOKEN_BALANCE));
lender.refinance(refinances);
(,,,, poolBalance,,,,) = lender.pools(poolIdTwo);
// Debt is transferred to new pool twice
assertEq(poolBalance, (POOL_B_LOAN_TOKEN_BALANCE) - (2 * 1000 * 10 ** 18));
}

Tools Used

Manual review and Foundry for the POC

Recommended Mitigation Steps

Remove the second debt transfer at line 696

pools[poolId].poolBalance -= debt;

Support

FAQs

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