Summary
A user can liquidate himself, effectively not allowing another liquidator to take rewards from his liquidation.
Vulnerability Details
Imagine the following scenario. Alice deposits collateral and mints dsc. Her initial health factor is okay and she isn't at risk of being liquidated. After some time, the price of Alice's collateral drops and her position is now eligible for liquidation. Alice sees that she can be liquidated and she liquidates herself. She takes no reward, but she takes the chance for someone else, another liquidator, to take the rewards for her liquidation since she is already liquidated.
POC:
function testFailLiquidationIfUserLiquidatesHimselfAndThemSomeoneElseTriesToLiquidateHim() public {
vm.startPrank(user);
ERC20Mock(weth).approve(address(dsce), amountCollateral);
dsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
vm.stopPrank();
int256 ethUsdUpdatedPrice = 18e8;
MockV3Aggregator(ethUsdPriceFeed).updateAnswer(ethUsdUpdatedPrice);
vm.startPrank(user);
dsc.approve(address(dsce), amountToMint);
dsce.liquidate(weth, user, amountToMint);
vm.stopPrank();
ERC20Mock(weth).mint(liquidator, collateralToCover);
vm.startPrank(liquidator);
ERC20Mock(weth).approve(address(dsce), collateralToCover);
dsce.depositCollateralAndMintDsc(weth, collateralToCover, amountToMint);
dsc.approve(address(dsce), amountToMint);
dsce.liquidate(weth, user, amountToMint);
vm.stopPrank();
vm.expectRevert(DSCEngine.DSCEngine__HealthFactorOk.selector);
}
Impact
A user that liquidates himself takes the chance for another user to reap the rewards for his liquidation.
Tools Used
Manual Review
Foundry
Recommendations
Add a check inside the liquidate()
function that checks if the user
that was passed is different from msg.sender
function liquidate(address collateral, address user, uint256 debtToCover)
external
moreThanZero(debtToCover)
nonReentrant
{
require(user != msg.sender, "A user cannot liquidate themselves");
uint256 startingUserHealthFactor = _healthFactor(user);
if (startingUserHealthFactor >= MIN_HEALTH_FACTOR) {
revert DSCEngine__HealthFactorOk();
}
uint256 tokenAmountFromDebtCovered = getTokenAmountFromUsd(collateral, debtToCover);
uint256 bonusCollateral = (tokenAmountFromDebtCovered * LIQUIDATION_BONUS) / LIQUIDATION_PRECISION;
uint256 totalCollateralToRedeem = tokenAmountFromDebtCovered + bonusCollateral;
_redeemCollateral(user, msg.sender, collateral, totalCollateralToRedeem);
_burnDsc(debtToCover, user, msg.sender);
uint256 endingUserHealthFactor = _healthFactor(user);
if (endingUserHealthFactor <= startingUserHealthFactor) {
revert DSCEngine__HealthFactorNotImproved();
}
_revertIfHealthFactorIsBroken(msg.sender);
}