Algo Ssstablecoinsss

AI First Flight #2
Beginner FriendlyDeFi
EXP
View results
Submission Details
Severity: high
Valid

MIN_HEALTH_FACTOR is only correct for WETH — WBTC health factor is deflated by 10^10 due to decimal precision mismatch

Description

  • Normal: The health factor check should work correctly regardless of which collateral token the user deposits. MIN_HEALTH_FACTOR should account for the different decimal precisions of WETH (18 decimals) and WBTC (8 decimals).

  • Bug: _get_usd_value() returns the USD value of collateral at different precisions depending on the token's decimals — WETH returns 18-decimal precision while WBTC returns 8-decimal precision. MIN_HEALTH_FACTOR = 1 * 10**18 is calibrated for WETH's 18-decimal output. For WBTC, the health factor is ~10^10 times smaller than it should be, causing the _revert_if_health_factor_is_broken check to unfairly reject healthy WBTC positions.

# src/dsc_engine.vy:24
MIN_HEALTH_FACTOR: public(constant(uint256)) = 1 * (10**18) #@> Only correct for WETH (18 decimals)
# src/dsc_engine.vy:302-316
def _get_usd_value(token: address, amount: uint256) -> uint256:
# ...
return (
(convert(price, uint256) * ADDITIONAL_FEED_PRECISION) * amount
) // PRECISION
#@> For WETH (amount in 18 decimals): returns USD value in 18 decimals ✓
#@> For WBTC (amount in 8 decimals): returns USD value in 8 decimals ✗
#@> The 10^10 difference in precision breaks the health factor comparison
# src/dsc_engine.vy:269-273
def _revert_if_health_factor_is_broken(user: address):
user_health_factor: uint256 = self._health_factor(user)
assert (
user_health_factor >= MIN_HEALTH_FACTOR #@> MIN_HEALTH_FACTOR assumes 18-decimal health factor
), "DSCEngine__BreaksHealthFactor"

Risk

Likelihood:

  • WBTC is one of only two supported collateral tokens (COLLATERAL_TOKENS: public(immutable(address[2])))

  • Every WBTC depositor is affected — the precision mismatch is deterministic

  • The bug activates on every _revert_if_health_factor_is_broken call for WBTC positions

Impact:

  • WBTC depositors cannot mint DSC even with sufficient collateral — health factor appears ~10^10 times too low

  • Healthy WBTC positions are incorrectly flagged for liquidation

  • WBTC as a collateral type is functionally broken — users are forced to use only WETH

  • Protocol fails its core design goal of supporting multiple collateral types

Proof of Concept

Trace the precision through the code for a WBTC deposit:

  1. User deposits 1 WBTC = 1 * 10**8 (8 decimals)

  2. Chainlink WBTC price = 60000 * 10**8 (8 decimals, standard)

  3. _get_usd_value calculation:

    usd_value = (60000e8 * 1e10 * 1e8) / 1e18 = 60000 * 10**8

    Result: 60000e8 — USD value with 8-decimal precision

  4. Compare with WETH (1 WETH deposit):

    usd_value = (2000e8 * 1e10 * 1e18) / 1e18 = 2000 * 10**18

    Result: 2000e18 — USD value with 18-decimal precision

  5. Health factor calculation uses usd_value * 10**18 / total_dsc_minted:

    • WETH with $2000 collateral, 1000 DSC minted: health_factor = 1000e36 / 1000e18 = 1e18 ✅ (passes)

    • WBTC with $60000 collateral, 1000 DSC minted: health_factor = 30000e26 / 1000e18 = 3e9 ❌ (fails!)

WBTC user has 30× more collateral value but the health factor is ~3×10^8 times smaller — the position is healthy but the protocol rejects it.

Recommended Mitigation

Add a precision normalization factor for WBTC in the health factor check. Scale the health factor to a consistent 18-decimal precision for all collateral types:

def _revert_if_health_factor_is_broken(user: address):
user_health_factor: uint256 = self._health_factor(user)
+
+ # Normalize health factor for WBTC (8 decimals) to match WETH (18 decimals)
+ # Check if user has WBTC collateral and adjust accordingly
+ if user_health_factor >= (MIN_HEALTH_FACTOR * 10**10):
+ assert user_health_factor >= MIN_HEALTH_FACTOR, "DSCEngine__BreaksHealthFactor"
+ else:
assert (
user_health_factor >= MIN_HEALTH_FACTOR
), "DSCEngine__BreaksHealthFactor"

Or more robustly, fix _get_usd_value to return a consistent 18-decimal precision regardless of the input token's decimals:

def _get_usd_value(token: address, amount: uint256) -> uint256:
# ...
+ # Normalize to 18 decimals based on token decimals
+ token_decimals: uint256 = IERC20(token).decimals()
+ precision_adjustment: uint256 = 10 ** (18 - token_decimals)
return (
- (convert(price, uint256) * ADDITIONAL_FEED_PRECISION) * amount
+ (convert(price, uint256) * ADDITIONAL_FEED_PRECISION) * amount * precision_adjustment
) // PRECISION
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 10 hours ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-01] In the function \_revert_if_health_factor_is_broken constatnt variable MIN_HEALTH_FACTOR is only for WETH.

## Description The `_revert_if_health_factor_is_broken` function is responsible for ensuring that a user's health factor meets the minimum required standard. There is only implementation for WETH. ## Vulnerability Details In the function, there is only implementation for WETH. ```Solidity @internal def _revert_if_health_factor_is_broken(user: address): user_health_factor: uint256 = self._health_factor(user) assert ( user_health_factor >= MIN_HEALTH_FACTOR ), "DSCEngine__BreaksHealthFactor" ``` Value of the `MIN_HEALTH_FACTOR=10^18`is higher than the Satoshi factor which is 10^8. As a result, for WBTC, the `user_health_factor` can be inflated to more than 101010^{10} times its normal value. ## Impact Bigger value of MIN_HEALTH_FACTOR for WBTC allows on bigger value of `user_health_factor`and wrong value when function should revert. ## Recommendations Add MIN_HEALTH_FACTOR also for WBTC. ```Solidity @internal def _revert_if_health_factor_is_broken(user: address): user_health_factor: uint256 = self._health_factor(user) # Check if the user's token is WBTC and adjust health factor accordingly if user_health_factor >= (MIN_HEALTH_FACTOR * 10**10): # If user health factor is higher due to WBTC precision, still ensure it meets the minimum assert user_health_factor >= MIN_HEALTH_FACTOR, "DSCEngine__BreaksHealthFactor" else: assert user_health_factor >= MIN_HEALTH_FACTOR, "DSCEngine__BreaksHealthFactor" ```

Support

FAQs

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

Give us feedback!