Delayed Snapshots and Same-Block Quarantine

  • Name: delayed snapshots and same-block quarantine
  • Category: integration-safety reference / read-only-reentrancy companion / delayed-valuation design note / downstream-solvency staging checklist
  • Summary: Some protocols cannot expose a truly safe same-transaction valuation surface at all. If reserve, supply, liability, or wrapper-accounting state can be transiently inconsistent during ordinary execution, the right design is often not document the sharp edges better but publish a delayed settled-state snapshot and make same-block consumers fail closed. Healthy systems distinguish latest observable state from latest state that downstream solvency logic may trust, then quarantine fresh writes, callbacks, and same-block reads until a clearly settled snapshot becomes active.
  • Why this note exists:
    • safe-read-surface-discipline covers the positive-design question what safe read surface should the upstream protocol expose? This note exists for the narrower case where the honest answer is no safe same-transaction read exists, so downstream users need a delayed object instead.
    • Read-only-reentrancy incidents often get framed as if one better guard or one more context check would always solve the problem. Some systems instead need an explicit time boundary: same-block reads must not be lending truth.
    • The useful design question is not just can the protocol detect unsafe context? but what valuation object should downstream users consume when safe live reads are structurally unavailable?

The four time boundaries a healthy design should keep separate

  1. Live accounting state

    • The protocol’s internal balances, reserves, liabilities, and share counts may be moving through an in-flight transition.
    • This state can be locally useful for execution, but that does not make it safe as external solvency truth.
  2. Settled observation state

    • The protocol should know when the accounting legs that matter for valuation have converged again.
    • If there is no machine-checkable settled moment, the safest interpretation is that live same-block valuation should not be trusted.
  3. Published snapshot state

    • The protocol should expose one explicit snapshot, checkpoint, or delayed rate object whose claim is this value comes from a settled state boundary.
    • That object may lag by one block, one epoch, or one bounded cooldown. The lag is the safety feature, not an implementation embarrassment.
  4. Consumer activation state

    • Even after a settled snapshot is published, downstream borrow, liquidation, and routing systems may still need their own activation policy.
    • A healthy design says plainly when a fresh snapshot becomes user-interface truth, redemption truth, collateral truth, or liquidation truth.

Three time promotions reviewers should not collapse

  1. Fresh state is observable

    • Balances, reserves, liabilities, or wrapper marks may already be readable somewhere in the system.
    • That only answers can someone see the newest in-flight state?
  2. The protocol published one settled snapshot as accounting truth

    • A checkpoint, delayed rate, or epoch snapshot may then answer the stronger question what settled value is the protocol willing to publish as its own accounting object?
    • That publication step is already a meaningful promotion above raw observability.
  3. Downstream systems activated that snapshot as solvency authority

    • Borrow logic, liquidation logic, redemption logic, route selection, or cross-module accounting may then answer the still stronger question which settled snapshot is allowed to move money or debt right now?
    • A published settled snapshot is still not automatically immediate debt authority.

Most failures in this cluster are not just read happened too early. They are collapses between these three promotions.

What sound design looks like

1. Same-block consumers fail closed when no safe live read exists

  • If the protocol cannot honestly offer a safe same-transaction valuation helper, downstream solvency code should not improvise one from raw reserves or partially updated accounting.
  • Revert, return stale-but-explicitly-labeled data, or require use of the last settled snapshot.
  • view convenience should not outrank solvency safety.

2. The protocol publishes one explicit settled-state object

  • Downstream users should not need to guess whether getRate(), get_virtual_price(), reserve reads, or custom LP math are quiescent.
  • Publish one canonical delayed object: snapshot NAV, previous-block rate, post-settlement checkpoint, epoch-stamped exchange rate, or similarly bounded valuation artifact.
  • The key property is not the exact delay length. It is that the object is defined around settlement, not around the freshest visible intermediate state.
  • That published object should answer one narrow question well: what settled accounting state is the protocol willing to stand behind? It should not silently answer the broader downstream question what may borrow, liquidate, redeem, or route against this right now?

3. Snapshot publication and snapshot activation should be separate choices

  • Writing a new settled snapshot does not mean every downstream consumer should treat it as immediate collateral truth.
  • Wrapper-local display, mint/redeem previews, collateral adapters, liquidation engines, and route scorers may deserve different activation delays or caps.
  • The safer mental model is published settled snapshot first, then specific consumers are allowed to act on it later. Those are separate authority decisions even when they happen close together.
  • This is the time-side companion to collateral-admission-lag-and-borrow-side-quarantine.

4. Same-block quarantine should be explicit, not folklore

  • The protocol should publish rules like do not use values updated in the current block for solvency or only snapshots with timestamp < block.timestamp may back lending decisions in code and interface semantics.
  • If the quarantine rule lives only in docs or postmortem tribal knowledge, new integrations will keep rediscovering it the hard way.

5. Stale snapshots need bounded failure semantics

  • A delayed snapshot is only a safety improvement if consumers know what to do when it is old, missing, or obviously inconsistent.
  • Healthy systems define stale cutoffs, pause behavior, fallback distrust, and bounded-cap modes rather than silently stretching last good forever.

Useful design shapes

1. Previous-block or previous-epoch valuation only

  • The simplest pattern is never use same-block state for solvency.
  • Publish the last settled checkpoint and make collateral or liquidation logic refuse anything fresher.
  • This is often the cleanest answer when intra-block execution can legally pass through multiple unsafe intermediate states.

2. Snapshot-on-settlement, activate-after-cooldown

  • Let a keeper, hook, or protocol-internal settlement path publish a new snapshot once accounting converges.
  • Keep that snapshot out of borrow and liquidation logic until a cooldown or next-block boundary passes.
  • This preserves liveness without letting one attacker-timed exit or rebalance define immediate balance-sheet truth.

3. Separate fast local accounting from slow external solvency truth

  • Wrapper-local accounting may need to know the freshest internal state for redemptions or fee calculations.
  • External adapters should still consume a slower object with stricter settlement guarantees.
  • This keeps the protocol can finish its own work separate from everyone else may lend against the result now.

4. Explicit stale/unsafe flags on the published object

  • A snapshot should carry enough metadata for consumers to fail closed: timestamp, block number, source context, pending-settlement bit, or another machine-readable freshness signal.
  • The goal is that downstream protocols can reject too fresh, too stale, or currently unsafe values without reverse-engineering call flow.

Strong incident cluster for this note

  • Balancer BPT — the clean reminder that a protocol can read a plausible same-block value that is still economically impossible because supply and balances have not reconverged yet.
  • Sturdy Finance — the strong most guarded paths were hardened, but one solvency-changing path still trusted a fresh same-block mark cousin.
  • QiDao — the stablecoin-printing / CDP near-miss reminder that same-block quarantine is just as relevant for mint-authoritative collateral marks as for spot borrowing or liquidation paths.
  • Conic Finance — the reminder that routing safe-read behavior through metadata heuristics is weaker than publishing a universal delayed or guarded valuation object.
  • SyncSwap — a useful non-Balancer, non-Curve exemplar where the underlying pool deliberately left stale reserves readable during callbacks, making a same-block quarantine the simpler design boundary.
  • dForce — the reminder that same-block quarantine protects against temporary markdowns used for liquidation too, not only temporary markups used for borrowing.

Design questions worth asking first

  • Can the protocol honestly expose a safe same-transaction valuation helper, or is delay the only robust answer?
  • What exact object counts as the canonical settled snapshot for downstream consumers?
  • Which consumers may use fresh local state, and which must wait for the delayed snapshot?
  • Does the protocol encode a same-block quarantine rule in code, or only in prose?
  • What stale cutoff, fallback, or pause behavior applies if the snapshot is old or missing?
  • Can an attacker choose the exact publication block of a new snapshot and immediately monetize it through collateral or liquidation logic?

Red flags

  • Use the latest value unless a reentrancy guard happens to be active.
  • Our reads are usually safe except during some internal transitions.
  • The snapshot is delayed for UI reasons only.
  • Borrow and liquidation logic consume the same freshest cache used for local accounting convenience.
  • Stale snapshots are acceptable indefinitely because freshness checks would hurt UX.
  • Integrators can infer safe timing from pool type or token metadata.

Failure-prevention checklist

  • Make one explicit policy choice for every valuation consumer: guarded safe live read or delayed settled snapshot. Do not leave downstream solvency logic free to mix raw same-block state with older checkpoints ad hoc.
  • Publish one canonical settled snapshot object with machine-readable freshness metadata such as block number, timestamp, settlement epoch, or pending-settlement status.
  • Enforce same-block quarantine in code for collateral, liquidation, deleveraging, and route-selection surfaces whenever no trustworthy live read exists; documentation only quarantine is not enough.
  • Separate snapshot publication from snapshot activation so a newly written checkpoint does not automatically become immediate borrow or liquidation truth.
  • Define stale-snapshot failure behavior explicitly: pause, cap, degrade, or deny. Do not silently stretch last good value across unbounded time or missing-settlement conditions.
  • Test temporary markup and temporary markdown paths, because same-block quarantine has to protect liquidation as well as borrowing.
  • Prevent privileged operators, keepers, or adversarial users from choosing a publication moment and immediately monetizing that same block through newly activated collateral or liquidation rights.
  • Keep UI/display freshness, redeemability, collateral admissibility, and liquidation eligibility as separately reviewable policy surfaces; one fresh local number should not automatically drive all four.
  • Force new wrappers, pool versions, adapters, and markets to inherit the quarantine policy by default rather than requiring one-off integration memory.
  • Monitor for same-block valuation anomalies, repeated publish then borrow/liquidate patterns, or snapshot age drifting outside documented bounds.

Audit prompts

  • What exact object is the canonical settled snapshot for downstream solvency consumers, and what proves it came from a fully converged state boundary?
  • Which consumers are allowed to use fresh local state, and which must wait for a delayed snapshot or cooldown boundary?
  • Is same-block quarantine enforced in contracts, or do integrators only learn about it from docs and postmortems?
  • Can an attacker influence when a new snapshot is published and then exploit that same publication block before downstream guards activate?
  • What happens when the snapshot is missing, stale, contradictory, or stuck behind paused settlement logic?
  • Do borrow, liquidation, deleveraging, collateral-toggle, and migration paths all respect the same freshness boundary, or do edge helpers consume fresher state?
  • Are UI previews, redemption logic, and solvency logic all reading the same object for convenience even though they deserve different freshness standards?
  • Could a temporary markdown during an unwind or withdrawal callback create false liquidation rights before the delayed snapshot boundary catches up?
  • How do new markets, wrappers, or oracle adapters inherit the quarantine rule, and what tests fail if they do not?
  • Which telemetry would reveal that same-block truth is still leaking into solvency decisions?

Lessons that should already be standard

  • Freshest visible state is not the same thing as safe collateral truth.
  • A published settled snapshot is not the same thing as immediate debt, liquidation, redemption, or routing authority.
  • Delay is sometimes the correct security primitive, not a UX embarrassment to be optimized away.
  • Snapshot publication and snapshot activation are different authority decisions and should be reviewed separately.
  • Same-block liquidation safety matters as much as same-block borrow safety; temporary markdowns can be just as expensive.
  • If the protocol cannot expose safe live truth, it should force the safer slower object instead of letting each integrator invent its own timing rules.
  • Stale-value handling is part of the design, not an operational afterthought. Last good without bounds is just another hidden trust assumption.
  • One market’s need for fresh local execution data does not justify exporting that same freshness as global balance-sheet truth.
  • Time boundaries deserve the same explicit code-level treatment as signer boundaries, parser boundaries, and callback boundaries.

Where this note stops

How to use this note with the exploit corpus

Internal linkages

Control surface