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 Chainlink price feeds 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: Chainlink 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
- Track external call returns: Identifies
CALLorSTATICCALLinstructions whose return values flow into arithmetic operations. - Identify arithmetic on mixed sources: Flags
ADD,SUB,MUL,DIVoperations where both operands originate from different external calls (likely different token contracts or price feeds). - Check for scaling operations: Searches for multiplication or division by powers of 10 (constants matching
10^nfor common decimal differences: 10^6, 10^8, 10^12, 10^18) between the external call and the arithmetic. - 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.
Related Detectors
- Precision Errors — detects rounding and precision loss in arithmetic
- Oracle Manipulation — detects oracle price feed manipulation
- Chainlink Staleness — detects stale Chainlink price data