Skip to main content
Sigvex

Decimal Mismatch

Detects arithmetic operations that mix values with different decimal precision without normalization, causing massive over- or under-payments.

Decimal Mismatch

Overview

Remediation Guide: How to Fix Decimal Mismatch

The decimal mismatch detector identifies arithmetic operations that combine values from tokens or price feeds with different decimal precision without applying normalization. ERC-20 tokens use varying decimal counts (USDC uses 6, DAI uses 18, WBTC uses 8), and aggregated price feed return values with 8 decimals. Mixing these values directly in multiplication, division, or comparison produces results that are off by orders of magnitude.

Sigvex traces return values from external calls (likely token operations or oracle queries) through subsequent arithmetic, looking for operations that combine two externally-sourced values without an intervening scaling step (multiplication or division by a power of 10).

Why This Is an Issue

Decimal mismatches have caused some of the largest DeFi losses. When a bridge mints wrapped tokens on a 6-decimal chain using an 18-decimal source value without conversion, it mints 10^12 times too many tokens. When a lending protocol calculates collateral ratios by dividing an 18-decimal token balance by an 8-decimal price feed, the result is 10^10 times too large, allowing under-collateralized borrowing.

Common scenarios:

  • Bridge transfers: Source chain uses 18 decimals, destination uses 6 — missing conversion results in 10^12x over-minting.
  • Oracle price calculations: the off-chain aggregator returns 8 decimals; contract assumes 18 — prices are off by 10^10.
  • Multi-token pools: Mixing USDC (6) with DAI (18) in arithmetic without normalizing to a common base.
  • Reward distributions: Staking token has 18 decimals, reward token has 6 — rewards calculated incorrectly.

How to Resolve

// Before: Vulnerable — mixing 6-decimal USDC with 18-decimal DAI
function calculateValue(uint256 usdcAmount, uint256 daiAmount) public view returns (uint256) {
    return usdcAmount + daiAmount; // Off by 10^12
}

// After: Fixed — normalize to common decimal base
function calculateValue(uint256 usdcAmount, uint256 daiAmount) public view returns (uint256) {
    uint256 normalizedUsdc = usdcAmount * 1e12; // Scale 6 decimals to 18
    return normalizedUsdc + daiAmount;
}

Examples

Vulnerable Code

contract VulnerableLending {
    IERC20 public usdc;   // 6 decimals
    IERC20 public weth;   // 18 decimals
    AggregatorV3Interface public ethUsdFeed; // 8 decimals

    function getCollateralValue(uint256 ethAmount) public view returns (uint256) {
        (, int256 price,,,) = ethUsdFeed.latestRoundData();
        // BUG: ethAmount (18 decimals) * price (8 decimals) = 26 decimals
        // Compared against USDC debt (6 decimals) — off by 10^20
        return ethAmount * uint256(price);
    }
}

Fixed Code

contract SafeLending {
    IERC20 public usdc;   // 6 decimals
    IERC20 public weth;   // 18 decimals
    AggregatorV3Interface public ethUsdFeed; // 8 decimals

    function getCollateralValue(uint256 ethAmount) public view returns (uint256) {
        (, int256 price,,,) = ethUsdFeed.latestRoundData();
        // Normalize: (18 + 8 - 6) = 20 extra decimals to remove
        return (ethAmount * uint256(price)) / 1e20;
    }
}

Sample Sigvex Output

{
  "detector_id": "decimal-mismatch",
  "severity": "critical",
  "confidence": 0.82,
  "description": "Arithmetic operation at offset 0x1a4 combines return values from two external calls without decimal normalization. The operands likely have different decimal precision.",
  "location": { "function": "getCollateralValue(uint256)", "offset": 420 }
}

Detection Methodology

  1. Track external call returns: Identifies CALL or STATICCALL instructions whose return values flow into arithmetic operations.
  2. Identify arithmetic on mixed sources: Flags ADD, SUB, MUL, DIV operations where both operands originate from different external calls (likely different token contracts or price feeds).
  3. Check for scaling operations: Searches for multiplication or division by powers of 10 (constants matching 10^n for common decimal differences: 10^6, 10^8, 10^12, 10^18) between the external call and the arithmetic.
  4. Confidence scoring: Higher confidence when no scaling is present; reduced confidence when partial scaling exists or when the function name suggests awareness of decimals.

Limitations

False positives:

  • Contracts that use a unified internal decimal representation and convert on input/output may be flagged during the conversion step.
  • Functions that only operate on a single token type (no mixing) may be flagged if the return value analysis cannot distinguish token identity.

False negatives:

  • Dynamic decimal lookups (calling decimals() and using the result for scaling) may not be recognized as normalization.
  • Contracts that store pre-normalized values in mappings and operate on them later may not be flagged.

References