Algo Ssstablecoinsss

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

_get_usd_value/_get_token_amount_from_usd hardcode an 18-decimal assumption, but the real configured WBTC collateral token has 8 decimals -- WBTC value is catastrophically mispriced

Summary

dsc_engine.vy's price math assumes every collateral token uses 18 decimals. The real WBTC token configured for zkSync Era mainnet in moccasin.toml (0xBBeB516fb02a01611cBBE0453Fe3c580D7281011) actually has 8 decimals, verified directly on-chain. Since _get_usd_value/_get_token_amount_from_usd never adjust for a token's actual decimals(), any real WBTC deposit is undervalued by a factor of 10^10, and any WBTC amount computed for liquidation payouts is off by the same factor in the other direction -- both directions make the protocol either impossible to use safely with real WBTC or trivially breakable via reverts/underflows.

Description

# dsc_engine.vy
PRECISION: public(constant(uint256)) = 1 * (10**18)
ADDITIONAL_FEED_PRECISION: public(constant(uint256)) = 1 * (10**10) # scales an 8-decimal Chainlink price to 18 decimals
FEED_PRECISION: public(constant(uint256)) = 1 * (10**8)
def _get_usd_value(token: address, amount: uint256) -> uint256:
...
return ((convert(price, uint256) * ADDITIONAL_FEED_PRECISION) * amount) // PRECISION
def _get_token_amount_from_usd(token: address, usd_amount_in_wei: uint256) -> uint256:
...
return (usd_amount_in_wei * PRECISION) // (convert(price, uint256) * ADDITIONAL_FEED_PRECISION)

Both formulas treat amount/their result as if the token always uses 18 decimals (that's what PRECISION = 1e18 implicitly assumes for the amount parameter's unit). This is correct for WETH (verified 18 decimals), but the real configured WBTC token has 8 decimals:

$ cast call 0xBBeB516fb02a01611cBBE0453Fe3c580D7281011 "decimals()(uint8)" --rpc-url https://mainnet.era.zksync.io
8

Concretely: 1 real WBTC = 1e8 raw units (not 1e18). Plugging amount = 1e8 (1 real WBTC) into _get_usd_value at a BTC price of, say, $30,000 (price = 30000e8 from the feed) gives:

((30000e8 * 1e10) * 1e8) // 1e18 = 30000e10 = $0.0000003 (should be $30,000)

i.e. 1 whole WBTC is valued at roughly 0.0000003 dollars instead of $30,000 -- undervalued by a factor of ~10^11. This breaks collateral accounting catastrophically: depositing real WBTC contributes effectively nothing to collateral_value_in_usd, so mint_dsc would always fail the health-factor check for anyone trying to mint DSC against WBTC collateral (the protocol becomes unusable for one of its two advertised collateral types), while _get_token_amount_from_usd (used inside liquidate to size the liquidator's payout) has the mispricing in the opposite direction relative to any correctly-priced debt, so a mixed WETH/WBTC liquidation scenario computes wildly inconsistent, exploitable payout sizes between the two collateral types.

Risk

Likelihood:

  • Deterministic given the protocol's own configured collateral set -- WBTC is one of only two collateral tokens the protocol supports, per its own README.md ("deposit WETH and WBTC") and moccasin.toml.

Impact:

  • Either bricks WBTC as usable collateral entirely (health factor checks always fail once WBTC is worth ~$0.0000003 per whole token in the engine's accounting) or, if any function path skips the health-factor gate, allows drastically miscalculated liquidation payouts -- both are severe breaks of the core collateral-value invariant the entire protocol is built on.

Proof of Concept

$ cast call 0xBBeB516fb02a01611cBBE0453Fe3c580D7281011 "decimals()(uint8)" --rpc-url https://mainnet.era.zksync.io
8
$ cast call 0xf00DAD97284D0c6F06dc4Db3c32454D4292c6813 "decimals()(uint8)" --rpc-url https://mainnet.era.zksync.io
18

The two collateral tokens the protocol is configured to accept have different decimals (WBTC=8, WETH=18), but _get_usd_value/_get_token_amount_from_usd apply the identical 18-decimals-assumed formula to both, with no per-token decimals normalization anywhere in dsc_engine.vy.

Recommended Mitigation

Read each collateral token's actual decimals() at deposit/valuation time (or store it per-token at construction) and normalize amount to 18-decimal terms before applying the price formula, e.g. amount_in_18_decimals = amount * (10 ** (18 - token_decimals)), mirroring how ADDITIONAL_FEED_PRECISION already normalizes the price feed's own 8-decimal output.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 7 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!