Algo Ssstablecoinsss

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

Hardcoded 18 decimals assumption breaks USD valuation and liquidation calculations for 8-decimal WBTC collateral

Root + Impact

Description

The DSCEngine protocol is designed to accept both WETH (18 decimals) and WBTC (8 decimals) as collateral assets to mint the pegged DSC stablecoin.

However, both _get_usd_value and _get_token_amount_from_usd hardcode PRECISION = 10**18 and assume all collateral tokens have 18 decimals. Because WBTC has only 8 decimals, _get_usd_value undervalues WBTC collateral by a factor of 10^10 (treating 1 WBTC as $0.00000001 instead of its true USD value). Inversely, _get_token_amount_from_usd overestimates required WBTC tokens by 10^10, causing liquidation math to revert or demand astronomical token amounts.

@internal
@view
def _get_usd_value(token: address, amount: uint256) -> uint256:
...
@> return (
@> (convert(price, uint256) * ADDITIONAL_FEED_PRECISION) * amount
@> ) // PRECISION
@internal
@view
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
@> )
@> )

Risk

Likelihood:

This bug manifests immediately whenever a user deposits WBTC, attempts to mint DSC against WBTC collateral, or when liquidators attempt to liquidate WBTC collateral.

Impact:

High. Complete breakdown of protocol accounting for WBTC. Users depositing WBTC receive virtually zero borrowing power, and any liquidation involving WBTC either reverts due to arithmetic overflow or drains liquidity.

Proof of Concept

Calculating USD value for 1 WBTC (10^8 units) at a BTC price of $60,000 yields 60,000 * 10^8 instead of the expected 18-decimal value 60,000 * 10^18, understating collateral by 10 orders of magnitude.

def test_wbtc_decimals_mismatch(dsce, wbtc, btc_usd):
# BTC price is $60,000 (60,000 * 10^8 in Chainlink feed)
# 1 WBTC has 8 decimals = 1 * 10**8
wbtc_amount = 1 * (10**8)
usd_val = dsce.get_usd_value(wbtc, wbtc_amount)
# Expected: 60_000 * 10**18 (in 18 decimal USD wei)
# Actual returned: 60_000 * 10**8 (undervalued by 10^10)
assert usd_val != 60_000 * (10**18)

Recommended Mitigation

Dynamically fetch token decimals from the ERC20 contract or store the token decimals in a mapping, adjusting amount or precision accordingly before scaling to 18 decimals.

@internal
@view
def _get_usd_value(token: address, amount: uint256) -> uint256:
...
+ decimals: uint256 = convert(staticcall IERC20Detailed(token).decimals(), uint256)
+ token_precision: uint256 = 10**decimals
- return ((convert(price, uint256) * ADDITIONAL_FEED_PRECISION) * amount) // PRECISION
+ return ((convert(price, uint256) * ADDITIONAL_FEED_PRECISION) * amount) // token_precision
Updates

Lead Judging Commences

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