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

Depostitor cannot provide wrapped BTC (wBTC) as collateral to mint dsc stable coins.

Summary

context: DSCEngine.sol

The DSCEngine protocol aims to mint dsc stable coins for depositors based on the provided collateral token value in USD.
However, the protocol has limitations in accepting only two tokens as collateral, namely wETH and wBTC. When a depositor
attempts to provide wBTC as collateral and requests to mint dsc stable coins, the protocol encounters an issue because
wBTC has 8 decimals. As a result, the protocol's functionality fails to mint dsc stable coins, and the transaction is
reverted

The minting of dsc stable coins is determined by the health factor, which is calculated using 50% of the total
collateralized token value in USD (the remaining 50% is over-collateralized) and the total dsc stable coins held by the
user in the protocol.

Here's an example calculation of the health factor for a depositor Alice:

Alice deposits 1 wBTC as collateral and requests to mint 1000 dsc stable coins.

1 wBTC = 30,000 USD.

50% of 30,000 USD = 15,000 USD.

Health Factor = 15,000 USD / 1000 USD = 15.

Despite a health factor of 15, which is greater than the MIN_HEALTH_FACTOR (set to 1), the protocol fails to mint tokens
for Alice due to the vulnerability in the DSCEngine.getUsdValue() function.

Vulnerability Details

The vulnerability lies in the DSCEngine.getUsdValue() function, which incorrectly returns the USD value for wBTC tokens
and tokens with decimals != 18. This erroneous value is used to calculate the health factor. As a result, the
computed health factor is always less than the MIN_HEALTH_FACTOR, leading to the failure of minting stable coins for
wBTC depositors.

Let's consider a user who wants to deposit 1 wBTC (wrapped Bitcoin) and requests to mint 1000 dsc coins (stable coins)
to the protocol.

Protocol Calculations:

uint256 private constant ADDITIONAL_FEED_PRECISION = 1e10;

uint256 private constant PRECISION = 1e18;

((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount)  /  PRECISION;

*wrapped BTC has 8 decimals

= ( ( 30000 * 1e8 ) * 1e10 * 1e8 ) / 1e18

= 30000 * 1e8

uint256 collateralAdjustedForThreshold = (collateralValueInUsd * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION;

= ( ( 30000 * 1e8 ) * 50 ) / 100

= 15000 * 1e8

return (collateralAdjustedForThreshold * 1e18)  /  totalDscMinted;

= ( ( 15000 * 1e8 ) * 1e18) / 1000 * 1e8

= 15 * 1e8

    uint256 private constant MIN_HEALTH_FACTOR = 1e18;

    uint256 userHealthFactor = 15 * 1e8     

    if (userHealthFactor < MIN_HEALTH_FACTOR) {

        revert DSCEngine__BreaksHealthFactor(userHealthFactor);

    }

= 15 * 1e8 < 1e18

    forge test

    function test_depositCollateralAndMintDsc() external {

       deal(WBTC_ADDRESS, user, 1e8, true);

       vm.startPrank(user);

       IERC20(WBTC_ADDRESS).approve(address(dscEngine), 1e8);

       vm.expectRevert();

       dscEngine.depositCollateralAndMintDsc(WBTC_ADDRESS, 1e8, 10 ether);

       vm.stopPrank();

     }

     output: 

      DSCEngine::depositCollateralAndMintDsc(StandardToken: [0xD0684a311F47AD7fdFf03951d7b91996Be9326E1], 100000000, 
      10000000000000000000)

      ← "DSCEngine__BreaksHealthFactor(145998936000)"

Impact

The current implementation of the code prevents wBTC depositors from minting dsc stable tokens by providing wBTC as
collateral. Consequently, the protocol loses wBTC depositors, which can be significant in the crypto market where wETH
and wBTC are crucial equity tokens.

This issue breaks the protocol's functionality.

Moreover, if any protocol forks this code to create a stable coin, the forked protocol will also be unable to accept
tokens with decimals != 18 as collateral.

Tools Used

Manual

Recommendations

Fix this issue by computing the getUsdValue() based on the token decimals, wBTC with 8 decimals will be correctly
multiplied by 1e18 to obtain the value in 18 decimals.

The below recommandation only for wBTC and wETH

//getUsdValue function in DSCEngine.sol

uint256 private constant PRECISION = 1e18; 

uint256 private constant ADDITIONAL_FEED_PRECISION = 1e10;

uint256 decimals = IERC20(token_address).decimals();

if (decimals == 8) {

    return ((price * ADDITIONAL_FEED_PRECISION) * amount * 1e10)  /  PRECISION;

} else {

    return ((price * ADDITIONAL_FEED_PRECISION) * amount)  /  PRECISION;

}

By implementing this fix, the protocol can calculate getUsdValue() according to the token decimals.

Support

FAQs

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