MetaPool Virtual-Price Normalization Failure / LP-Token Scaling Mismatch
- Name: MetaPool virtual-price normalization failure / LP-token scaling mismatch
- Category: exploit pattern / stable-swap accounting failure / metapool LP-token pricing bug
- Summary: This exploit family hits Curve-style metapools that pair one plain asset with the LP token of a base pool. The LP token is not a
$1stablecoin; its economic value drifts with the base pool’sgetVirtualPrice(). Safe metapool math therefore has to normalize that LP token symmetrically whenever it enters or leaves pricing calculations. If one swap path scales the LP token correctly but another path forgets to scale it up on input or down on output, the pool starts treating a drifting LP share like a flat stable asset. The attacker can then harvest too much base-pool LP on one leg, distort the metapool’s invariantD, and cash the inconsistency out on a second leg. Sometimes the profit comes from routing through one vulnerable path and one correct path, as in Nerve. Sometimes the same family first appears as a contained near miss, as in Synapse, where the AMM bug succeeded but the attacker failed to monetize through the paused bridge exit rail. Sometimes two vulnerable swaps are enough, as in Saddle, because the first under-accounted LP outflow pushesDto the wrong level and the reverse swap then prices the pool off that corrupted state. - What it is:
- A metapool holds one ordinary pooled asset plus the LP token of another base pool.
- The base-pool LP token has its own time-varying virtual price, so metapool math must translate between raw LP-token units and normalized pool value.
- At least one quote or execution path mishandles that translation: it fails to scale LP-token input up by base virtual price, fails to scale LP-token output back down, or otherwise applies inconsistent normalization across related functions.
- Because the vulnerable path updates reserves and invariant state from the wrong economic quantity, the pool’s
Dand subsequent price curve no longer reflect the value that actually left or entered the pool. - The attacker chains swaps so the metapool honors two different truths about the same LP token and pays the spread in real assets.
- Typical preconditions:
- The target metapool uses a base-pool LP token as one side of the pool.
- The protocol exposes multiple paths involving that LP token, such as
swap()versusswapUnderlying(), or multiple branches inside one swap path. - One or more of those paths normalize LP-token value inconsistently.
- The pool is liquid enough that the mispricing can be converted into meaningful stablecoin or base-pool outflows.
- No downstream guardrail verifies that a swap-then-reverse-swap cycle only loses fees under identical market state.
- Common vulnerable surfaces:
calculateSwap()or_calculateSwap()branches that treat the base-pool LP token like a unit-price stablecoinswap()paths that forget to divide outgoing LP-token amounts by base virtual price before transfer and bookkeeping- reverse swap paths that forget to multiply incoming LP-token value by base virtual price before quote calculation
- codebases with both
swap()andswapUnderlying()implementations whose normalization logic drifts over time - redeployments or library-linked pools where the source repository is patched but one live pool still points at an older library
- Exploit sequence:
- The attacker identifies a metapool whose LP-token normalization is inconsistent across swap paths or swap directions.
- The attacker performs a first swap that makes the vulnerable path hand out too much base-pool LP token or under-account for the value that just left the pool.
- That bad accounting pushes the metapool’s invariant
Dand reserve state away from the economically correct curve. - The attacker immediately performs a second swap or routes through a second path that prices against the corrupted state.
- The metapool pays out more stablecoin or underlying value than the attacker economically put in.
- The attacker repeats until the pool is exhausted or defenders pause the contracts.
- Variants:
- Vulnerable-vs-correct path arbitrage — one path mishandles LP-token normalization while another path prices the same LP token correctly, so the attacker harvests excess LP on the first leg and sells it on the second.
- Two-vulnerable-swaps invariant-shift exploit — even without a correct second path, the first vulnerable swap moves
Dfar enough off reality that the reverse vulnerable swap becomes profitable. - Output-side normalization failure — outgoing LP-token amount is not scaled down before transfer, so the pool sends too many LP shares.
- Input-side normalization failure — incoming LP-token amount is not scaled up before quote calculation, so the reverse leg prices too favorably from a corrupted state.
- Stale-library redeploy — the canonical source tree is patched, but one live pool still links to the old vulnerable helper library.
Three metapool-LP value promotions reviewers should not collapse
-
A base-pool LP-token state change exists at all
- The metapool now holds or releases some raw amount of base-pool LP token, and the base pool’s
getVirtualPrice()implies some normalized economic value for that amount. - This is only the weak fact that
an LP-token quantity moved. It does not yet mean the metapool updated reserves, invariant state, or quotes using the right normalized value.
- The metapool now holds or releases some raw amount of base-pool LP token, and the base pool’s
-
The protocol publishes one canonical metapool value story from that movement
- A particular
swap(),swapUnderlying(), quote helper, or invariant-update path converts the raw LP-token amount into reserve deltas and a newD. - This is the dangerous promotion metapool bugs keep getting wrong: the system stopped saying merely
LP movedand started sayingthis is the authoritative economic value that now defines pool state.
- A particular
-
Downstream paths still spend against that published story
- Reverse swaps, sibling swap paths, routers, redemptions, and bridge or exit rails then honor the corrupted reserve / invariant story as if it were already money-good solvency truth.
- That is a stronger claim than
one branch mis-scaled an LP token. The real loss arrives because later paths are still willing to release stablecoins, underlying, or bridged value against the bad canonical state.
Healthy review gets materially better once teams stop flattening those promotions into one vague sentence like the metapool mispriced the LP token.
- Detection heuristics:
- Diff every LP-token-bearing path against every other one;
swap(),swapUnderlying(), quote helpers, and deposit/withdraw helpers should agree on where base virtual price is applied. - Treat any path asymmetry around base-pool LP tokens as high risk.
scale up on inputandscale down on outputshould be explicit and test-covered, not implicit. - Run differential tests where a metapool LP-token swap is reversed immediately under unchanged market state. The expected result should be a fee loss, not a gain.
- Compare deployed library addresses and bytecode against the patched repository version; a fix in source is irrelevant if a live pool still points to stale code.
- Review invariant movement directly. If a single swap can push
Ddownward because the pool under-accounted outgoing LP value, the pool is already lying to itself about solvency.
- Diff every LP-token-bearing path against every other one;
- Defenses:
- Centralize LP-token normalization in one helper used by all quote and execution paths.
- Add differential tests between
swap()andswapUnderlying()for the same economic route. - Add round-trip property tests ensuring a swap followed by the reverse swap cannot profit absent fee rebates or external price movement.
- Verify deployed library addresses and bytecode as part of release and post-deploy monitoring, not just source control review.
- Treat base-pool LP tokens as their own asset class with explicit unit-conversion discipline, not as just another stablecoin slot.
What sound design looks like
- A healthy metapool design keeps three questions separate:
what raw LP-token movement happened,what canonical normalized value story did the pool publish from it, andwhich later paths are allowed to spend against that published story. Those should not collapse into one unreviewedswap updated statemoment. - A metapool that holds a base-pool LP token should have one explicit rule for translating between
raw LP unitsandnormalized economic value, and every quote, swap, add/remove-liquidity, fee, and invariant-update path should call that same rule. - The healthy mental model is
LP token is a drifting receipt token, notLP token is just another stable slot. The virtual-price conversion is part of the asset definition, not a convenience adjustment for one function. - Quote helpers and execution paths should agree exactly. A reviewer should be able to diff
swap(),swapUnderlying(),_calculateSwap(), and any view helpers and see the same normalization semantics rather than path-specific arithmetic folklore. - Deployment provenance is part of solvency. If source control says the normalization bug is fixed but one live pool still links an old library, the protocol is still vulnerable in the only place that matters.
- The clean reviewer question is:
where do we prove that every path moving base-pool LP uses the same value transformation before reserves and invariant state are updated?
Failure-prevention checklist
- Centralize base-pool LP normalization in one helper or library routine and make every path call it instead of hand-rolling scale-up or scale-down math.
- Differential-test
swap(),swapUnderlying(), add/remove-liquidity helpers, and quote views against the same economic route so path drift becomes visible before deployment. - Add round-trip properties that require a same-block swap followed by the exact reverse route to lose only fees under unchanged market state, never produce positive PnL.
- Assert invariant sanity directly: after any trade,
Dshould move in a way consistent with the economically normalized value that actually entered or left the pool. - Treat base-pool LP tokens as a special asset class in code review, with explicit comments around where
getVirtualPrice()is applied on input and output. - Gate deployments on bytecode and linked-library verification, not just repository diffs, especially for pools inheriting previously patched math helpers.
- Maintain an inventory of all live pools and the exact library addresses or implementation hashes they depend on so stale-library survivors cannot hide in production.
- Include emergency pause criteria for any pool whose LP-token normalization semantics are later questioned; repeated loopable arbitrage means response speed matters.
Audit prompts
- Which exact functions convert raw base-pool LP units into normalized value, and do all quote and execution paths reuse that exact logic?
- Does outgoing LP-token math divide by base virtual price exactly where incoming LP-token math multiplies by it, or has one side silently drifted?
- If a trader executes
swap()and then reverses throughswapUnderlying()under unchanged state, can the pool ever return more value than it received apart from fees or rebates? - Can one bad first trade push invariant
Daway from economically correct state, so later trades become profitable even without an external correctly priced path? - Are any deployed pools still linked to older helper libraries or implementations even though the repository, audit report, or docs describe the bug as fixed?
- Where is library-address / implementation-hash verification captured in deployment tooling, release review, and post-deploy monitoring?
- If the metapool pairs a plain stablecoin with a base-pool LP token, where is the assertion that those two assets are never treated as unit-price peers without normalization?
- Do view functions, simulations, and offchain routers reproduce the same normalization semantics as execution, or could offchain tooling mask a path-dependent bug until funds are live?
- What exact invariant or differential test would fail if one branch forgot to apply
getVirtualPrice()tomorrow? - If a public exploit in a sibling fork lands, how quickly can the team enumerate every live pool sharing that math and halt or disable them?
Lessons that should already be standard
- A base-pool LP token is not a stablecoin slot. It is a receipt token with a drifting claim on another pool, and forgetting that for one branch is enough to create a drain.
- Path symmetry is a solvency invariant, not just a code-style nicety. If two routes price the same LP token differently, the pool is an arbitrage machine against itself.
Fixed in sourceis not a defense. Live bytecode, linked libraries, and deployment manifests are the real security boundary.- Invariant corruption compounds. A bad first quote does not merely overpay once; it can move
Dso that later trades keep paying from the same accounting lie. - Differential tests are more valuable than local unit tests for this family. The bug often hides in disagreement between two individually plausible functions.
- Forked metapool code needs herd-level response. Once one Saddle-derived or Curve-style implementation fails, sibling deployments should be treated as presumptively at risk until proven otherwise.
- Virtual-price handling belongs in explicit API and code comments. If reviewers must mentally reconstruct where scaling happens, one path will eventually forget.
High-signal incidents
- bridge-halt near miss (2021-11-06) — the contained early warning: the Saddle-derived metapool bug already let the attacker drain about
$8.2Mfrom Avalanche LPs, but validators halted the bridge exit before the attacker could mint the value out on another chain. - UST MetaPool virtual-price normalization exploit (2021-11-15) — the precursor on a Saddle-derived codebase where the AMM-side exploit completed cleanly: vulnerable
swap()handed out too much base-pool LP, thenswapUnderlying()converted that excess LP back through a correctly normalized path for profit. - Saddle Finance sUSDv2 MetaPool virtual-price scaling exploit (2022-04-30) — the canonical redeploy failure: a live Ethereum metapool still pointed at the old vulnerable
MetaSwapUtilslibrary, and two uses of the vulnerableswap()path were enough because the first swap pushedDbelow reality before the reverse leg cashed out.
Where this pattern stops
- Use lp-token-oracle-mispricing when the decisive mistake is an external lending market, vault, or reward system over-trusting an LP-token mark derived from reserve state. This note is for cases where the metapool itself misprices the base-pool LP token inside its own invariant math.
- Use amm-invariant-scaling-mismatch when the sharper story is broken AMM invariant math or fee-constant scaling without a special role for a drifting base-pool LP receipt token.
- Use unit-normalization-failure when the important mismatch is token decimals or unit conversions across assets rather than omission of base-pool virtual-price normalization for one LP constituent.
- Use release provenance failure only when deployment drift is the main analytical weight and the pool math itself is not the dominant lesson. Saddle belongs here first because the stale deployment re-exposed a very specific LP-normalization bug.
- In short: this pattern begins when a metapool holds a base-pool LP receipt token and different paths stop agreeing on how that receipt token maps to economic value. It stops once the sharper story is external LP valuation, generic invariant math breakage, plain decimal drift, or release-process compromise without distinctive metapool semantics.
Internal linkages
- Positive-design companion: Metapool LP normalization and path-equivalence discipline — use this when the sharper question is how a protocol should keep one canonical value transformation for base-pool LP receipt tokens across
swap(),swapUnderlying(), invariant updates, linked libraries, and deployment inventories before path drift can become self-arbitrage. - Best preserved contained early-warning case: synapse-protocol-nusd-metapool-virtual-price-normalization-exploit-bridge-halt-near-miss-2021-11-06 — useful when the exploit primitive succeeded inside the AMM but operator action still blocked the bridge or exit rail cash-out.
- Best preserved precursor cash-out case: nerve-bridge-fusdt-ust-metapool-virtual-price-normalization-exploit-2021-11-15 — the clean vulnerable-path-versus-correct-path arbitrage form.
- Best preserved invariant-corruption case: saddle-finance-susdv2-metapool-virtual-price-scaling-exploit-2022-04-30 — the stronger later branch where two uses of the vulnerable path were enough.
- Adjacent pattern: amm-invariant-scaling-mismatch — both families are math-consistency failures inside AMM solvency logic, but this note is specifically about asymmetric normalization of a drifting base-pool LP token rather than mismatched fee constants in a constant-product check.
- Adjacent pattern: donation-share-price-manipulation — both families turn a share-like instrument into false solvency, but here the failure is inside the metapool’s own pricing of an LP token rather than a lending market or oracle over-trusting a manipulated exchange rate downstream.
- Adjacent pattern: lp-token-oracle-mispricing — use that note when a separate protocol overvalues LP collateral from reserve math or an oracle; use this note when the AMM holding the LP token misprices it internally because normalization logic is inconsistent.
- Adjacent deployment-control companion: release-provenance-failure — useful when a patched math bug survives because bytecode, library linkage, or deployment provenance drifted from the reviewed source tree.
Control surface
- The leverage sits in the exact places where raw base-pool LP-token units become normalized pool value and back again.
- If one path applies
getVirtualPrice()and another path silently skips it, the metapool is no longer enforcing one coherent invariant. It is enforcing path-dependent accounting fiction.
Sources
- https://docs.saddle.finance/solidity-docs/meta/metaswaputils
- https://medium.com/synapse-protocol/11-06-2021-post-mortem-of-synapse-metapool-exploit-3003b4df4ef4
- https://blocksec.com/blog/the-analysis-of-nerve-bridge-security-incident
- https://blocksec.com/blog/how-to-exploit-the-same-vulnerability-of-meta-pool-in-two-different-ways-nerve-bridge-saddle-finance-what-you-see-is-not-what-you-get
- https://curve.fi/files/stableswap-paper.pdf
Last reviewed
- 2026-07-31 UTC