Migration Cutover Authenticity and Single-Record Consumption Discipline
-
Name: migration cutover authenticity and single-record consumption discipline
-
Category: migration reference / cutover-security checklist / successor-state design note / legacy-entitlement consumption discipline
-
Summary: Healthy migrations do not let the successor system accept an attacker-authored story about legacy value. The safer shape is that one canonical legacy record authorizes the cutover, the same record is irreversibly consumed, the replacement beneficiary and any residual refund are both derived from that record, and no public callback or helper can substitute fresh authority in the middle. This note exists to give the corpus a positive-design answer to migration exploits where the new system let users choose the certifying old contract, the claimed migrated amount, the migration callback context, or the refund destination. The reusable question is narrower than generic upgrade hygiene and sharper than generic withdrawal safety:
what exact consumed legacy right makes this successor credit legitimate, and what prevents anything nearby from being treated as the same proof? -
Why this note exists:
- Migration incidents keep repeating because teams frame cutover helpers as operational glue even though they are authenticating real value.
- The exploit usually sits at a seam: one record authorizes migration, another record is actually burned or unlocked, and a third attacker-shaped field controls the replacement beneficiary or refund.
- The healthy-side answer needed its own note because the recurring failure is not merely
legacy code still existed; it is that the successor system accepted a split story about which exact legacy entitlement had been consumed.
What this note is trying to preserve
- A migration should consume one canonical legacy entitlement and create at most one bounded successor entitlement plus explicitly derived residuals.
- The old record that authorizes migration should be the same record whose value is withdrawn, burned, unlocked, or closed.
- Callback success, helper return data, or user-supplied context should never count as standalone proof that legacy value was consumed.
- Replacement ownership and refund routing should inherit from the consumed legacy record, not from nearby calldata that merely arrived during the same transaction.
The five joints a healthy migration should keep fused
-
Legacy source identity
- The successor should know exactly which old contract, market, trove manager, staking system, lock registry, or helper is allowed to certify legacy state.
- Users should not be able to choose the certifying legacy source through calldata, adapter selection, or callback sender choice.
-
Legacy record identity and amount
- One stored legacy record should answer all of these together: who owns the right, what asset or position it represents, how much value is being consumed, and whether it has already been spent.
- The successor should derive migrated amount and asset identity from that record or from a proof tightly bound to it, not from caller-declared amounts.
-
Consumption before successor cashability
- The old entitlement should be irreversibly burned, withdrawn, nullified, or marked consumed before the new entitlement becomes redeemable, borrowable, or transferable.
Callback returned successfullyis weaker thanlegacy right is now unavailable for any second use.
-
Successor beneficiary and residual routing
- The successor beneficiary, replacement position owner, and any leftover-asset or refund recipient should all be derived from the consumed legacy record unless the protocol deliberately allows something else and authenticates that exception.
- Migration should not let one user-controlled record authorize value while a different attacker-controlled field decides who gets the replacement or the leftovers.
-
Session / callback authenticity
- If the migration uses a zap, flash-loan hook, bridge-style callback, or other multi-step helper, the callback must be authenticated as a continuation of a migration the protocol itself initiated.
- Public callbacks should carry migration meaning only through nonce-bound, session-bound, or initiator-bound state the attacker cannot forge from scratch.
Three authority facts migrations should not collapse
-
This helper may act for meis not the same fact asthis cutover is authentic.- Standing delegate approval, zap approval, router approval, or operator authorization only says a helper is allowed to exercise some power if the rest of the migration story is real.
- It does not prove that the current callback, flash-loan hook, adapter return, or settlement frame corresponds to a protocol-authored migration session.
-
This cutover is authenticis not the same fact asthis successor credit may already be money-good.- Even a legitimate migration session should still prove the exact legacy record was consumed, the migrated amount matches that record, and any residuals resolve deterministically.
- A real callback is still weaker than
the old right is gone and this exact replacement plus these exact leftovers are the only authorized outcome.
-
Some residual value existsis not the same fact asthe helper may choose who gets it.- Leftover tokens, partial unlocks, dust, and bridgeback balances are still part of the consumed legacy record’s liability object.
- They should inherit their destination from the same canonical record rather than from free-form user input, adjacent calldata, or whoever reaches the helper next.
What sound design looks like
- The protocol can point to one canonical legacy record and say:
this exact thing was consumed, so this exact successor thing may now exist. - Migration helpers are subordinate to stored truth. They may transport assets or improve UX, but they do not get to decide legacy source identity, migrated amount, replacement owner, residual routing, or whether a standing approval now counts as proof that a real migration is in flight.
- The successor treats migration as a balance-sheet event, not a convenience callback. Burn, unlock, close, or consume happens first; successor credit comes second; ordinary exits trust only that consumed-state result.
- The protocol keeps
helper authority,session authenticity, andsuccessor cashabilityas separate gates. A helper being approved to act does not authenticate the cutover, and an authenticated cutover still does not make arbitrary replacement state or residual payouts money-good. - Replacement positions and refunds inherit from the same consumed legacy record. If a protocol wants user choice during migration, it narrows that choice explicitly and authenticates it against the original record rather than reading free-form adjacent calldata.
- Temporary cutover permissions expire. Once the migration window closes, delegated helper approvals, broad callback authority, and legacy-source acceptance should narrow or disappear instead of aging into permanent latent leverage.
- Incident response can halt migration without freezing the entire product. High-risk cutover rails have their own kill switches, separate telemetry, and explicit post-launch sunset plans.
Useful positive design shapes
1. Canonical record-driven migration
- The simplest healthy migration reads one immutable or tightly governed legacy source, loads one stored record, consumes that record, and derives replacement amount, owner, and refund semantics entirely from that consumed record.
- The reusable point is not
hardcode everything; it isone record stays authoritative all the way through cutover.
2. Burn-and-mint with successor credit lag
- If the protocol must recreate stake, debt, shares, or collateral in a new system, it should prove the old side has already burned or locked the outgoing entitlement before the new side becomes cashable.
- This is the positive-design answer to migrations that otherwise act like ambient mint functions.
3. Callback-bound migration sessions
- Complex zaps can still be safe if the public callback is merely a continuation of a migration session the protocol previously recorded, scoped, and nonce-bound.
- The bad smell is
public callback plus attacker-provided migration data; the healthier shape ispublic callback that can settle only one pre-committed migration context.
4. Residual refund derivation from the consumed record
- Migrations that burn one position and return leftovers should derive refund asset identity and recipient from the same canonical consumed record that authorized the migration.
Residualis safer when it is treated as part of the cutover liability object rather than as a miscellaneous payout bucket.
Design questions worth asking first
- What exact legacy record authorizes this migration?
- Is that the same record whose assets or entitlement are actually consumed?
- What proves the legacy right became unavailable before successor credit became economically live?
- Which fields in the migration are still attacker-shaped: source address, amount, owner, lock ID, callback sender, refund recipient, helper module, or repayment route?
- If the helper or callback returned success while moving no value, what concrete state check would still stop the successor credit?
- After the migration window closes, what exact authority disappears and what exact authority remains?
Red flags
Users can pass the old contract address for flexibilityThe helper just needs to return successfullyThe frontend will only call the intended migration sourceRefund destination can stay user-specified because it is only leftoversThe flash-loan callback is public, but only our zap will call it correctlyWe can sunset the broad migration approval laterThe old record is used for auth, but the actual burned position comes from a nearby id or token supplied in calldata
Failure-prevention checklist
- Pin the acceptable legacy source, helper contracts, and callback senders in immutable or tightly governed state; never accept them directly from user calldata unless the protocol is explicitly authenticating a broader set.
- Derive migrated amount, position identity, owner, and asset type from one canonical legacy record or one proof tightly bound to that record.
- Make legacy consumption machine-checkable before successor credit becomes withdrawable, borrowable, transferable, or collateral-eligible.
- Bind replacement beneficiary and residual refund routing to the consumed legacy record unless an authenticated policy says otherwise.
- Treat public callbacks, flash-loan receivers, router hooks, and zap continuations as hostile until they prove they are continuing one protocol-authored migration session;
helper already has approvalmust never be accepted as a substitute for that proof. - Test dummy legacy contracts, no-op callbacks, mismatched record ids, forged refund recipients, forged owners, helper returns that succeed without moving value, and direct callback entry where the helper already has standing authority over user positions.
- Scope and expire migration-specific approvals, delegated helper rights, and broad callback permissions aggressively once cutover ends.
- Add per-record invariants:
successor value created + residual refunded <= value consumed from this exact legacy record. - Add per-cohort invariants too: total value recreated in the successor system cannot exceed total value provably consumed from the predecessor system.
- Keep a dedicated kill switch and telemetry around migration helpers so teams can stop the cutover rail quickly without improvising governance theater during the exploit.
Audit prompts
- Which exact old contract or registry is allowed to certify legacy state, and can the user influence that choice anywhere?
- What exact stored record answers
who owns this right,how much value exists,which asset is it, andhas it already been consumed? - Does the successor credit derive from that same record, or from adjacent calldata and callback data that only coincidentally arrived during migration?
- Can one user-controlled record satisfy migration authorization while a different victim-owned record is the thing actually burned, unlocked, or refunded?
- If a public flash-loan or zap callback reaches the settlement function directly, what authenticates that the encoded migration context came from a protocol-initiated session rather than from the attacker exercising already-approved helper authority?
- Are leftover assets, unlocked tokens, or residual payouts routed by canonical record derivation or by attacker-shaped destination fields?
- What standing approvals or delegated rights remain dangerous if migration-session authenticity fails, and which exact state gates keep
approved helperfrom collapsing intoauthenticated migration? - After launch, what prevents the migration path from remaining a softer shadow mint / withdraw rail months later?
- If the protocol paused immediately after
successor credit createdbut beforeresidual refund paid, would accounting still reconcile per consumed record? - Which dashboard, alert, or invariant would show first that successor liabilities now exceed predecessor value actually consumed?
Lessons that should already be standard
- Migration is not merely a UI bridge between old and new contracts. It is a value-authentication ceremony.
- One consumed legacy record should answer all the important questions. If helper authority, authenticated migration context, amount, replacement owner, and refund destination come from different places, the cutover is already attacker-shaped.
- Callback success is not economic proof. A helper that returned cleanly did not necessarily consume anything real, and a helper that already had standing approval still did not prove this specific cutover was legitimate.
- Residuals are part of the solvency story. Leftovers and refunds deserve the same identity discipline as the main migrated amount.
- Temporary cutover powers are dangerous when they linger. Old approvals, public helper reachability, and broad migration rights become latent exploit surface once the team mentally moves on.
- The safest migration invariant is boring: one exact old right dies, one exact new right appears, and everything else resolves to zero or to tightly derived leftovers.
How to use this note with the exploit corpus
- Start here when the sharper design question is
what should a healthy contract migration, position migration, staking upgrade, or trove cutover have kept bound to one consumed legacy record? - Pair it with Migration trust-boundary failure for the recurring negative exploit family.
- Pair it with Decommissioning authority-extinguishment discipline when the broader governance or lifecycle mistake is that a socially retired system still retained real authority during or after the cutover.
- Pair it with Helper-path invariant reuse and fail-closed discipline when the migration helper should have reused the same economic finality checks as ordinary consume / withdraw paths.
- Pair it with Exit-right consumed-state binding when the sharper downstream question is whether the same migrated right could later be paid twice through sibling exit branches.
- Pair it with Exit-right provenance binding when the sharper downstream question is why the successor balance became cashable at all.
- Negative exemplars worth keeping nearby: STAX, Prisma Finance, and Team Finance.
Internal linkages
- Negative exploit-family companion: migration-trust-boundary-failure.
- Lifecycle / retirement companion: decommissioning-authority-extinguishment-discipline.
- Helper-path parity companion: helper-path-invariant-reuse-and-fail-closed-discipline.
- One-time-use companion: exit-right-consumed-state-binding.
- Cashability-provenance companion: exit-right-provenance-binding.
- Closely related incident set: STAX, Prisma Finance, and Team Finance.
Control surface
-
The decisive control surface is the cutover boundary where the successor system decides which old contract, which legacy record, which callback context, which beneficiary, and which residual routing actually count as money-good migration truth.
-
In practice that means migration entrypoints, helper modules, callback receivers, legacy-source allowlists, record-id selection logic, beneficiary derivation, refund routing, and the timing of when successor credit becomes redeemable or borrow-relevant.
-
Systems are healthier when those layers all inherit one consumed-record story instead of letting a migration combine
auth from here,amount from there,callback context from elsewhere, andrefund destination from calldatainto one attacker-authored success path. -
Sources:
-
Last reviewed: 2026-07-25 UTC