Entrypoint Equivalence Under Shared Standards / One Backing Boundary Across Sibling Verbs

  • Name: entrypoint equivalence under shared standards / one backing boundary across sibling verbs
  • Category: standards-integration reference / solvency-boundary design note / shared-entrypoint checklist / inheritance-hardening reference
  • Summary: Many tokenized-vault, wrapper, and reserve-backed interfaces expose sibling verbs that are supposed to be economic synonyms under the same state: deposit() versus mint(), withdraw() versus redeem(), custom depositETH() helpers versus inherited standard paths, outer-frame issuance versus callback-bearing standard receive paths such as safeTransferFrom() plus onERC721Received() when both can still account for the same economic event, or even plain transfer() / transferFrom() when a balance move also moves the same live reward, fee, or reserve-backed claim. Healthy integrations do not collapse this sibling ABI surface still exists, this sibling path still proves the same backing or retirement truth, and this sibling path still deserves live authority under this deployment, asset, and fallback state into one vague the standard is supported story. This note exists to make the positive-design target explicit for the family captured by shared-standard entrypoint invariant drift: if a protocol keeps a standard-shaped surface after moving the real invariant, it must still prove that all surviving sibling verbs hit one coherent economic boundary before they can create or retire claims.

Why this note exists

  • The exploit-side lesson in Meta Pool and similar bugs is easy to phrase negatively: an inherited sibling verb or callback-bearing sibling receive path stayed live after the invariant moved.
  • The more reusable design-side question is sharper: what exact equivalence should sibling standard verbs still satisfy once the protocol customizes one of them?
  • Standards often give teams multiple user-facing verbs for convenience, but downstream safety depends on those verbs remaining economic synonyms rather than just API cousins.
  • That sibling set is wider than vault verbs alone. Once a transferable share balance also carries pending fees, reward debt, claim-consumed state, or another live reserve-backed entitlement, inherited ERC-20 movement verbs become part of the same equivalence story too.
  • A protocol can keep the right function names, events, and interfaces while silently splitting the real solvency boundary underneath them.

What this note is trying to preserve

  • A shared standard surface should imply one shared economic truth, not merely several similarly named public functions.
  • If two verbs claim to do the same economic job under the same state, they should both prove the same backing condition before claims are created and the same retirement condition before claims are destroyed.
  • If a plain balance movement also transports the same reward, fee, withdrawal, or reserve-backed claim, then transfer()-style verbs belong inside that equivalence proof too; they are not mere UX plumbing.
  • If that equivalence is no longer true, the protocol should say so in code by reverting, disabling the unsupported verb, or exposing a new non-standard surface with different semantics.

The six questions sibling entrypoints should answer the same way

1. Where does backing get proven?

  • For every issuance verb, point to the exact line or internal primitive where the contract proves assets arrived, inventory was genuinely consumed, or another conserved backing event happened.
  • If deposit() proves receipt in one place and mint() assumes receipt happened elsewhere, or if an outer issuance frame and a standard receiver hook can both still certify the same inbound transfer, the standard surface is already split.

2. Where does claim quantity become final?

  • Sibling issuance verbs may take different user inputs such as assets versus shares, but they still need one coherent rule for when claim quantity becomes canonical.
  • The contract should not let one verb finalize claim quantity before the backing leg is proven while another waits until afterward.

3. Where does retirement get proven?

  • Withdraw-side siblings like withdraw() and redeem() should consume the same liability object under the same accounting assumptions.
  • If one path burns against live backing while another reaches a stale helper, compatibility surface has become solvency surface.

4. What fallback branch is still allowed to mint or release value?

  • Internal-liquidity shortcuts, residual mint branches, or custom routing helpers are not free-standing UX details.
  • If pool-first or buffer-first logic fails, the fallback path still has to hit the same proof-of-backing boundary before claims leave the function.

5. Which balance-movement verbs still move the same economic claim?

  • If a receipt token is transferable and the balance carries pending rewards, fee checkpoints, withdrawal rights, or another live reserve-backed entitlement, then transfer() and transferFrom() are sibling economic verbs too.
  • A pure balance move must either carry that entitlement coherently, crystallize and consume it coherently before movement, or fail closed. If sender and receiver can become richer merely because a standard balance verb ran, the shared-entrypoint story is already broken.

6. What states are intentionally unsupported?

  • If only one favored entrypoint is safe after customization, unsupported sibling verbs should fail closed.
  • Users are not supposed to call that inherited function is not a security boundary.

Three sibling-entrypoint facts healthy systems should not collapse

1. The sibling surface still exists and can still be called

  • A public selector, router helper, receiver hook, inherited verb, or plain balance-movement surface may still be reachable even after product docs or the favored path moved elsewhere.
  • Reachability is only the first fact. It says the lane is live enough to matter, not that it still deserves normal economic trust.

2. That surviving surface still proves the same backing or retirement truth

  • A reachable sibling verb is only economically equivalent if it still reaches the same proof-of-backing, claim-finalization, or liability-retirement primitive as its supposed peers.
  • This is where equivalence usually breaks: one path proves receipt directly while another inherits a stale helper, one exit path burns against live accounting while another reaches a leftover buffer shortcut, or one transfer path carries the same entitlement while another silently drops or duplicates it.

3. That surviving surface still deserves live authority under today’s state

  • Even when a sibling verb still exists and still looks standard-shaped, the protocol still has to decide whether that surface should remain money-good under this chain, asset, fallback, migration, liquidity, or wrapper state.
  • A helper that is safe only on one deployment, only with one asset domain, only while a buffer is full, or only before a customization fork should not keep ordinary production authority everywhere else.

What sound design looks like

  • One issuance boundary for all sibling minting verbs.
    • Whether the user calls deposit(), mint(), depositETH(), or another wrapper helper, the contract should still reach one canonical proof-of-backing step before claims are created.
  • One retirement boundary for all sibling exit verbs.
    • Whether the user exits by assets or shares, liability retirement should consume one canonical claim object rather than sibling-specific accounting shortcuts.
  • Explicit non-equivalence when semantics truly differ.
    • If the protocol genuinely wants one verb to mean something else, that should be visible in code and interface design: different function name, different docs, explicit revert on inherited siblings, or another hard separation that prevents accidental standard-shaped trust.
    • The design target is not merely the old selector is still callable. It is the old selector either still proves the same economic truth and still deserves authority here, or it is clearly deauthorized.
  • Fallback paths inherit the same invariant.
    • Pool depletion, empty buffers, partial fill branches, native-asset wrappers, or residual mint routes should not create a second softer solvency boundary.
  • Transfer semantics inherit the same invariant when balance moves also move entitlement.
    • If shares carry pending rewards, fee history, or reserve-backed withdrawal rights, plain balance movement must either preserve that same economic object exactly or settle it away exactly once before transfer finalizes.
  • Tests prove economic synonymy, not just happy-path functionality.
    • Passing unit tests for deposit() and mint() independently is weaker than proving they cannot disagree about solvency under the same state.

The minimum test discipline this note wants

1. Same-state sibling equivalence tests

  • Under the same state, compare sibling verbs as economic synonyms rather than as separate features.
  • Example checks:
    • deposit(x) versus mint(previewDeposit(x))
    • withdraw(x) versus redeem(previewWithdraw(x))
    • custom helper path versus inherited standard path under the same backing state
  • The exact share or asset rounding can differ within the standard’s allowed tolerance, but the backing boundary must not differ.

2. Empty-buffer and fallback-only tests

  • Force the states teams least want to simulate:
    • zero internal liquidity
    • depleted fast-withdraw pool
    • empty native-asset buffer
    • fallback-only residual mint path
  • These are the states where accidental sibling-path drift usually stops being masked by the honest first branch.

3. Transfer-hop conservation tests when balances carry entitlement

  • If a share balance also carries pending rewards, fee checkpoints, or withdrawal rights, test pure movement paths as sibling economic verbs too.
  • Example checks:
    • deposit -> transfer -> claim -> transfer -> claim -> transfer back -> withdraw
    • sender-plus-receiver combined claimable + withdrawable value before and after a pure transfer
    • transferFrom() and permit-funded pulls versus direct transfer() under the same accounting state
  • A balance move should not become a mint, replay, or claim-reopening surface merely because the accounting bug lives outside deposit() / withdraw().

4. Unsupported-verb assertions

  • If a product only supports one path safely, test that every stale inherited sibling verb reverts explicitly and remains reverted after upgrades.
  • Not in the docs is not enough; the unsupported verb needs an onchain failure mode.

5. Call-graph regression tests across upgrades

  • Diff not just balances and outputs, but which internal primitive each public verb reaches before claims become real.
  • An override can remain locally correct while globally unsafe if an inherited sibling still reaches the old hook.

6. Round-trip reserve-boundedness checks

Common red flags

  • We only customized one of the standard verbs.
  • That inherited sibling is still public, but users are supposed to ignore it.
  • The safe branch sources internal liquidity first, so the old mint path almost never runs.
  • The helper name is still _deposit()or_withdraw(), so upstream assumptions still look true.
  • The unsupported verb still passes preview functions and interface checks, but product docs do not mention it.
  • We tested normal deposit and withdraw flows, but not the empty-buffer or fallback-only states where sibling equivalence matters most.

Failure-prevention checklist

  • Enumerate every economically equivalent public and indirect surface that can create or retire the same claim: deposit, mint, withdraw, redeem, native-asset helpers, router entrypoints, migration paths, and any wrapper or internal-looking helper that can still reach the same liability object.
  • Force review to answer three separate questions for each sibling lane: does this surface still exist?, does it still hit the same backing or retirement primitive?, and does it still deserve live authority under this deployment, asset, liquidity, and fallback state?
  • Route all sibling issuance verbs through one canonical proof-of-backing primitive before claim quantity becomes final. If one path proves receipt while another inherits a stale helper, the standard surface is already split.
  • Route all sibling exit verbs through one canonical liability-retirement primitive. Asset-denominated exits, share-denominated exits, buffer-first helpers, and residual fallback branches should all consume the same claim object under the same state assumptions.
  • If plain balance-movement verbs still move the same economic claim, route transfer()-family surfaces through one canonical entitlement-carry or entitlement-settlement primitive too. Reward-bearing shares, fee-bearing shares, and reserve-backed wrappers do not get to treat inherited ERC-20 movement as out-of-scope plumbing.
  • Make unsupported non-equivalent sibling verbs fail closed onchain. If the protocol no longer intends one inherited standard path to be safe, disable it explicitly rather than relying on docs, frontend routing, or social convention.
  • Test same-state economic synonymy instead of only per-function happy paths. deposit(x) and mint(previewDeposit(x)) should agree on the same backing boundary; withdraw(x) and redeem(previewWithdraw(x)) should agree on the same retirement boundary.
  • Force the states where equivalence usually breaks: empty internal buffers, depleted pool-first branches, fallback-only routing, low-liquidity exits, native-asset wrapper paths, transfer hops across fresh addresses, and any state where one branch stops being masked by the favored path.
  • Keep preview, quote, and discovery helpers aligned with the real support boundary. If a sibling verb is dead, its preview surface should not keep advertising normal support to routers and integrators.
  • Add call-graph regression tests across upgrades so new inheritance order, helper reuse, or override drift cannot silently reopen the old entrypoint family.
  • Pair sibling-entrypoint tests with reserve-boundedness checks. The sharper question is not only whether two verbs agree with each other, but whether they agree on a claim that remains bounded by real backing.
  • Treat chain-specific, asset-specific, or wrapper-specific helpers as part of the same equivalence proof. If one sibling path only makes sense on one deployment surface, encode that constraint directly instead of assuming operators will remember it.

Audit prompts

  • Which exact public selectors, router calls, wrapper helpers, and internal primitives can create or retire the same economic claim today, and which of them remain reachable only because a standard or inherited surface was never explicitly deauthorized?
  • For each issuance-side sibling verb, where is backing actually proven, and do all of them reach the same receipt-validation primitive before claims become real?
  • For each exit-side sibling verb, where is liability actually retired, and do all of them consume the same claim object under the same accounting assumptions?
  • Are any sibling verbs still relying on inherited _deposit, _withdraw, quote, or preview helpers whose economic meaning changed after customization?
  • If the favored entrypoint were paused or depleted, what fallback branch would still create, mint, release, or retire value, and does it preserve the same solvency boundary?
  • Do preview and max* helpers still advertise support for a sibling verb that the product no longer safely honors onchain?
  • Under the same state snapshot, can deposit and mint, or withdraw and redeem, disagree materially about which reserve, buffer, or pricing state makes the claim valid?
  • After upgrades, inheritance reshuffles, router additions, or wrapper integrations, what explicit regression check proves the sibling-equivalence boundary stayed intact?
  • If balances are transferable, what exact check proves a pure transfer cannot reopen historical entitlement, duplicate claimable value, or move the claim without moving its consumed-state boundary too?
  • If one sibling verb is intentionally unsupported, what exact onchain failure mode proves it is dead at the economic boundary rather than only discouraged socially?
  • If this protocol lost money tomorrow through a standards-shaped surface, which seam would have split first: backing proof, claim finalization, retirement boundary, fallback routing, or unsupported-verb deauthorization?

Lessons that should already be standard

  • Standard siblings are equivalent security authority, not just ergonomic API variations. If they can create or retire the same claim, they must share one solvency boundary.
  • Still deployed is not the same thing as still economically equivalent, and still economically equivalent is not yet still deserves normal authority on this deployment and fallback state. Healthy integrations keep those three facts separate.
  • Customizing one standard path without re-proving the others is a balance-sheet change, not a local UX refactor.
  • Inherited helpers are not automatically safe just because their names still look canonical. Old helper semantics can survive under a familiar ABI long after the real invariant moved.
  • Unsupported onchain verbs are live authority until they revert explicitly. Users should not call that path is not a security control.
  • Fallback routing is part of entrypoint equivalence. A safe primary branch does not matter if the empty-buffer or residual branch can still create value under weaker truth.
  • If balances carry live economic rights, transfer() is a sibling entrypoint too. Inherited token mobility can be just as balance-sheet critical as deposit() or redeem().
  • Preview and discovery helpers sit inside the trust boundary once routers and integrators use them to decide which sibling path is viable.
  • Same-state synonymy tests are stronger than per-function correctness tests. Two verbs can each look locally correct while still disagreeing about what counts as backed issuance or valid retirement.
  • Upgrade review has to preserve economic equivalence, not only signature compatibility. Reopening one stale sibling path is enough to turn a familiar standard surface into a solvency exploit.

Where this note helps most

  • Start here when the design question is how should sibling standard verbs stay economically equivalent after customization?
  • Pair with shared-standard entrypoint invariant drift when the exploit-side question is how a stale inherited sibling verb stayed live after the real invariant moved.
  • Pair with unsupported standard-verb revocation and fail-closed surface when the right design answer is no longer keep every sibling verb equivalent, but keep only one safe route and prove the unsupported inherited verbs are truly dead.
  • Pair with reserve-backed claim truth as a single security object when the sharper follow-up is the broader reserve-boundedness object that all of those sibling verbs should still point at.
  • Pair with redemption solvency asymmetry when the exploit-side question is broader than standards or inheritance and the core failure is simply issue cheaply, redeem against real reserves.
  • Compare against first-deposit inflation when backing really did enter through all verbs, but low-supply exchange-rate math made one early issuance state manipulable.

Internal linkages

Control surface