The engine converts between token amounts and USD using only the price feed precision and never the collateral token's own decimals. Both pricing helpers assume every collateral token has 18 decimals.
_get_usd_value (src/dsc_engine.vy:300-316):
_get_token_amount_from_usd (src/dsc_engine.vy:344-364):
ADDITIONAL_FEED_PRECISION (1e10) only upscales the 8 decimal Chainlink answer to 18 decimals. There is no term for the token's decimals, so the result is correct only when amount is expressed in 18 decimals.
WBTC is named as a supported collateral and has 8 decimals on Ethereum mainnet and on ZKsync Era. With 8 decimals, _get_usd_value(wbtc, 1e8) returns about 3e12 wei of USD, that is roughly $0.000003 instead of $30,000. Any WBTC position is therefore valued at about 1e10 times less than its real worth, and _get_account_collateral_value (src/dsc_engine.vy:332-341) sums these wrongly scaled values together.
Because liquidation uses the same broken conversion, a liquidator can repay a negligible amount of DSC and seize the victim's WBTC. The attack path is:
A victim holds a position that becomes liquidatable through a normal price move, for example WETH collateral plus a DSC debt taken against it.
The attacker calls liquidate(wbtc, victim, debt_to_cover) with a tiny debt_to_cover. The engine computes token_amount_from_debt_covered = _get_token_amount_from_usd(wbtc, debt_to_cover), which is scaled up by the missing 8 decimal factor, adds a 10 percent bonus, and transfers that WBTC to the attacker.
The attacker pays a few wei of DSC and receives the WBTC.
Likelihood: Medium. WBTC as collateral is the protocol's stated configuration and liquidation is permissionless. The mispricing itself is deterministic for every collateral token with fewer than 18 decimals; the theft path additionally requires a victim whose health factor is below 1 while holding such a token. Separately, every WBTC depositor loses almost all borrowing power from the first deposit, because their collateral is valued at roughly 1e10 times too little, so the flaw harms honest users even outside the theft path.
Impact: High. Direct theft of collateral for any token with fewer than 18 decimals. In the proof below the attacker repays about $0.0000027 of DSC and receives 0.99 WBTC, about $29,700. Sizing debt_to_cover up to the rounding limit takes essentially the entire WBTC balance.
Self contained moccasin/titanoboa test. The 8 decimal token is defined inline with boa.loads, and the engine is deployed exactly as script/deploy_dsc_engine.py wires it. Save as tests/poc_decimals_theft.py and run uv run mox test tests/poc_decimals_theft.py -s.
Output (test passes):
Normalize every token amount to 18 decimals before pricing, and de-normalize when returning a token amount. Read the token's decimals() once, or store it per collateral at construction:
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.