Settled-State Read Surface Discipline / Safe-Read Oracle Interfaces

  • Name: settled-state read surface discipline / safe-read oracle interfaces
  • Category: integration-safety reference / read-only-reentrancy companion / valuation-interface design note / quiescent-state read checklist
  • Summary: If a protocol’s accounting can be transiently inconsistent inside callback windows, it should not force integrators to guess which view reads are safe. Healthy upstream design exposes an explicit settled-state read surface: a public context probe, a safe valuation helper that reverts or refuses service mid-transition, a delayed or snapshotted read path, or enough machine-readable guidance that downstream protocols do not mistake view for quiescent. Just as importantly, it keeps three authority steps separate: raw state is visible, the protocol has published one canonical settled-state valuation object, and only later downstream solvency consumers may spend against that object. This note exists to keep that positive design target explicit instead of leaving readers to infer it only by inverting Balancer-style docs, upstream hardening examples like Basin’s getReserves() fail-closed patch, or integrator losses like Rari, Sentiment, Sturdy, Conic, and EraLend. delayed-snapshots-and-same-block-quarantine is the narrower companion when the honest answer is that no safe same-transaction valuation surface exists, so downstream consumers should be forced onto a delayed settled object instead.
  • Why this note exists:
    • Read-only reentrancy often gets narrated as an integrator mistake, but many integrators were never given a clean upstream answer to is this read safe right now?.
    • A protocol that knows its own reserve, supply, or liability state can be inconsistent mid-transition is the natural place to publish safe-read semantics.
    • Many integrations also collapse we can still observe raw state, the protocol has published one supported settled-state read, and lenders, liquidators, or routers may already trust that read as money-good truth into one event. This note exists to keep those promotions separate.
    • The useful design question is often not just how should downstream protocols defend? but what explicit settled-state object or context signal should the upstream protocol have exposed before anyone priced against it?

The five joints a healthy safe-read surface should keep fused

  1. Public transition-context signal

    • The upstream protocol should expose whether a callback-sensitive state transition is currently live.
    • The important object is not is there a reentrancy guard somewhere in the codebase? but can an integrator prove this valuation read is happening outside the unsafe window?
    • Balancer’s VaultReentrancyLib.ensureNotInVaultContext(vault) is the clean reference shape.
  2. Explicit safe-versus-raw read boundary

    • The protocol should distinguish raw accounting helpers from integration-safe valuation helpers.
    • If getRate(), get_virtual_price(), reserve reads, or LP-share math are unsafe during callbacks, that fact should be encoded in interface behavior or naming rather than left to folklore.
    • A healthy surface lets downstream users say this helper is safe only after settlement without reverse-engineering the whole pool.
  3. Fail-closed behavior during unsafe windows

    • Sensitive valuation helpers should revert, return a clearly unusable state, or force callers through a context check when the system is mid-transition.
    • view does not mean safe if downstream protocols will immediately monetize the result.
    • Healthy design prefers explicit refusal over optimistic reads plus advisory documentation.
  4. Settled-state valuation object

    • The best upstream systems expose a read surface whose claim on truth is this value only exists once the matching accounting legs have converged.
    • That may mean a guarded live helper, a post-settlement snapshot, a delayed rate cache, or another object whose entire meaning is quiescent-state valuation rather than whatever reserves look like right now.
    • The key is that the protocol chooses the settled object once, instead of every integrator inventing its own approximation.
  5. Integration guidance that survives pool-type or metadata drift

    • Safe-read guarantees should not hinge on fragile token metadata or pool-shape heuristics unless those heuristics are themselves part of a documented interface contract.
    • Conic is the reminder that an _isETH() branch tied to an ETH sentinel does not really publish safe-read semantics for a live WETH pool.
    • If the guard only works when the integrator guessed the pool taxonomy correctly, the upstream safe-read surface is still underspecified.

Three read-authority promotions reviewers should not collapse

  1. Raw or transitional state is visible

    • A reserve ratio, virtual price, exchange rate, or wrapper valuation can still be read somewhere during execution.
    • That fact alone only answers can someone observe a number right now? It does not yet answer whether the protocol stands behind that number as settled truth.
  2. The protocol publishes one canonical settled-state read surface

    • The upstream system exposes a guarded helper, explicit context probe, delayed snapshot, or other supported object whose whole contract is this is the valuation surface we are willing to defend as settled enough for integrations.
    • This is already a stronger claim than the raw state is readable, but it is still not automatically the same thing as every downstream consumer may act on it immediately in every mode.
  3. Downstream solvency consumers spend against that published surface

    • Borrowing, liquidation, deleveraging, collateral toggles, route selection, reserve spending, or sibling-market accounting may now honor that published object as money-good authority.
    • This still deserves its own review boundary. The protocol published a safe read surface is not yet the same thing as every downstream consumer is entitled to turn it into immediate debt, liquidation, or payout truth.

The recurring design target is to answer three separate questions cleanly: what raw state remained observable?, what exact settled-state read object did the protocol publish for integrations?, and which downstream consumers were actually allowed to spend against that object right away?

What sound design looks like

  • Observation, publication, and downstream authority stay distinct

    • The protocol can expose raw state for local execution or analytics, publish one narrower settled-state read surface for integrations, and still stage which downstream consumers may honor that surface immediately.
    • Healthy designs do not flatten readable somewhere, published as supported truth, and already spendable by every solvency consumer into one state transition.
  • Unsafe windows are machine-detectable

    • Integrators should be able to ask a direct question like is the upstream protocol in a callback-sensitive context right now?.
    • If the answer is yes, sensitive reads should fail or be skipped.
  • The protocol publishes one canonical settled-state read path

    • Upstream systems should not make every lender, router, or liquidation engine invent its own version of safe virtual price or safe LP valuation.
    • The healthier pattern is one protocol-authored read surface whose whole purpose is settled-state consumption.
  • Unsafe reads fail harder than docs warn

    • Documentation matters, but a design that only says don't call this during callbacks still leaves too much room for drift, forgotten edge paths, and new integrators.
    • Reverting unsafe views, or requiring a context probe before valuing, is stronger than prose alone.
  • Context checks cover every solvency-changing consumer

    • Borrow, liquidation, collateral-toggle, deleverage, and closeout paths all need the same safe-read discipline.
    • Sturdy is the reminder that partial coverage still leaves one path that turns transient state into money.
    • Even after the protocol publishes a canonical safe-read surface, downstream activation still needs to be intentional: some consumers may deserve extra delay, tighter caps, or separate quarantine before they spend against it.
  • Metadata does not secretly become the trust boundary

    • If safety depends on ETH versus WETH, wrapper flavor, or pool-version labels, the protocol should publish that dependency explicitly and test it like a security boundary.
    • Otherwise the safest interpretation is that the read surface was never truly canonical.

Useful positive design shapes

1. Public context probe plus guarded valuation helper

  • Expose a cheap public probe or library check that tells integrators whether the protocol is currently mid-transition.
  • Make the canonical valuation helper revert or refuse service when that probe says the system is unsafe.
  • This is the cleanest answer when live reads are acceptable only outside callback windows.

2. Raw-versus-safe interface split

  • Keep low-level reserve or accounting reads available for internal or analytical use, but pair them with a separately named safe helper whose contract is settled-state only.
  • The safe helper should do the context gating itself rather than expecting every downstream caller to remember it.

3. Post-settlement snapshot or delayed-rate surface

  • If the protocol cannot safely expose an on-demand quiescent read during the same transaction, it should prefer an explicitly delayed or snapshotted valuation object over raw live reserves.
  • The design question is not is delay ugly? but what object can downstream solvency logic trust without reconstructing your internal transition rules?
  • delayed-snapshots-and-same-block-quarantine is the narrower companion when the real design move is to quarantine same-block consumers entirely rather than trying to salvage a live safe-read helper.

4. Revert unsafe views, not only unsafe writes

  • Reentrancy guards that protect mutations but leave sensitive valuation views callable mid-transition only solve half the problem.
  • If downstream protocols monetize the view, then the view is part of the critical state machine.
  • Basin’s May 2023 getReserves() patch is the compact implementation exemplar: once the team accepted that reserve publication itself was the dangerous surface, the right fix was to make the view revert whenever the Well was already inside a guarded state-changing flow.

5. Documented integration envelope with versioned caveats

  • The upstream protocol should publish which helpers are unsafe during callbacks, which guards must be checked, and which pool or wrapper variants share the same rules.
  • If later pool versions, wrappers, or adapters change that envelope, the guidance should version with them instead of relying on tribal knowledge.

6. Downstream fail-closed fallback when no safe surface exists

  • If the upstream protocol cannot expose a clean safe-read primitive yet, the safest published guidance may be do not use same-transaction live reads for solvency or liquidation.
  • ChainSecurity’s Curve LP writeup shows why lock-probing workarounds exist, but also why they are still second-best to a real protocol-authored safe-read surface.

Choosing the remedy: safe live read or delayed settled object?

  • Choose a guarded live-read surface when the protocol can prove one crisp unsafe window and can expose that window directly. Basin is the clean positive exemplar: if the only problem is this reserve read is unsafe while the Well is already inside a guarded state-changing frame, then a public context check or a reverting getReserves() is a real interface improvement rather than a patch note.
  • Choose delayed settled snapshots when the economic object itself is not well-defined mid-transaction. EraLend’s SyncSwap integration and many Curve-LP cases are the warning. If LP value depends on reserves, balances, fees, and supply that intentionally diverge during burns or exits, then one more live helper may still be counterfeit because there is no single same-transaction truth to expose.
  • Prefer venue-wide context guarantees over pool-shape heuristics. Conic is the negative exemplar: the protocol nominally had a guard, but _isETH() and ETH-sentinel expectations silently decided whether the guard ran. If the remedy depends on wrapper labels, pool versions, or registry-returned aliases, it is not yet a canonical safe-read surface.
  • Treat liquidation and collateral-toggle paths as forcing functions in the decision. If temporary markdowns or markups can be spent through liquidation or collateral-membership edits, the system does not merely need a safer borrow() path; it needs one valuation policy that every solvency-changing consumer shares.
  • Ask the blunt design question first: can the protocol publish one same-transaction value that it is actually willing to defend as money-good? If the honest answer is no, stop searching for a prettier live API and move straight to delayed settled objects, caps, and same-block quarantine.

Retrofit sequence for live protocols that already export unsafe views

  1. Inventory every monetizable read surface

    • Enumerate reserve, share-price, wrapper-rate, LP-value, liability-ratio, and collateral-mark helpers that downstream code can consume in borrow, liquidation, deleverage, collateral-toggle, or closeout paths.
    • Distinguish purely informational reads from reads that currently carry solvency authority.
  2. Mark one canonical safe surface and demote the rest

    • Decide which helper is the integration-safe object going forward.
    • Rename, wrap, document, or deprecate raw helpers so integrators can no longer mistake legacy raw read for supported valuation surface.
  3. Publish transition context in code, not only in prose

    • Expose a public lock/context probe or make the canonical valuation helper revert while the protocol is mid-transition.
    • If no reliable probe exists, publish that fact explicitly and move consumers onto delayed settled state instead of encouraging folklore workarounds.
  4. Patch every solvency-changing consumer before celebrating

    • Borrow, liquidation, collateral-toggle, migration, deleverage, redemption, and deprecated helper paths all need to consume the same guarded or delayed object.
    • Sturdy is the reminder that most paths fixed still means one path exploitable.
  5. Add blast-radius controls for the migration window

    • Lower caps, isolate markets, throttle liquidations or withdrawals where appropriate, and keep fast pause rights ready while integrations migrate.
    • A protocol that just discovered unsafe live reads should assume at least one downstream consumer still depends on them incorrectly.
  6. Instrument for same-transaction misuse

    • Alert on reserve/share-price spikes that disappear by transaction end, same-block borrow-then-liquidate loops, or collateral toggles that occur inside large callback-bearing exits.
    • The goal is to catch continued dependence on transitional reads before the full exploit chain lands.
  7. Version the guarantee across wrappers, pool types, and upgrades

    • New adapters, wrappers, and pool versions should inherit the same safe-read semantics automatically.
    • If shipping a new pool type requires another metadata branch to decide whether the guard runs, the retrofit is incomplete.

Design questions worth asking first

  • Can an integrator prove the protocol is outside its callback-sensitive window right now?
  • Which exact helper is the canonical settled-state valuation surface?
  • Do unsafe valuation reads revert while the protocol is mid-transition, or do they still return monetizable numbers?
  • Would a new wrapper, pool type, token alias, or adapter inherit the same safe-read guarantees automatically, or only if someone remembers a metadata branch?
  • Does every solvency-changing consumer use the same guard, or only the obvious borrow/deposit paths?
  • If no safe same-transaction read exists, is that fact published explicitly enough that downstream protocols fail closed?

Red flags that usually mean the protocol is exporting folklore instead of a safe-read surface

  • The write path is nonReentrant used as if that proved the read path is safe
  • Integrators should not call this during callbacks with no machine-checkable way to know whether a callback is active
  • one guard for deposits and borrows, but no equivalent on liquidation, collateral-toggle, or closeout paths
  • safety branches that depend on token metadata, sentinel addresses, or pool labels rather than canonical context signals
  • reserve or virtual-price helpers that keep returning plausible values mid-transition because reverting them would be inconvenient
  • downstream integrations forced to discover lock-probing tricks or same-block delay rules from postmortems instead of interface docs

Failure-prevention checklist

  • Publish one canonical integration-safe valuation surface for every callback-capable reserve, vault, wrapper, or LP accounting system, and make that surface’s contract explicit: safe only outside active transition context or safe only from delayed settled snapshots.
  • Expose a machine-checkable transition-context probe whenever same-transaction safe reads are possible at all; downstream consumers should never have to infer callback liveness from token metadata, pool labels, or guessed code paths.
  • Make unsafe valuation helpers fail closed during active callback or settlement windows. If getRate(), get_virtual_price(), reserve math, or wrapper exchange-rate helpers are not safe mid-transition, they should revert or route callers to a guarded surface instead of returning plausible-but-false numbers.
  • Apply the same safe-read discipline to every solvency-changing consumer: borrow, liquidation, collateral-toggle, deleverage, closeout, migration, and deprecated helper paths, not only the main deposit / borrow flow.
  • Version safe-read guarantees across pool types, wrappers, adapters, and upgraded implementations so new variants inherit the same security envelope automatically rather than depending on remembered special cases.
  • Prefer delayed settled snapshots over live guards when the upstream protocol cannot honestly offer one same-transaction valuation surface that downstream solvency logic can trust.
  • Test integrations against transient numerator/denominator splits, reserve/supply desynchronization, temporary markups, and temporary markdowns; the invariant is not merely nonReentrant, but no consumer can monetize intermediate state.
  • Instrument production monitoring for impossible same-transaction valuation behavior: reserve spikes that disappear by transaction end, same-block liquidations against self-reverting marks, or repeated borrow / liquidate flows that only work during callback windows.
  • Document raw-versus-safe read boundaries in interface docs, contract names, and code comments so auditors and new integrators do not have to recover the security model from postmortems.
  • Treat view publication as part of the security boundary whenever downstream protocols can lend, liquidate, route, or withdraw against the answer immediately.

Audit prompts

  • Which exact reserve, share-price, LP-valuation, wrapper-rate, or liability-ratio helpers are intended to be safe for downstream solvency use, and how is that guarantee enforced in code rather than only in prose?
  • Which raw or transitional helpers remain readable for local execution, analytics, or non-solvency use, and what prevents integrators from mistaking those reads for the canonical settled-state surface?
  • Can integrators prove that the upstream protocol is outside its callback-sensitive or mid-transition context right now, or are they expected to infer that from side conditions?
  • Do any unsafe helpers still return monetizable values during active transition windows because reverting them would be inconvenient for analytics or UI callers?
  • Which solvency-changing entrypoints consume upstream valuation data without passing through the protocol’s main guarded read surface?
  • Once the protocol publishes a canonical safe-read object, which consumers are still allowed to honor it immediately as debt, liquidation, route-selection, or reserve-spend authority, and which should still be staged behind tighter activation rules?
  • Does any defense depend on token metadata, sentinel addresses, wrapper flavor, pool version, or adapter taxonomy instead of a direct execution-context check?
  • If a temporary markdown of someone else’s collateral appeared only inside a callback window, would liquidation, deleveraging, or closeout logic spend it as if it were settled truth?
  • When new pool types, wrappers, or integrations are added, what process proves they inherited the same safe-read semantics rather than silently bypassing them?
  • If the protocol cannot publish a trustworthy live read surface, what forces downstream consumers onto delayed settled snapshots instead of raw same-block state?
  • What invariant or test demonstrates that no same-transaction callback can convert a transient reserve/share mismatch into durable collateral value, liquidation rights, or withdrawal value?
  • Which telemetry would show safe-read drift in production before a full exploit lands?

Lessons that should already be standard

  • View is not a safety property. If downstream logic can monetize the answer immediately, publishing a number is part of the state machine.
  • Reentrancy protection that only covers writes is incomplete when the protocol’s real gift to attackers is a false read.
  • Upstream protocols own part of integration safety. If they know some reads are only true after settlement, they should expose that boundary explicitly in code.
  • Metadata-driven safety branches are brittle trust boundaries. If the guard only runs when the pool looks the way the integrator expected, the security model is already underspecified.
  • Liquidation paths need the same read discipline as borrow paths. Temporary markdowns can be as dangerous as temporary markups.
  • A protocol that cannot expose safe live truth should say so plainly and force consumers onto delayed settled objects instead of letting every integration improvise.
  • Readable now, published as supported truth, and safe to spend against everywhere are different authority claims. Treat them as separate promotions, not one convenience gradient.
  • The real question is not can this callback mutate me? but can this callback make me believe a state that stops being true before the transaction ends?
  • Interface design is part of exploit prevention. Folklore is not a security boundary.

Negative exemplar cluster: what missing safe-read surfaces look like

  • Alpha ibETH is the early wrapper-ratio exemplar: the dangerous read was not a later Curve-style LP oracle but Alpha’s own totalETH()/totalSupply() ratio, which Rari consumed as if view meant settled value inside Bank.work().
  • Balancer BPT is the clean integrator priced raw mid-transition state as if it were settled exemplar; Balancer later became the best public example of how to publish a safer context check explicitly.
  • Sturdy Finance is the strong partial guard coverage still leaks one solvency-changing path exemplar.
  • QiDao is the whitehat-preserved raw callback-window LP mark became stablecoin-mint authority exemplar; it keeps the borrow-side / CDP variant visible instead of letting this family read as only a spot-borrow or liquidation problem.
  • Conic Finance is the guard existed, but safe-read semantics were routed through brittle pool metadata exemplar.
  • SyncSwap is the upstream left stale reserves readable during a deliberate callback window exemplar.
  • dForce is the reminder that unsafe read surfaces do not only overvalue collateral; they can also create transient liquidation markdowns that healthy positions should never face.
  • Beanstalk Basin Wells is the upstream hardening exemplar: the useful lesson was not just that getReserves() was dangerous, but that the protocol could and should make that view fail closed while the lock was active.

How to use this note with the exploit corpus

  • Start here when the design question is what should the upstream protocol have exposed so integrators did not need to guess whether a valuation read was safe?
  • Pair with read-only reentrancy when the exploit classification is already clear and the sharper follow-up becomes what safe-read surface was missing?.
  • Pair with delayed-snapshots-and-same-block-quarantine when the sharper follow-up becomes should downstream consumers have been forced onto a delayed settled snapshot because no safe live read existed at all?.
  • Pair with collateral-admission-lag-and-borrow-side-quarantine when the upstream protocol did publish a coherent settled-state object, but the downstream mistake was still granting that object immediate debt or liquidation authority.
  • Pivot back to oracle manipulation when the decisive bad price is durable enough that no callback-window detection would have mattered.
  • Pair with callback frame truth as a single security object when the sharper question is not the read surface itself, but which in-flight callback context should have stayed authenticated before any downstream valuation happened.

Internal linkages

Control surface