Shared-Standard Entrypoint Invariant Drift / Inherited-Entrypoint Backing Bypass
- Name: shared-standard entrypoint invariant drift / inherited-entrypoint backing bypass
- Category: exploit pattern / standards-integration failure / inheritance footgun / unbacked issuance
- Summary: This pattern appears when a protocol keeps a familiar standard surface but quietly moves the real invariant to a different place in the call graph. A standard often assumes that several public entrypoints converge on one shared primitive that proves asset receipt, burns claims, or otherwise enforces the economic boundary. Teams then customize one entrypoint or hook, move the receipt check up a level, or split routing across internal liquidity and minting branches. If another inherited or still-public entrypoint continues to call the old shared primitive under the standard’s original assumptions, attackers can choose the stale path and obtain claims without the backing event the standard surface appears to promise. The failure is not just
bad inheritance; it isthe protocol still looks standard-shaped after the invariant moved elsewhere. - What it is:
- A standard exposes multiple public entrypoints that are supposed to mean the same economic thing through different user-facing parameters, such as
deposit()versusmint()in ERC-4626. - Those entrypoints normally converge on a shared internal primitive whose semantics the standard relies on.
- A protocol customizes part of that flow and moves the real asset-receipt, burn, or backing check to a different function or earlier call frame.
- Another inherited or still-enabled public entrypoint continues to route into the old primitive as if the original invariant were still enforced there.
- The attacker calls the stale entrypoint, bypasses the relocated invariant, and receives claims or value the protocol never actually backed.
- A standard exposes multiple public entrypoints that are supposed to mean the same economic thing through different user-facing parameters, such as
- Typical preconditions:
- The protocol inherits a standard with sibling public entrypoints that share internal accounting logic.
- The team overrides only one public entrypoint, or overrides a shared internal hook without re-auditing every inherited caller that still reaches it.
- Asset-transfer or receipt validation is moved out of the shared primitive into a wrapper-specific helper or one favored public path.
- A fallback branch such as
mint residual shares,source from pool then mint remainder, orburn from buffer then settle remaindercan still execute even when the honest asset-transfer leg did not happen. - Downstream pools, collateral adapters, or redemption routes still honor the printed claims once they exist.
- Common vulnerable surfaces:
- ERC-4626 vaults where
deposit()andmint()no longer share one honest receipt boundary - wrapper standards that preserve inherited public entrypoints while changing where assets are actually sourced or validated
- staking wrappers that first try internal liquidity and only later mint residual claims
- upgradeable contracts where inherited methods remain live after the new implementation changes the meaning of a shared hook
- standards with paired entrypoints such as deposit/mint, withdraw/redeem, or pull/push settlement paths whose equivalence is assumed but no longer true in code
- ERC-4626 vaults where
- Exploit sequence:
- The protocol changes a standard flow and relocates the true backing invariant away from the standard’s shared primitive.
- One inherited or still-public sibling entrypoint remains callable and still reaches that primitive under the old assumptions.
- The attacker first forces execution onto the branch where the honest liquidity-sourcing path is exhausted, skipped, or irrelevant.
- They call the stale entrypoint, which now reaches accounting and share-minting logic without the asset-transfer or other backing event having occurred.
- The protocol issues unbacked claims because the standard-shaped surface still looks valid while the real solvency boundary has drifted.
- The attacker cashes those claims out through sibling liquidity, external pools, collateral adapters, or redemption paths that still honor them.
- Variants:
- Receipt-check relocation — the asset-transfer proof moves from a shared primitive into one favored public entrypoint, but another inherited entrypoint stays live.
- Inherited sibling left public — a team customizes
deposit()or another top-level function but forgets thatmint()or a sibling path still reaches the internal accounting primitive directly. - Liquidity-first then mint fallback — the safe branch uses existing inventory first, but after pool depletion the contract falls through to an unsafe mint path that assumes backing already arrived.
- Partial standard override — some standard verbs are intentionally supported and others are only accidentally supported because inheritance was never fully closed off.
- Hook-semantics drift — a shared internal hook keeps the same name and call sites, but its meaning changes enough that old callers are no longer safe.
Three sibling-entrypoint promotions reviewers should not collapse
-
Some standard-shaped public entrypoint exists at all
- An inherited
mint(),deposit(),withdraw(),redeem(), router helper, or wrapper-facing selector is still live onchain. - That only proves
this verb is reachable.
- An inherited
-
That entrypoint still counts as the same economic verb the standard surface implies
- The harder question is whether the live caller still reaches the same proof-of-backing, claim-burn, or liability-retirement boundary as its sibling verb.
- Once one route moved receipt checking, inventory sourcing, or fallback mint semantics elsewhere,
same function familyno longer guaranteessame economic meaning.
-
That accepted sibling path still deserves live issuance or release authority right now
- Even a genuinely equivalent sibling verb should not automatically inherit unlimited same-window mint, withdrawal, or reserve-touching authority without caps, pauses, or other blast-radius controls.
- This is where bounded-loss design still matters after entrypoint equivalence is restored.
Healthy review gets materially better once teams stop flattening those three promotions into one vague claim like the standard verb is still supported.
- Detection heuristics:
- For every inherited standard, map every public entrypoint to the exact line where backing assets are proven received, burned claims are proven consumed, or another economic invariant is actually enforced.
- Diff the standard’s expected call graph against the customized implementation’s real call graph; if the real invariant moved, every inherited caller needs revalidation.
- Test same-state equivalence across sibling entrypoints. If
deposit(x)andmint(sharesFor(x))are no longer economically equivalent under the same state, ask exactly why. - Force depleted-internal-liquidity, zero-buffer, or fallback-only states in tests. Many of these bugs stay latent while the honest first branch still succeeds.
- Treat inherited methods as attack surface even when product docs tell users to ignore them. Publicly callable means security-relevant.
- Review whether standard helper names such as
_deposit()or_withdraw()still mean what upstream auditors assume they mean.
- Defenses:
- Keep the standard’s shared primitive as the real economic boundary whenever possible; if
_deposit()used to prove receipt, either preserve that property or fully retire every caller that assumes it. - If the invariant moves, override or disable every inherited sibling entrypoint that still depended on the old location.
- Write explicit invariant tests for sibling-entrypoint equivalence and for
no backing event, no claims issuedacross all public verbs. - Document the exact backing boundary in code comments and review notes so upgrades do not inherit stale assumptions from upstream standards.
- Prefer failing closed on unsupported standard verbs rather than leaving them accidentally live because inheritance made them available.
- Exercise fallback branches under empty-pool and thin-liquidity states before deployment; the dangerous path is often not the primary route but the residual route.
- Keep the standard’s shared primitive as the real economic boundary whenever possible; if
What sound design looks like
- One shared primitive should remain the actual solvency boundary for every standard verb that claims to mint, deposit, withdraw, redeem, or otherwise transform backing into claims. If the protocol wants different UX entrypoints, those entrypoints should still collapse onto the same proof-of-backing step.
- Unsupported verbs should fail noisily. If a product only wants
depositETH()plus one custom flow, inherited siblings likemint()ordeposit()should revert explicitly rather than surviving as accidental API surface. - Internal-liquidity shortcuts should still settle through the same accounting truth as residual minting.
Source from pool first, mint the remainderis only safe when both branches prove the same backing condition before claims leave the function. - Reviews should treat
entrypoint equivalenceas an invariant, not as a documentation aspiration. If two standard verbs are intended to be economic synonyms under the same state, tests should force them through thin-liquidity, empty-buffer, and fallback-only states and assert that neither can print extra value. Use Entrypoint equivalence under shared standards when the sharper design question becomes what one healthy sibling-entrypoint surface should still prove after customization. - If the product intentionally supports only one surviving route, the next job is not documentation polish but explicit deauthorization of the rest. Use Unsupported standard-verb revocation and fail-closed surface when the sharper follow-up is how to make unsupported inherited verbs, previews, wrappers, and upgrade paths stay truly dead.
- Upgrades should diff call graphs, not just function bodies. A custom override that looks locally correct may still be globally unsafe if an inherited sibling keeps reaching the old hook under upstream assumptions.
- The clean design question is: for every public standard verb, where exactly does the contract prove
backing enteredorclaims were genuinely retired, and do all sibling verbs hit that same boundary before any user-redeemable state changes? - High-signal incidents:
- [[../../../incidents/accounting-issuance-claims/meta-pool-mpeth-erc4626-mint-without-asset-transfer-unbacked-mint-pool-drain-2025-06-17|Meta Pool mpETH ERC-4626
mint()without asset transfer / unbacked mint pool drain (2025-06-17)]] — the clearest current exemplar: Meta Pool moved the receipt check out of the shared ERC-4626 issuance boundary, left inheritedmint()live, and let the stale sibling path print unbackedmpETHonce the honest internal-liquidity branch was exhausted.
- [[../../../incidents/accounting-issuance-claims/meta-pool-mpeth-erc4626-mint-without-asset-transfer-unbacked-mint-pool-drain-2025-06-17|Meta Pool mpETH ERC-4626
Failure-prevention checklist
- For every inherited standard verb, write down the exact line where backing is proven: assets received, claims burned, liquidity consumed, or some other real economic input verified. If two sibling verbs are supposed to mean the same thing, they should hit the same proof-of-backing boundary or one of them should be dead.
- Keep the shared primitive as the real invariant boundary whenever possible. If upstream
_deposit(),_withdraw(), or an equivalent helper used to be the place where receipt or retirement became true, preserve that property instead of moving it into one wrapper path and hoping the rest of inheritance still behaves. - If the invariant must move, override or explicitly revert every inherited sibling that still depends on the old primitive semantics, including previews, router helpers, wrappers, and upgrade-exposed legacy selectors.
- Add same-state sibling-equivalence tests:
deposit(x)versusmint(sharesFor(x)),withdraw(a)versusredeem(sharesFor(a)), and any custom wrapper verb versus the standard verb it economically replaces. - Force empty-buffer, depleted-internal-liquidity, and fallback-only states in tests. These bugs often stay hidden while the first branch still sources honest liquidity and only become exploitable once the residual mint or withdrawal branch is the only path left.
- Treat unsupported standard verbs as a deauthorization task, not a documentation task. If the product really only supports one custom flow, inherited standard routes should revert explicitly and stay reverted after upgrades.
- Diff full call graphs during upgrades and refactors. A safe-looking override can still be globally unsafe if some inherited entrypoint, proxy-exposed selector, or router path still reaches a primitive whose semantics changed.
- Add solvency assertions around every standard-shaped path: no user-redeemable claim balance may increase unless the matching backing event actually happened on that same path.
- Review internal-liquidity shortcuts and residual-mint branches as one accounting surface.
Use inventory first, mint the remaindermust prove that both branches produce the same liabilities from the same backing truth. - Make downstream redemption and liquidity paths part of the review scope. A stale issuance path is only dangerous because something else still honors the resulting claims.
Audit prompts
- For each public standard verb inherited from upstream, where exactly is the protocol proving that backing arrived or that liabilities were genuinely retired before balances change?
- Does every sibling entrypoint that issues the same claim token still converge on one shared economic boundary, or did one path move the receipt check while another path kept assuming the old semantics?
- Which inherited functions, router adapters, previews, helper wrappers, or upgrade-exposed selectors are still live even though product docs imply they are unsupported or irrelevant?
- What happens when internal inventory is empty, the fast path is exhausted, or the fallback branch becomes the only executable route? Does the residual path still prove backing before minting or releasing claims?
- If an attacker calls the least-used standard verb directly instead of the intended custom wrapper, what proof-of-backing is missing compared with the happy-path call flow?
- Are there any shared helpers whose names still imply upstream semantics like
deposit,mint,withdraw, orredeemeven though the real invariant moved somewhere else? - Do same-state calls through sibling verbs remain economically equivalent under thin liquidity, zero buffer, dust rounding, and fallback-only conditions, or can one path print more redeemable value than another?
- During upgrades, where is the explicit assertion that newly inherited selectors and old storage or helper assumptions did not resurrect a stale standard path?
- Can downstream markets, pools, lending adapters, or redemption functions still honor claims minted through the stale path, turning a local inheritance mistake into a solvency drain?
- If the team says
users should only call this custom entrypoint, what onchain mechanism actually enforces that statement?
Lessons that should already be standard
- Standard shape is not standard safety. Once a protocol keeps the API surface but moves the real invariant, every inherited sibling verb becomes a fresh solvency review target.
- Inheritance is part of the threat model. A public function the team did not intend to support is still a production entrypoint if the bytecode will execute it.
We changed only one pathis exactly how these bugs happen. Shared standards derive safety from sibling equivalence, so partial overrides deserve suspicion by default.- Empty-state and fallback-state testing should be routine. Many custom wrappers look safe in normal liquidity conditions and fail only when the system is stressed into the path nobody expected users to reach.
- Unsupported verbs should fail closed, not linger as accidental compatibility features. Leaving them callable because they are convenient or upstream-standard is a balance-sheet decision.
- Call-graph diffs matter more than local diff comfort. The dangerous bug is often not inside the changed function body but in the unchanged inherited function that still reaches a primitive whose meaning moved.
- The cleanest design is usually the least clever one: one shared backing boundary across sibling verbs, or explicit reverts everywhere else.
Where this pattern stops
- Use issuance-redemption-solvency-asymmetry when the system can already print claims under broken arithmetic, reserve math, or other issuance-side fiction even though there is no inherited-standard sibling-path mistake. Truebit fits there first: buy-side arithmetic broke while the redeem side still honored real reserves.
- Use this note when the decisive mistake is specifically that the contract still looks standard-shaped after the real invariant moved, and an inherited or still-public sibling verb keeps relying on the old location of that invariant.
- Use first-deposit inflation when backing really enters the vault but low-liquidity exchange-rate math lets the first shareholder steal later deposits. There the problem is not
stale sibling verb skipped the receipt check; it isreceipt shares were priced manipulably at near-zero supply. - Use donation-share-price manipulation when the protocol honestly issued claims first and only later let exchange-rate inflation make those claims overvaluable somewhere else. Meta Pool failed earlier: the claim itself could be minted without backing.
- Use interface-conformance semantic-compatibility failure when the bug is mainly that an admitted token or integration endpoint satisfied the right interface while violating deeper semantic assumptions. Here the dangerous drift lives inside the protocol’s own inherited-standard call graph, not in a hostile external asset or callback partner.
- In short: this family begins once a standard’s sibling verbs are no longer equivalent because the true economic invariant moved, but at least one inherited path still behaves as if it had not.
Internal linkages
- Parent solvency family: issuance-redemption-solvency-asymmetry.
- Positive-design companion: one backing boundary across sibling verbs — use this when the sharper follow-up is how sibling standard verbs should keep sharing one real proof-of-backing and liability-retirement boundary after customization.
- Narrower fail-closed companion: Unsupported standard-verb revocation and fail-closed surface — use this when the right answer is not preserving sibling equivalence across all verbs but proving that unsupported inherited routes and their discovery surfaces are explicitly dead.
- Adjacent ERC-4626 family: first-deposit inflation — another case where a standard-looking vault surface hides an attacker-shaped asset-for-share boundary, though the failure there is low-liquidity exchange-rate manipulation rather than stale inherited entrypoints.
- Adjacent share-pricing family: donation-share-price manipulation — useful when claims are not printed unbacked at issuance time but later made overvaluable through exchange-rate inflation.
- Adjacent compatibility family: interface-conformance semantic-compatibility failure — there the caller or asset only looks compatible with the protocol’s assumptions; here the protocol’s own inherited-standard surface only looks like one coherent solvency boundary.
- Useful contrast incident: truebit-purchase-bonding-curve-overflow-zero-cost-mint-eth-drain-2026-01-08 — same broad
issuance side prints claims, redemption side still honors reservesfamily, but the exploit enters through broken buy-side arithmetic rather than inherited-standard boundary drift. - Best current incident exemplar: meta-pool-mpeth-erc4626-mint-without-asset-transfer-unbacked-mint-pool-drain-2025-06-17.
Control surface
-
The leverage sits in where the real standard invariant actually lives, which inherited sibling entrypoints still reach the old primitive, what fallback branch executes after internal liquidity is exhausted, and whether downstream liquidity will still honor claims once the stale path prints them.
-
The practical lesson is simple: if a contract keeps the standard’s public shape after moving the standard’s real invariant, inheritance itself becomes a solvency boundary.
-
Sources:
- https://docs.openzeppelin.com/contracts/4.x/erc4626
- https://medium.com/meta-pool/meta-pool-security-incident-on-mpeth-contract-on-ethereum-ba7a328d45c6
- https://www.quillaudits.com/blog/hack-analysis/meta-pool-hack-analysis
- https://raw.githubusercontent.com/SunWeb3Sec/DeFiHackLabs/main/src/test/2025-06/MetaPool_exp.sol
-
Last reviewed: 2026-08-01 UTC