Unit-Domain Seam Typing and Explicit Conversion Discipline

  • Name: unit-domain seam typing and explicit conversion discipline
  • Category: accounting-and-oracle reference / dimensional-review checklist / module-seam conversion discipline
  • Summary: A healthy protocol never asks reviewers, operators, or downstream code to guess what one raw integer means after it crosses a boundary. uint256 amount is not a solvent interface. The design target is to make every domain crossing explicit: one named source quantity, one named destination quantity, one conversion owner, one observable transform, and one post-condition in the caller’s economic domain before any later module is allowed to spend against the result. This note exists to preserve the positive-design layer underneath incidents like bEarn, Tender.fi, Blueberry, Morpho PAXG, and Moonwell: the bug was not only wrong math or wrong config, but that an ambiguous number was allowed to become accounting or solvency truth without one explicit typed seam proving what changed.

Why this note exists

  • cross-module asset-denomination mismatch preserves the internal-protocol version: one module meant underlying assets, another meant receipt shares, and the protocol never forced a typed conversion boundary between them.
  • decimal normalization failure preserves the oracle version: one branch returned a quantity that was valid in its own domain, but the protocol treated it as if it were already the final solvency-domain output.
  • price-domain-and-reciprocal-range-discipline preserves the downstream pricing and reciprocal side once the market is already consuming a final published price object.
  • What still deserves its own positive-design note is the seam discipline that sits underneath all of them: before one number changes meaning, who owns that conversion, where is the source domain named, where is the destination domain named, and what exact artifact proves the transform happened once rather than being guessed, repeated, or skipped?

What this note means by unit-domain seam typing

  • Unit domain means the economic meaning attached to a quantity: underlying assets, receipt shares, LP units, scaled debt, reserve balance, exchange-rate ratio, asset / ETH, asset / USD, reciprocal borrow cost, or another explicitly named object.
  • Seam means the boundary where that quantity leaves one domain owner and enters another: vault to strategy, adapter to core market, oracle branch to final publisher, migration helper to replacement ledger, or governance payload to live parameter state.
  • Seam typing means both sides of that boundary name the quantity they expect, not just amount.
  • Explicit conversion means one specific component owns the transform between source and destination domains and can show both values before downstream code relies on either.
  • The core rule is simple: no ambiguous integer should be allowed to become reserve truth, collateral truth, borrow truth, redemption truth, or reward truth.

Three quantity promotions reviewers should not collapse

1. A local module can produce a number that is valid in its own domain

  • A strategy can report shares, an oracle branch can report asset / ETH, and a wrapper can report exchange rate.
  • This proves only a number exists that is locally meaningful to its producer.
  • It does not yet prove that the next consumer wants that same domain.

2. The protocol can convert or reinterpret that number as canonical local accounting truth

  • The stronger promotion is that some seam owner has decided this source-domain quantity now maps to this destination-domain quantity.
  • This is where dimensional mistakes stop being innocent engineering ambiguity and become accounting policy.
  • The useful review question is not does the number look plausible? but who explicitly proved the new meaning?

3. Downstream code can spend against that converted meaning

  • The last promotion is that minting, borrowing, liquidation, reserve publication, reward issuance, or redemption logic is allowed to act on the converted quantity as if it were money-good.
  • Most severe incidents pay only because the protocol collapsed step 2 and step 3: an ambiguous or partially converted number was not merely stored, it was immediately granted economic force.

Healthy designs keep these promotions separate. Producer has a number, seam owner converted it, and other modules may now spend against it should not be one ambient blur.

The main design rules

1. Every seam names both sides of the quantity transform

  • Reviewers should be able to write the boundary as a sentence:
    • strategy reports ibBUSD shares -> bank needs BUSD assets
    • oracle branch reports cbETH/ETH -> market needs cbETH/USD
    • reserve stores scaled debt -> liquidator path needs actual debt assets
  • If the seam cannot be written that clearly, it is under-specified.
  • Generic interfaces like withdraw(uint256 amount) or getPrice() are only safe when the unit domain is made explicit somewhere stronger than reviewer memory.

2. One component owns the conversion, and it happens exactly once

  • A safe system does not let the caller assume one domain, the callee assume another, and a later helper silently patch the difference.
  • The conversion owner should be singular and legible: adapter, oracle composer, vault seam, market preview helper, or another named boundary object.
  • The dangerous failure shape is everyone thought someone else was converting it.

3. Source quantity and destination quantity should both stay observable

  • The seam should preserve both the upstream and downstream meanings long enough for review, testing, and telemetry.
  • Good designs expose some combination of:
    • assetsIn
    • sharesOut
    • sourcePrice
    • normalizedPrice
    • scaledAmount
    • actualAmount
  • A protocol gets much harder to audit when one raw integer is repeatedly overwritten and later readers cannot recover what it used to mean.

4. Post-conditions should be stated in the caller’s economic domain

  • It is not enough that the callee did something internally coherent.
  • If the caller asked for X assets, the seam should prove what shares were burned, what assets were actually realized, and whether the caller’s X assets claim was truly satisfied.
  • If the lending engine needs asset / USD, the seam should prove the published output is actually that domain, not just a nonzero ratio from some nearby path.

5. Downstream consumers should distrust freshly converted quantities until the seam is proven

  • A converted number may be locally explainable before it deserves broader solvency authority.
  • This matters for:
    • fresh oracle branches
    • wrapper migrations
    • strategy swaps
    • reserve migrations
    • new adapter paths
    • patched math under incident response
  • The right posture is often: first compute and expose the converted value, then separately decide when collateral, liquidation, or redemption logic may trust it.

6. Migrations and upgrades should be treated as re-typing events

  • If an implementation changes what one storage slot, adapter output, preview function, or reserve variable means, the protocol has changed the seam type even if the variable name stayed the same.
  • That deserves the same review seriousness as a new integration.
  • A large share of recurring incidents are simply stale type assumptions surviving across upgrades, wrappers, or config changes.

What sound design looks like

1. Seams read like dimensional algebra, not folklore

  • The protocol can produce one short artifact for each important boundary:
    • source domain
    • destination domain
    • conversion formula
    • scale / decimals
    • rounding direction
    • post-condition expected by the next consumer
  • Reviewers do not have to reconstruct semantics from several comments and variable names after the fact.

2. Interfaces encode meaning rather than hiding it

  • Better surfaces look like depositAssets, redeemShares, previewWithdrawAssets, normalizeToUsd, or scaledDebtToAssets.
  • Even when the language or ABI cannot create a real static type, the protocol can still create a semantic type discipline through names, structs, events, and invariant tests.
  • The goal is not cosmetic verbosity. It is to make the wrong interpretation feel obviously illegal.

3. One seam owner proves both the conversion and the remaining error budget

  • Good boundaries say not only how did we convert? but also what rounding or approximation remains?
  • This matters because many exploit paths monetize the leftover slack after a supposedly harmless conversion.
  • If the seam uses preview math, scale normalization, or share-price reads, it should also state what downstream tolerances are acceptable.

4. Fresh conversions can be locally visible before they become globally authoritative

  • A healthy protocol can distinguish:
    • local publication for accounting or monitoring,
    • local exit semantics,
    • full collateral or borrowing authority.
  • This keeps one newly normalized or newly migrated quantity from becoming instant systemwide truth before the seam has been pressure-tested.

5. Impossible domain states are encoded explicitly

  • Examples:
    • asset/ETH may not be published into a market expecting asset/USD
    • shares may not satisfy an assets request without an explicit preview conversion
    • a reciprocal or decimal-normalized output may not be zero if the supported domain excludes zero
    • a strategy leftover may not be rebooked as yield until the seam proves its provenance and denomination
  • Good designs reject these states directly instead of narrating them as things that should never happen.

Useful positive design shapes

1. Seam sheets for high-value quantity boundaries

  • For every high-value seam, keep one short review artifact with:
    • source quantity name
    • source scale and semantics
    • destination quantity name
    • destination scale and semantics
    • conversion owner
    • rounding rule
    • downstream consumer
    • explicit impossible states
  • This is the accounting / integration equivalent of a good oracle domain sheet.

2. Preview-first, effect-second boundaries

  • When possible, the seam should offer an explicit preview surface in the caller’s domain before mutation happens.
  • The practical benefit is not only UX. It forces the conversion object to exist as a first-class artifact instead of as an implicit side effect hidden inside state mutation.
  • If the preview and execution paths disagree, that disagreement is already a seam bug worth stopping on.

3. Event logs and telemetry that preserve both meanings

  • Emit or record both the source and destination quantities.
  • Example pairs:
    • assetsRequested, sharesBurned
    • rawPrice, normalizedPrice
    • scaledAmount, actualAssets
    • donatedBalance, recognizedReserveIncrease
  • This makes incident detection, audit replay, and postmortem work materially easier.

4. Typed quarantine at boundary changes

  • When a new seam becomes live, the protocol should be able to say this quantity is locally computed but not yet globally trusted.
  • Quarantine can mean collateral factor 0, borrow caps, disabled recursion, paused liquidation, or reconciliation-only accounting until the seam proves stable.

Negative exemplar cluster: what missing seam typing looks like

  • ibBUSD — the bank asked in BUSD terms, the strategy acted in ibBUSD terms, and no explicit seam owner forced the conversion into one typed boundary before leftover value was rebooked as yield.
  • Tender.fi tGMX — the oracle path published a syntactically valid number, but the seam between branch-local value and final market value was under-typed and double-scaled.
  • Blueberry Protocol — one component returned a 1e18-scaled price object and another consumer expected a different normalization domain, but the boundary did not force one explicit re-typing artifact before borrowing went live.
  • USDC — permissionless configuration let a valid local quote become final market truth without a strong enough seam-level proof of decimal and domain meaning.
  • USD — a cbETH / ETH quantity crossed a seam into a slot that demanded cbETH / USD, and the missing typed conversion object turned one nearby ratio into bad-debt liquidations.
  • The shared lesson is not merely someone used the wrong number. It is that the protocol never forced one named boundary to prove what the number meant before economic authority attached.

Red flags that usually mean the seam is underdesigned

  • amount appears on both sides of an interface, but reviewers cannot say whether it means assets, shares, scaled units, or normalized price.
  • One path uses preview, another path re-implements conversion math locally, and a third path trusts storage counters as if all three were the same domain.
  • Comments explain the unit semantics, but events, function names, and tests do not.
  • Operators can supply decimals, quote legs, or scale constants manually without one artifact that shows the full source-to-destination transform.
  • A later sync, sweep, reserve update, or harvest helper can reinterpret leftover balances or intermediate values into canonical truth.
  • The protocol fixes a rounding bug, but still has no explicit owner for the earlier domain conversion that made the rounding edge valuable.
  • Upgrade notes say same interface even though the underlying quantity meaning changed.

Design questions worth asking first

  • What exact quantity domain does the producer mean, and what exact quantity domain does the consumer require?
  • Where is the one explicit conversion between those domains, and who owns it?
  • Does the system preserve both the source value and the converted value long enough for review and telemetry, or does one raw integer silently change meaning in place?
  • Which downstream modules act on the converted value, and do they know whether they are consuming local accounting truth or full economic authority?
  • If the conversion is wrong by one quote leg, one decimal exponent, one exchange-rate interpretation, or one rounding direction, what concrete control catches it before real money moves?
  • During upgrades and migrations, what proves the stored quantity still means what downstream code thinks it means today?

Failure-prevention checklist

  • Write a seam sheet for every high-value boundary that names the source domain, destination domain, scale, conversion formula, rounding direction, and downstream consumer.
  • Ban generic quantity ambiguity on solvency-critical interfaces. If the path works in assets, shares, ratios, scaled balances, or normalized prices, say so in function names, structs, events, and tests.
  • Assign one explicit conversion owner per seam. Do not let callers, callees, and later helpers each guess the transform independently.
  • Preserve source and destination meanings in telemetry. Emit or log both quantities so reviewers can reconstruct the seam after the fact.
  • Require post-conditions in the caller’s economic domain, not only in the callee’s local accounting domain.
  • Treat freshly converted values as locally visible before globally authoritative. Use quarantine, caps, or delayed activation when a new seam is going live.
  • Re-run seam typing review on upgrades, wrapper replacements, oracle migrations, parameter rewires, and reserve migrations. Same ABI does not mean same quantity meaning.
  • Reject impossible domain states directly: wrong quote asset, unsupported decimal range, zero reciprocal, unexplained leftovers, or share-versus-asset mismatch should fail closed.
  • Pair seam review with rounding review. A well-named conversion still needs explicit bounds on truncation, approximation, and low-unit edge cases.
  • Add invariant tests that round-trip across the seam: assets → shares → assets, raw feed → normalized price, scaled debt → actual assets → scaled debt, and similar boundary loops should not create unexplained surplus or deficit.

Audit prompts

  • Can the protocol write one sentence describing the source and destination domains at this seam without relying on tribal knowledge?
  • Which component is the sole owner of the conversion, and what proves no sibling path reinterprets the same number differently?
  • If the consumer expects assets, where is the proof that it did not receive shares, LP units, scaled balance, or a partial quote leg instead?
  • What evidence remains after execution that the conversion happened as intended: events, previews, state deltas, or only comments?
  • Are there any helpers, migrations, or fallback branches that bypass the reviewed seam and publish a different meaning under the same variable or function name?
  • If the conversion were off by 10^6, one missing quote leg, or one receipt-share interpretation, which guard would stop the result from becoming borrow, mint, or redemption truth?
  • Does the protocol distinguish locally converted and visible from safe for collateral, liquidation, or recursive borrow use?
  • Which domain assumptions would break first if the upstream wrapper, token semantics, or oracle source changed tomorrow without changing the interface shape?

Lessons that should already be standard

  • uint256 is storage width, not economic meaning.
  • A number that is correct in its producer’s domain can still be catastrophically false in the consumer’s domain.
  • The most dangerous conversions are the ones nobody explicitly owns.
  • Generic names like amount, price, balance, and value are often exploit surfaces when they cross boundaries without a typed seam artifact.
  • Local accounting coherence is weaker than economic coherence. A module can be internally consistent and still publish the wrong quantity to the next consumer.
  • Upgrades and migrations routinely change quantity meaning without changing interface shape. Review should treat that as a fresh seam, not as a maintenance detail.
  • If a protocol cannot show both the source quantity and the converted quantity, it is much easier for mistakes to survive into production and much harder to unwind them once they do.
  • Many math bugs are really meaning bugs: the arithmetic only looked reasonable because the protocol never forced itself to say what the operands were.

How this note complements the exploit corpus

Internal linkages

Control surface