Helper-Path Invariant Reuse and Fail-Closed Discipline

  • Name: helper-path invariant reuse and fail-closed discipline

  • Category: protocol-design reference / helper-path discipline / canonical-state-transition checklist / positive-design companion to lending-accounting bypasses

  • Summary: When a protocol already has one canonical path that mutates economically meaningful state safely, helper paths should almost never open-code a weaker cousin. emergencyWithdraw(), repayForAll(), reserve-donation helpers, migration shortcuts, rescue functions, optimistic management multicalls, asynchronous-order collateral adapters, and bulk-accounting utilities should either route through the same invariant-preserving core or fail closed until they are requalified under the new architecture. The recurring failure is not just a helper had a bug; it is a secondary path quietly became an alternative economic finality boundary by collapsing three promotions that deserve to stay separate: a side path is reachable at all, that side path is allowed to author one canonical post-action state story, and later borrow / liquidation / refund / migration / reserve-spend paths are still allowed to cash out against that helper-authored story. Euler, Platypus, Arcadia Finance, Team Finance, Abracadabra Cauldron V4, and Abracadabra GMX V2 preserve six useful shapes of that lesson: unchecked reserve donation, stale pre-withdraw emergency exit, optimistic collateral release with sibling liquidation reentry, migration-closeout identity mismatch with refund capture, aggregate debt-ledger corruption, and pending-order phantom collateral that liquidation can consume without retiring the same solvency value.

  • Why this note exists:

    • Several exploit families are easier to recognize once the reviewer asks which path is the real economic truth surface?
    • Teams often re-audit the main borrow, withdraw, liquidation, or migration flow after a feature expansion, but leave old helpers, emergency exits, or bulk-management functions carrying assumptions from a simpler era.
    • Reviewers also flatten this helper is still callable, this helper is still allowed to publish an economically meaningful result, and the rest of the protocol may still spend against that result into one vague claim that the helper is part of the same feature.
    • The useful design question is often not does the helper still run? but why is this helper allowed to define its own weaker version of the same accounting boundary at all?

Fast route inside this family

  • Euler is the unchecked reserve-donation / self-liquidation anchor: a reserve helper deleted collateral without reusing the same post-mutation liquidity boundary as other eToken balance changes.
  • Platypus is the stale pre-withdraw emergency-exit anchor: emergencyWithdraw() checked solvency before collateral actually left, so the helper reused the name of the solvency boundary but not its real sequencing.
  • Abracadabra Cauldron V4 is the aggregate debt-ledger corruption anchor: repayForAll() left elastic and base in a state the main borrow path would never have authored.
  • Arcadia Finance is the optimistic-management / sibling-liquidation anchor: vaultManagementAction() deliberately moved collateral out before the final health check, and liquidateVault() was still allowed to rewrite debt state mid-flight.
  • Team Finance is the migration-closeout identity-mismatch anchor: migrate() authenticated one attacker-owned lock record while burning and refunding a different victim LP position, so helper-local migration and residual-routing logic quietly became the real custody boundary.
  • Abracadabra GMX V2 is the pending-order phantom-collateral anchor: a liquidation-side helper extracted real RouterOrder value without consuming the same order state later trusted by _isSolvent().
  • Stay here when the sharper design question is not only which accounting invariant broke? but why was a helper, adapter, emergency path, liquidation side path, or bulk-management surface allowed to define a weaker economic finality boundary than the canonical core?

What this note means by canonical-state-transition reuse

  • A canonical state transition is the internal routine or tightly equivalent sequence that mutates economically important state and then proves the resulting state is still valid.
  • Helper-path invariant reuse means secondary wrappers reuse that same core instead of reimplementing their own looser mutation order, guard set, or accounting interpretation.
  • Fail closed discipline means that once protocol architecture changes make an old helper’s assumptions uncertain, the helper should revert, pause, or narrow its authority until it is explicitly requalified.

Three helper-authority promotions healthy reviewers should not collapse

  1. A side path is reachable at all
  • A helper, rescue path, emergency exit, migration wrapper, reserve-management utility, or asynchronous adapter callback can still touch a live balance-sheet object.
  • That only answers can this path still run and mutate something economically relevant?
  1. That side path is still allowed to publish one canonical post-action state story
  • The protocol treats the helper’s local sequencing, returned object, or intermediate bookkeeping as good enough to stand in for the canonical mutation-and-validate core.
  • This is already a stronger claim than reachability. It answers does the system accept this helper's version of completion, collateral release, debt retirement, refund identity, or reserve movement as if it were the canonical one?
  1. Later paths are still allowed to spend against that helper-authored story
  • Borrow, liquidation, migration refund, reserve spend, redemption, or cross-module accounting paths then keep honoring the state the helper authored.
  • This is the still-stronger promotion: is the rest of the protocol allowed to cash out against the helper's weaker state story as if the real economic boundary had already been crossed?

Most expensive helper-path failures collapse those three promotions into one vague comfort claim that this is only another way to do the same thing. The more useful review split is why was the helper callable, why did its local state story count as canonical, and why were later cash-out paths still allowed to trust it?

The main rules healthy helper paths should obey

1. One economic boundary, many UX wrappers

  • Different entrypoints may exist for UX, gas, batching, panic handling, or admin convenience.
  • But once they change collateral, debt, reserves, claims, or other balance-sheet objects, they should converge on one invariant-preserving core.
  • The dangerous shape is same economic effect, different safety boundary.

2. Helpers should skip optional work, not the truth-producing step

  • It can be fine for emergency or bulk paths to skip reward harvests, event niceties, route search, or convenience bookkeeping.
  • It is not fine for them to skip the state mutation ordering or post-state validation that makes the economic action safe.
  • Platypus is the clean reminder: the emergency wrapper skipped the meaningful solvency boundary by checking before collateral had actually left.
  • Arcadia is the sibling-path reminder: an optimistic management helper that intentionally delays validation is only safe if every other path that can rewrite debt, collateral status, or liquidation state is locked out until that validation finishes.

3. Pre-feature helpers should fail closed after collateral or debt reuse expands

  • A helper written for staking only, repayment only, or local accounting only can become dangerous once the same object later backs debt, redemption rights, or cross-module solvency.
  • After that expansion, the old helper should default to revert, narrow authority, or reuse the now-canonical debt-aware path until explicitly re-reviewed.
  • It used to be harmless is not a security argument once the surrounding system has changed what the object means.

4. Bulk-management helpers still own real invariants

  • Functions with names like repayForAll, sync, donateToReserves, migrate, sweep, rescue, manage, or adapter-side sendValue / closeOrder style utilities often sound administrative or mechanical.
  • If they mutate aggregate debt, collateral balances, reserve balances, liquidation eligibility, or entitlement objects, they are not mere convenience code; they are economic-control surfaces.
  • Abracadabra is the clean reminder that a debt-management helper can poison the same aggregate state the main borrow path later prices as truth.
  • Arcadia is the complementary reminder that even a user-facing manage helper becomes an economic-control surface once it can temporarily externalize collateral before the shared solvency boundary reasserts itself.

5. Callers should not be asked to remember social rules the code can encode

  • Users should not emergency withdraw while borrowing and nobody should call this helper in that state are folklore, not defenses.
  • If the helper is only safe in a narrow state, encode that state explicitly with reverts or tightly scoped authority.
  • Good fail-closed design turns implied etiquette into machine-checked preconditions.

6. Shared return types and shared state names do not guarantee shared semantics

  • Two paths can both call isSolvent(), both touch totalBorrow, or both talk about the same pending order while still asking different economic questions.
  • Reviewers should compare when state is read, which fields are consumed, and whether the call is proving pre-state comfort or post-state validity.
  • Platypus and Abracadabra both looked superficially integrated with the main accounting model while actually preserving weaker helper-specific semantics.

What sound design looks like

  • Reachability, publication, and downstream spending authority stay separate on purpose

    • A helper being callable does not by itself mean it may publish a canonical post-action state story, and helper-local publication still does not by itself mean later borrow, liquidation, migration, or reserve-spend paths may cash out against it.
    • Healthy systems force those promotions to be explicit instead of letting a secondary path inherit them ambiently from the main flow.
  • One internal mutation-and-validate core

    • Collateral release, debt updates, reserve movements, or entitlement consumption happen in one shared routine, with wrappers only changing UX behavior around it.
  • Helpers fail closed on uncertain architecture upgrades

    • When a staking position becomes borrowable collateral, when a debt helper starts touching rebase state, or when a migration helper can now mint rights in a richer system, old wrappers revert until requalified.
  • Post-state validation, not pre-state reassurance

    • The system proves the resulting state is valid after the helper’s economically relevant mutation has occurred.
  • Administrative sounding code treated as security-critical

    • Bulk repayment, reserve sync, migration, rescue, and panic functions get the same invariant review as ordinary user-facing flows.
  • Fail-closed preconditions encoded in code, not docs

    • If a helper is only safe when debt is zero, base and elastic are synchronized, or one entitlement object has not already been consumed elsewhere, that condition is enforced directly.

Useful positive design shapes

1. Shared economic-finality core

  • Keep one internal function that mutates the economic object and validates the resulting state.
  • Let emergency, batch, and admin wrappers feed into that core instead of branching around it.
  • This is the most direct defense against helper-path drift.

2. Upgrade-triggered helper requalification

  • Every architecture change that broadens what an object can back or settle should trigger a review of every old helper that can still move that object.
  • The default answer should be pause or narrow first, reopen after requalification, not assume old wrappers are still harmless.

3. Impossible-state assertions on aggregate accounting

  • Helpers that touch aggregate debt, reserve, or supply rebase state should assert that amount/share pairs remain mutually meaningful after execution.
  • If elastic == 0 && base > 0 or similar impossible combinations appear, later user-facing flows should stop rather than keep interpreting corrupted state.

4. Helper-specific permissions narrower than core-path permissions

  • If a helper exists only for exceptional situations, its authority should be narrower than the normal path, not broader.
  • Emergency and rescue code should usually remove options, not create fresh ways to bypass economic checks.

5. Optimistic windows need sibling-path exclusion, not just a final check

  • Some helpers intentionally permit a temporarily invalid intermediate state because they expect to restore solvency or accounting coherence before returning.
  • That pattern only survives if sibling entrypoints that can consume, liquidate, refinance, or otherwise rewrite the same economic object are excluded until the optimistic window closes.
  • A final isHealthy() check is not a safety boundary if liquidation, debt erasure, or collateral-state rewrites can happen halfway through the helper.

Design questions worth asking first

  • Which path is the canonical truth-producing state transition for this economic action?
  • Does this helper reuse that core, or does it open-code a cousin with different ordering or guard logic?
  • What assumptions did this helper make before the protocol added borrowing, collateral reuse, rebase accounting, liquidation side paths, or migration semantics?
  • If the helper corrupts aggregate state, or temporarily externalizes collateral while debt is still live, what later canonical path will keep trusting that state as if nothing happened?
  • If the helper is only safe under one narrow condition, why is that condition not an explicit revert?

Red flags that usually mean the helper has become an unsupervised control surface

  • This path is just for emergencies
  • This is only a bulk-management convenience function
  • The normal flow checks that already
  • Users are not supposed to call this while X is live
  • The helper returns enough information if callers use it correctly
  • This code predates borrowing / migration / collateral reuse, but should still be fine

Failure-prevention checklist

  • Identify one canonical mutation-and-validate core for every economically meaningful action: collateral release, debt update, reserve movement, entitlement consumption, migration certification, or adapter-side collateral retirement.
  • Force helpers, bulk utilities, rescue paths, and emergency wrappers to reuse that core or revert. If a path cannot cleanly reuse the core yet, treat that as a reason to fail closed, not a reason to open-code a weaker cousin.
  • Requalify legacy helpers whenever architecture changes broaden what an object can back or settle: borrowing, collateral reuse, rebasing debt state, async order adapters, migration receipts, or cross-module closeout semantics.
  • Encode helper-safe preconditions directly in code: debt-zero, synchronized amount/share pairs, unconsumed entitlement state, no pending-order value still counted elsewhere, no sibling liquidation path that can observe deliberately incomplete state, and no cross-module ledger expecting the same object to remain live.
  • Add impossible-state assertions at aggregate-ledger boundaries so corrupted base / elastic, reserve, share, or entitlement states halt later user paths instead of becoming new canonical truth.
  • Narrow helper permissions relative to the normal path. Exceptional wrappers should remove optionality and authority, not gain broader ability to move value or bypass validation.
  • Treat administrative-sounding helpers as security-critical surfaces in review, monitoring, and incident response. sync, sweep, migrate, donate, repayForAll, and rescue can all be balance-sheet code.
  • Adversarially test helper sequencing under feature-expanded architecture, not only under the assumptions that existed when the helper was first written.

Audit prompts

  • What is the single canonical truth-producing state transition for this economic action, and where does each helper diverge from its ordering, guard set, or state interpretation?
  • Which helpers were written before borrowing, collateral reuse, migration certification, async adapters, or rebasing debt semantics existed, and what explicit requalification happened afterward?
  • Can any helper mutate collateral, reserves, aggregate debt, liquidation eligibility, entitlement identity, or pending-order state without reusing the exact same post-state validation as the normal path?
  • If a helper intentionally delays final health checks, which sibling entrypoints are still allowed to act on the same vault, position, or debt object before that check runs?
  • If a helper corrupts aggregate state, which later user-facing flow will still price that state as canonical truth and cash it out into real value?
  • What explicit revert protects the helper’s narrow safe state, and which safety assumptions are still being outsourced to caller etiquette or operator memory?
  • Are emergency, rescue, bulk, or admin wrappers actually narrower than the canonical path, or do they secretly gain broader mutation authority because they skip normal sequencing?
  • Do helper return values or shared state names create false comfort by looking semantically aligned with the main path while actually proving a weaker economic claim?
  • If the protocol were upgraded tomorrow to let the touched object back one more module, would this helper fail closed automatically or remain live under stale assumptions?

Lessons that should already be standard

  • Convenience code that moves economic state is not peripheral code; it is another finality boundary until proven otherwise.
  • Reachable helper, helper-authored state counts as canonical, and later paths may cash out against that state are different authority claims. Treat them as separate promotions, not one feature flag.
  • A helper that cannot safely reuse the canonical core should usually be disabled, not allowed to preserve a looser local version of truth.
  • The main path checks that is not a defense when the helper can still author state the main path later trusts.
  • Architecture changes silently promote old helpers into new security-critical surfaces; feature expansion is a reason to narrow authority first.
  • Shared variable names and helper calls do not guarantee shared economic semantics; sequencing and post-state meaning are what matter.
  • Administrative labels like sync, rescue, or repayForAll are camouflage, not scope limits.
  • Fail-closed discipline is an architectural posture: uncertainty about helper safety should shrink authority immediately instead of waiting for a future cleanup pass.

How to use this note with the exploit corpus

  • Start here when the sharper design question is why a helper, legacy, emergency, or bulk-management path was allowed to define a weaker economic boundary than the canonical core path.
  • Pair with debt-zero-emergency-exit-discipline when the concrete issue is emergency collateral release while debt is still live.
  • Pair with solvency check bypass when the broader exploit family is unchecked balance-sheet mutation that manufactures bad debt.
  • Pair with zero-share borrow inflation when the helper poisoned aggregate debt/share state rather than releasing collateral directly.
  • Pair with exit-right-consumed-state-binding when sibling helper and ordinary paths may each be spending the same entitlement object differently.

Internal linkages

Control surface