Algo Ssstablecoinsss

AI First Flight #2
Beginner FriendlyDeFi
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

_revert_if_health_factor_is_broken uses MIN_HEALTH_FACTOR scaled for 18-decimal WETH, inflating the WBTC health factor by ~1e10 and letting WBTC positions evade liquidation

Root + Impact

Description

  • _revert_if_health_factor_is_broken enforces a single MIN_HEALTH_FACTOR = 1e18 for every collateral, and the health factor is derived from _get_usd_value, which scales all amounts by PRECISION = 1e18 as if every token had 18 decimals.

  • WBTC has 8 decimals, so its USD valuation and resulting user_health_factor are off by a factor of 1e10 relative to the 18-decimal assumption baked into MIN_HEALTH_FACTOR. The health-factor logic is effectively implemented only for WETH; WBTC is mishandled.

@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 # @> MIN_HEALTH_FACTOR=1e18 assumes 18-dec collateral; WBTC is 8-dec
), "DSCEngine__BreaksHealthFactor"

Risk

Likelihood:

  • WBTC is an in-scope collateral token (README: "Tokens: WETH and WBTC") and on the target chain has 8 decimals, so the mismatch applies to every WBTC position once the protocol is deployed with real WBTC.

  • The 18-decimal MIN_HEALTH_FACTOR and PRECISION constants are fixed, so the discrepancy is deterministic, not conditional.

Impact:

  • For WBTC, user_health_factor is inflated by ~1e10 versus its true value, so _revert_if_health_factor_is_broken passes positions that are actually undercollateralized — WBTC borrowers can mint far more DSC than their collateral backs.

  • The same inflation makes undercollateralized WBTC positions read as healthy, so they cannot be liquidated, leaving the protocol with unbacked DSC and bad debt.

Proof of Concept

With an 8-decimal WBTC, the health-factor check evaluates a value scaled for 18 decimals. Depositing 1 WBTC (1e8 base units) yields a USD value ~1e10 too small in _get_usd_value, and conversely the per-position health factor is inflated relative to the 18-decimal MIN_HEALTH_FACTOR threshold:

def test_poc_wbtc_health_factor(dsc, btc_feed, user):
# 8-decimal WBTC mock wired to an 8-decimal BTC/USD feed at $60,000
wbtc = deploy_8dec_token()
engine = boa.load(
"src/dsc_engine.vy",
[wbtc.address, weth.address],
[btc_feed.address, eth_feed.address],
dsc.address,
)
# 1 whole WBTC == 1e8 base units (NOT 1e18)
one_wbtc = 1 * 10**8
# The engine values 1 WBTC at ~$0.000006 instead of $60,000 — off by 1e10
# because _get_usd_value divides by PRECISION=1e18 assuming 18-dec amounts.
usd = engine.get_usd_value(wbtc.address, one_wbtc)
assert usd == (60000 * 10**8 * 10**10 * one_wbtc) // 10**18 # = 6e-6 * 1e18
assert usd < 60000 * 10**18 // 10**9 # grossly undervalued
# MIN_HEALTH_FACTOR (1e18, an 18-dec scale) is the same threshold applied to this
# mis-scaled value, so WBTC health factors are inflated ~1e10 vs WETH.

Run: mox test --match test_poc_wbtc_health_factor -vvv

Observed: 1 WBTC is valued ~1e10 below its true USD value, so the MIN_HEALTH_FACTOR check in _revert_if_health_factor_is_broken is evaluated against a mis-scaled health factor for WBTC.

Recommended Mitigation

Normalize every collateral amount to 18 decimals before valuation so a single MIN_HEALTH_FACTOR applies correctly to all tokens — store a per-token precision factor (10 ** (18 - decimals())) at construction and apply it in _get_usd_value:

- (convert(price, uint256) * ADDITIONAL_FEED_PRECISION) * amount
- ) // PRECISION
+ (convert(price, uint256) * ADDITIONAL_FEED_PRECISION) * (amount * self.token_precision[token])
+ ) // PRECISION
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 12 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!