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

wBTC collateral cannot be utilized to mint DSC tokens, due to an incorrect `getUsdValue` calculation

Summary

When a user deposits wBTC into the system, the _calculateHealthFactor function responsible for calculating their health factor, which relies on the _getAccountInformation and the getAccountCollateralValue function determining their total collateral and total debt.

The getAccountCollateralValue() function will return a significantly lower amount of USD value that it should in case the user deposits wBTC to the system. As a result, the user will be treated unfairly, appearing undercollateralized, and they will be unable to mint DSC tokens under this circumstance.

Vulnerability Details

wBTC is a token with 8 decimals, so when a user deposits wBTC into the system, the amount parameter will also have 8 decimals. For example, if a user deposits 1 BTC, they will send 1 * 1e8 as the amount parameter, and the s_collateralDeposited state will be updated as follows:
s_collateralDeposited[user][wBTC] = 1 * 1e8;

The calculateHealthFactor function relies on getAccountCollateralValue to determine the total USD value of an account's deposits in the system. This value is then used to assess the health factor, influencing whether a transaction should be reverted or not.

For accurate calculation of the health factor, getAccountCollateralValue must provide the result in 18 decimal places. The issue arises from how getAccountCollateralValue calculates the USD value of the wBTC collateral. It uses getUsdValue(wBTC, s_collateralDeposited[user][token]) to determine the USD value of the deposited wBTC. However, as the amount is in 8 decimal places (wBTC amount), the getUsdValue(token, amount) function returns a USD value with 8 decimal places instead of 18.

Consequently, the calculated value will be much lower than the actual USD value of the deposited wBTC tokens, resulting in a health factor 8 decimals which is significantly lower than the minimum health factory (18 decimals).

This impacts the user in two ways:

  1. The user can't mint DSC tokens.

  2. The user's position becomes vulnerable to immediate liquidation right after the deposit.

POC

Add the following function to the DSCEngineTest.t.sol file to regenerate the bug:

function testWBTCCantBeUsedAsCollateralPOC() public {
vm.startPrank(user);
uint256 depositAmount = 1 * 1e8; // We deposit 1 WBTC (8 decimls)
(, int256 price,,,) = MockV3Aggregator(btcUsdPriceFeed).latestRoundData();
uint256 toMint = depositAmount * uint256(price) / 3; // We try to mint 33% of out collateral
ERC20Mock(wbtc).approve(address(dsce), depositAmount);
// Expect the transaction to be reverted with "BreaksHealthFactor" error
// Even though our health factor should be ok.
vm.expectRevert(abi.encodeWithSelector(DSCEngine.DSCEngine__BreaksHealthFactor.selector, 15000000000));
dsce.depositCollateralAndMintDsc(wbtc, depositAmount, toMint);
vm.stopPrank();
}

Impact

Users can't mint DSC tokens for their wBTC deposited collateral.

Tools Used

VSCode

Recommendations

Modify the getUsdValue(address token, uint256 amount) function so it will take into consideration the token's decimals.

Change this:
uint256 usdValue = ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;

To that:
uint256 usdValue = ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / token.decimals();

Support

FAQs

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