For educational purposes only; not investment advice. Investing may result in loss.
Direct answer
An oracle decimal mismatch occurs when a contract interprets a raw integer using the wrong unit exponent, price direction, or internal scale. Feed decimals, token decimals, quote-token decimals, wrapper or share exchange-rate scales, and protocol WAD or RAY conventions are independent. A correct-looking frontend does not prove that the consumer contract performs the same arithmetic.
For a base-token amount A_raw with d_t decimals and a positive feed answer P_raw quoting one base token in quote tokens with d_p decimals, a quote-token raw amount with d_q decimals is V_raw = round(A_raw * P_raw * 10^d_q / 10^(d_t + d_p)). That formula is valid only for the stated quote direction and after validating the feed, units, types, and rounding rule. An adapter that has already normalized a value must not be scaled again.
ERC-20 decimals() is optional display metadata, not a universal 18-decimal guarantee. A feed’s decimals() describes that feed response, not the token or protocol scale. Checked arithmetic can stop overflow by reverting, but it cannot detect a dimensionally wrong formula and may turn an otherwise representable result into denial of service if an intermediate product overflows.
How it works
- Pin the chain, block, consumer, adapter, feed proxy and aggregator, token or vault, compiler and math-library versions, and upgrade state.
- Inventory every integer with its type and unit: raw token amount, token decimals, signed feed answer, feed decimals, base and quote orientation, wrapper or share rate, protocol internal scale, and output-token decimals.
- Validate the source before conversion: correct address and direction, positive answer, timestamp and status, acceptable age and range, fallback semantics, and L2 sequencer grace rules where applicable.
- Derive one dimensional formula from input raw units to output raw units. Branch explicitly when scaling up or down, bound every power-of-ten exponent, and normalize each leg exactly once.
- Use a proved full-precision multiply-divide or a bounded cancellation strategy. Avoid divide-before-multiply truncation, checked intermediate overflow, unchecked wraparound, and unsafe signed or narrow casts.
- Specify floor, ceiling, or nearest rounding for each economic action. Collateral, debt, borrowing, minting, redemption, fees, liquidation, and vault share conversions can require different conservative directions.
- Test golden vectors and properties across extreme amounts, decimal combinations, reciprocals, composite feeds, zero, negative and stale answers, upgrades and dust; reconcile onchain results against an independent high-precision model and cap affected exposure.
Normalization is dimensional analysis, not formatting. BTC/USD and USD/BTC require reciprocal formulas; changing a label does not invert a price. Composite feeds require every leg’s scale and timestamp. Solidity integer division truncates toward zero, so algebraically equivalent rearrangements can produce different onchain results. Full-precision mulDiv solves an intermediate-width problem, but callers must still supply the right numerator, denominator, units, bounds, and rounding direction.
Worked examples
- Eight versus eighteen decimals. A BTC/USD feed returns
6,000,000,000,000withd_p = 8, so the price is60,000 USD/BTC. Treating the raw answer as 18 decimals gives0.000006 USD/BTC, an undervaluation by10^10, not10^9. Scaling the price to WAD gives6,000,000,000,000 * 10^(18 - 8) = 60,000,000,000,000,000,000,000. - Amount, price, and output units.
A_raw = 2,500,000represents2.5tokens atd_t = 6;P_raw = 200,000,000represents2quote tokens atd_p = 8. For an 18-decimal internal quote value,2,500,000 * 200,000,000 * 10^18 / 10^(6 + 8) = 5,000,000,000,000,000,000, or5 quote tokens. Omitting the token denominator overvalues the position by10^6. - Reciprocal and truncation. ETH/USD at WAD scale is
2,000 * 10^18. USD/ETH at the same scale isfloor(10^36 / (2,000 * 10^18)) = 500,000,000,000,000, or0.0005 ETH/USD. Separately, withA_raw = 999,999,d_t = 6, and price2 * 10^18, full multiply-divide yields1,999,998,000,000,000,000; dividing the amount by10^6first yields0and loses all value. - Intermediate overflow and rounding. Let
x = 2^200,y = 2^100, and denominator2^100. The exact result is2^200, which fitsuint256, butx * y = 2^300does not. Checked multiplication reverts and unchecked multiplication wraps; a full-precisionmulDivreturns2^200. Integer5 / 2floors to2, while a ceiling rule returns3, so rounding is part of the economic invariant.
Risks
- Wrong chain, feed, proxy, adapter, token, vault, or consumer address.
- Base and quote orientation is reversed without reciprocal conversion.
- Token decimals are assumed to be 18 or optional metadata is unavailable or wrong.
- Feed decimals are assumed to be 8 instead of read and pinned.
- Quote-token decimals and protocol WAD, RAY, market, or accounting scale are confused.
- Wrapper, share, index, or exchange-rate decimals are omitted.
- A scale factor is applied twice after an adapter already normalized the value.
- A required scale factor or denominator is omitted.
- A signed answer is cast to unsigned before checking that it is positive.
- A zero, stale, incomplete, capped, or invalid feed result is accepted.
- A decimal exponent or power-of-ten calculation underflows, overflows, or exceeds bounds.
- A multiplication intermediate overflows even though the final quotient would fit.
- Unchecked arithmetic, bit shifts, or explicit narrowing silently wraps or truncates.
- Division before multiplication destroys precision or turns dust into zero.
- Floor, ceiling, or nearest rounding is wrong for the economic action.
- Repeated conversions accumulate precision loss or systematic value leakage.
- Prices, caps, ratios, percentages, basis points, WAD, and RAY values are compared in different units.
- Feed, token, proxy, adapter, or vault upgrades invalidate cached decimal assumptions.
- Frontend, wallet, RPC, or indexer formatting hides a different onchain calculation.
- Misvaluation amplifies borrowing, minting, redemption, liquidation, caps, bad debt, or unfair share issuance.
Common misconceptions
- “Every ERC-20 token uses 18 decimals.” The ERC-20 metadata method is optional, and deployed assets use different values and behaviors.
- “Every USD price feed uses 8 decimals.” Feed precision is an interface property of the exact deployment and must be read and versioned.
- “A large raw integer proves manipulation.” Raw magnitude is meaningless without units, direction, decimals, timestamp, and consumer scale.
- “Solidity 0.8 makes scaling correct.” Checked overflow can revert, but it does not repair wrong units, truncation, casting, or rounding policy.
- “Multiplying before dividing or adding more decimals always improves accuracy.” It can overflow, double-scale, or preserve the wrong unit; full-precision arithmetic still needs a correct formula.
Related topics
Sources
- Data Feeds API Reference - Chainlink Documentation (accessed: 2026-08-13)
- Chainlink Data Feeds - Chainlink Documentation (accessed: 2026-08-13)
- ERC-20: Token Standard - Ethereum Improvement Proposals (accessed: 2026-08-13)
- Types - Solidity Documentation (accessed: 2026-08-13)
- Expressions and Control Structures - Solidity Documentation (accessed: 2026-08-13)
- Utils - OpenZeppelin Contracts Documentation (accessed: 2026-08-13)
- Oracles - Aave Protocol Documentation (accessed: 2026-08-13)
- Compound v2 Price Feed - Compound Documentation (accessed: 2026-08-13)