The engine values every collateral token as if amounts were 18-decimal base units, with a single fixed price-feed precision and no per-token decimal handling. WETH is 18 decimals, but the real WBTC token (the exact token configured for this system on ZKsync Era in moccasin.toml) has 8 decimals. As a result, 1 WBTC deposited in its real base units (10^8) is credited as 10^-10 of a WBTC: depositors get essentially zero minting power for WBTC, and liquidation amounts computed in 18-decimal terms exceed what WBTC depositors actually deposited, so redeeming collateral during a WBTC liquidation underflows and reverts.
_get_usd_value applies one formula to both collaterals, with no decimal adjustment per token:
and _get_token_amount_from_usd inverts the same 18-decimal assumption when liquidate() computes how much collateral to seize. Nothing in deposit_collateral, the constructor, or the deployment configuration scales WBTC's 8-decimal amounts, while moccasin.toml points the ZKsync deployment at the real 8-decimal WBTC contract (0xBBeB516fb02a01611cBBE0453Fe3c580D7281011).
Likelihood: High - this is not an edge case; it triggers for every genuine WBTC deposit on the configured deployment, because the mismatch is in the unit convention itself.
Impact: Medium - the WBTC leg of the protocol is effectively broken: WBTC depositors receive ~zero borrowing power, and WBTC-backed positions cannot be liquidated correctly (the seize amount is computed 10^10 times too large relative to deposits and the redemption underflows), which can leave bad debt in the system.
Executed locally with titanoboa (BTC feed mock at $2,000, matching the repo's own mock setup): the engine values 10^18 units at the full $2,000, but 1 WBTC expressed in its real base units (10^8) is valued at exactly 10^10 times less.
Track each collateral token's decimals and normalize amounts to 18 decimals inside the valuation and seizure math (for WBTC, scale by 10^10), or explicitly require an 18-decimal wrapped BTC token as collateral:
## 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" ```
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.