Skip to content

Oracle decimal mismatches

A decimal mismatch applies the wrong unit scale to an oracle answer, token amount, wrapper rate, or protocol value, causing orders-of-magnitude errors in borrowing, minting, redemption, and liquidation.

Updated

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

  1. Pin the chain, block, consumer, adapter, feed proxy and aggregator, token or vault, compiler and math-library versions, and upgrade state.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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,000 with d_p = 8, so the price is 60,000 USD/BTC. Treating the raw answer as 18 decimals gives 0.000006 USD/BTC, an undervaluation by 10^10, not 10^9. Scaling the price to WAD gives 6,000,000,000,000 * 10^(18 - 8) = 60,000,000,000,000,000,000,000.
  • Amount, price, and output units. A_raw = 2,500,000 represents 2.5 tokens at d_t = 6; P_raw = 200,000,000 represents 2 quote tokens at d_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, or 5 quote tokens. Omitting the token denominator overvalues the position by 10^6.
  • Reciprocal and truncation. ETH/USD at WAD scale is 2,000 * 10^18. USD/ETH at the same scale is floor(10^36 / (2,000 * 10^18)) = 500,000,000,000,000, or 0.0005 ETH/USD. Separately, with A_raw = 999,999, d_t = 6, and price 2 * 10^18, full multiply-divide yields 1,999,998,000,000,000,000; dividing the amount by 10^6 first yields 0 and loses all value.
  • Intermediate overflow and rounding. Let x = 2^200, y = 2^100, and denominator 2^100. The exact result is 2^200, which fits uint256, but x * y = 2^300 does not. Checked multiplication reverts and unchecked multiplication wraps; a full-precision mulDiv returns 2^200. Integer 5 / 2 floors to 2, while a ceiling rule returns 3, 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.

Sources

Navigation

Search the wiki...