Issuance / Redemption Solvency Asymmetry

  • Name: issuance / redemption solvency asymmetry
  • Category: exploit pattern / arithmetic-solvency failure / reserve-backed pricing asymmetry / bonding-curve failure
  • Summary: Issuance / redemption solvency asymmetry happens when a protocol exposes two sides of the same economic promise — mint versus redeem, buy versus sell, quote versus cash-out, or add liquidity versus remove liquidity against the same economic claim — but only one side stops tracking reality. The attacker does not need an oracle, a flash loan, or another protocol. They use the broken side to mint, buy, or otherwise acquire claims far too cheaply, then immediately present those claims to the still-honest side, which keeps paying out against a real reserve. Once one leg prints fiction and the other leg honors reality, the spread itself becomes the drain path. The cleanest modern cousin is not always bad math; it can also be bad boundary placement, where a standard’s shared issuance primitive no longer enforces asset receipt but an inherited public entrypoint still assumes it does, or where a preview / quote helper quietly becomes the protocol’s real payment certificate. The important design point is that intentional pricing asymmetry is not itself the bug: convex buy-side and linear sell-side logic can coexist, but only if same-state round trips still stay reserve-bounded.
  • What it is:
    • A protocol holds a real reserve and offers some way to issue or sell claims against it.
    • It also offers the inverse path: burn, redeem, or sell those claims back into the reserve.
    • The system assumes both directions are constrained by one coherent pricing function or at least one coherent solvency envelope.
    • In practice, one side can fail alone through unchecked arithmetic, scaling drift, stale cached quote logic, or another local bug.
    • The attacker crosses the broken boundary in the cheap direction, then exits through the still-honest boundary in the expensive direction.
  • Typical preconditions:
    • Mint/quote logic and redeem/payout logic live in different functions, formulas, or code generations rather than one algebraically shared path.
    • At least one side depends on large intermediate arithmetic, manual scaling, or legacy unchecked math.
    • A standard or inherited interface exposes multiple issuance entrypoints that are supposed to converge on one shared primitive, but a customization quietly moves the asset-receipt guarantee somewhere else.
    • The reserve-paying side still honors real balances and remains callable without meaningful throttles.
    • The protocol lacks per-transaction caps, circuit breakers, or reserve-outflow guards that would stop repeated same-transaction cycling.
    • Operators assume the spread itself is a safety margin even though only one side of the spread is actually broken.
  • Common vulnerable surfaces:
    • bonding-curve contracts with separate buy and sell math
    • treasury buyback or continuous-token contracts that mint from a formula but redeem from a live reserve
    • intentionally asymmetric reserve systems where the buy side is convex, fee-loaded, or approximate while the sell side is linear or reserve-proportional, and review quietly proves monotonicity instead of reserve-boundedness
    • wrappers or receipt-token systems where issuance uses one quote path and redemption uses another reserve-based path
    • quote, preview, or helper functions whose output is later treated as sufficient proof that the user paid enough to mint a redeemable claim
    • concentrated-liquidity or vault-like systems where add liquidity / mint position math can undercharge while remove liquidity / burn position still releases real assets against the resulting claim
    • inherited vault or wrapper standards such as ERC-4626 where teams override only part of the shared issuance flow and leave other public entrypoints still pointing at a primitive whose semantics have changed
    • legacy contracts whose buy-side arithmetic predates checked-overflow semantics while the reserve side still remains economically live
    • systems that cache quote inputs or use approximations on the issuance side but settle redemptions against current reserve truth
  • Failure sequence:
    1. A local bug makes the issuance side quote a large position at zero, near zero, or otherwise far below the reserve value it can later command.
    2. The attacker acquires an outsized amount of claims through that broken path.
    3. They immediately approve, burn, redeem, or resell those claims through the payout side.
    4. The redemption side still honors real reserve withdrawals because its own arithmetic or reserve accounting remains intact.
    5. The attacker repeats the cycle until reserve liquidity, a cap, or outside intervention finally stops the drain.
  • Variants:
    • Unchecked-overflow issuance — a large buy or mint amount wraps an intermediate term and collapses purchase cost.
    • Scaling-only-on-one-side failure — decimal or unit normalization breaks issuance math while redemption still uses economically correct reserve accounting.
    • Liquidity-quote collapse — position-minting or liquidity-add math undercharges for a claim that the burn / removal side still values correctly against real reserves.
    • Approximation / cache asymmetry — issuance relies on stale or approximate state while redemption settles against live reserve reality.
    • Inherited-entrypoint divergence — a team moves asset-transfer enforcement out of a shared issuance primitive, but another inherited public entrypoint still reaches that primitive and can now mint claims without backing.
    • Transfer-success semantic divergence — the issuance side treats transferFrom returned as proof that assets arrived, but the integrated token signals failure with false rather than revert, so shares are minted against no backing at all.
    • Fee-path asymmetry — one side includes the intended spread, premium, or haircut while the other side accidentally omits it, turning the protocol’s own round trip into a profit source.

Three promotions reviewers should not collapse

  1. The protocol published or computed an issuance quote

    • A helper, bonding-curve formula, preview, liquidity-add calculator, or mint estimator produced a number that suggests how many claims the user should get.
    • This is still only a pricing or accounting claim. By itself it should not yet prove that enough value entered or that the reserve now owes anything.
  2. The protocol treated that quote as sufficient proof to issue a live claim

    • The dangerous promotion is when the quote output, partial transfer check, inherited standard primitive, or stale approximation stops being informational and becomes a mint certificate.
    • This is where many incidents actually cross the line: the system has not merely mis-estimated value; it has converted the mis-estimate into a live claim on reserves.
  3. The reserve side still honored that claim as ordinary payout truth

    • The final and most expensive promotion is when redemption, treasury payout, LP removal, or reserve withdrawal still spends real assets against the newly created claim as if issuance and payout still shared one coherent solvency envelope.
    • Keeping these steps separate is analytically useful because protocols often notice the first step (quote math looks odd) while missing the second (the odd quote can already mint) or third (the reserve path still treats the minted claim as money-good immediately).
  • Detection heuristics:
    • Ask whether mint -> redeem immediately can ever be profitable without market movement. If yes, the protocol may already be one arithmetic bug away from reserve drain.
    • Review whether buy and sell logic are algebraic inverses in code, not just in design docs. If they are intentionally not inverses, prove the stronger property the protocol actually needs: immediate round trips must still be reserve-bounded.
    • Fuzz extreme requested amounts on the issuance side and compare the result to the reserve value the same claims can later withdraw.
    • Differential-test the buy / mint path against the sell / redeem path under the same reserve snapshot. The useful question is not only does each side look plausible alone? but can the two sides disagree enough to print redeemable spread from nothing?
    • In large-number quote paths, inspect the final composed numerator / denominator terms rather than only the helper multiplications. Truebit is the reminder that a curve can look mathematically sensible until one last unchecked addition wraps and turns a convex quote into a free mint.
    • Map which exact helper, preview, quote output, or pricing subroutine is treated as sufficient payment proof for live minting. Once another path trusts that output to create redeemable claims, that helper is already a reserve-certification surface.
    • For inherited standards, map every public issuance entrypoint to the exact line where backing assets are proven received. If that proof moved out of a shared primitive, verify that no inherited function can still bypass the new boundary.
    • Track reserve-boundedness directly: after any issue -> redeem round trip, reserve loss should never exceed net external value that genuinely entered, minus explicit fees the protocol meant to pay out.
    • Treat old or unverified pricing contracts as live solvency surfaces, especially if they still custody meaningful reserves.
    • Inspect the last unchecked addition, multiplication, and division in quote paths; the decisive bug is often not the obvious main formula but the final composition step.
  • Defenses:
    • Prefer one shared pricing library or one formally checked inverse relationship instead of independently maintained buy and sell formulas.
    • Treat reserve-backed claim truth as a single security object as the design target: the same object that proves value intake should also bound claim creation, entrypoint reachability, reserve liability, and same-state round-trip losses.
    • Keep quote publication, claim issuance, and reserve spend authority as deliberately separate promotions. A computed number should not become a live redeemable claim until receipt is proven, and a newly issued claim should not automatically gain full reserve-paying authority without the same solvency object still holding.
    • When the protocol intentionally uses different shapes on the two sides — for example convex purchase pricing but linear reserve redemption — prove reserve-boundedness directly instead of relying on the intuition that the spread looks wide enough.
    • Use checked arithmetic everywhere on solvency-critical quote paths and bound user-supplied amounts before large intermediate math.
    • If a customization moves asset-transfer or receipt checks out of a shared standard primitive, override or disable every inherited public entrypoint that still assumes the old primitive enforces backing.
    • Add reserve-outflow caps, per-transaction issuance caps, and emergency pause logic so one bad quote cannot empty the reserve in one call sequence.
    • Continuously test round-trip invariants such as redeem(mint(x)) <= x + tolerated error across extreme input ranges.
    • Treat dormant reserve-holding contracts as production systems until they are emptied or disabled; age is not a safety control.

What sound design looks like

  • Healthy reserve-backed systems treat how claims are created and how reserves leave as one object, not two product features. The same accounting boundary that proves value intake should constrain issuance quantity, reserve liability, and immediate round-trip losses.
  • If the protocol intentionally exposes asymmetric pricing — for example a convex purchase curve, fee-loaded entry, or cached issuance quote against a simpler reserve redemption rail — it still needs one stronger invariant: in the same state, no user should be able to mint claims whose immediate redemption value exceeds what just entered minus the protocol’s deliberate fees.
  • Unsupported issuance verbs should be explicitly dead. If the design only wants one mint path, every sibling standard entrypoint, helper wrapper, router adapter, and upgrade-exposed legacy selector should either share that same receipt boundary or revert.
  • Quote logic should be reviewable as solvency logic, not UX math. preview, estimate, bonding-curve helpers, liquidity-add formulas, and wrapper conversion functions become custody surfaces the moment another path will honor the resulting claims against live reserves.
  • A helper stops being just a quote once live issuance trusts it as sufficient proof that the user paid enough. The exact output that certifies this much reserve buys this much claim is already part of the reserve boundary, even if the codebase labels it as informational or reusable math.
  • Fast exits need limits that assume local pricing can fail. Reserve-outflow caps, issuance caps, and pause / circuit-breaker hooks are not substitutes for correct math, but they keep one broken quote from becoming instant treasury extinction.
  • Empty, legacy, and fallback states deserve first-class design treatment. The dangerous path is often not the happy path but the exhausted-buffer branch, the deprecated reserve contract still holding assets, or the max-input arithmetic path nobody expected users to hit.
  • When the deeper follow-up is how to keep value intake, claim quantity, entrypoint reachability, and reserve liability fused together, use reserve-backed claim truth as a single security object.

Failure-prevention checklist

  • Write down every public or indirectly reachable issuance path and the exact line where external value is proven to have entered before claims are minted, credited, or released.
  • Add explicit same-state round-trip invariants for every reserve-backed claim surface: issue -> redeem, buy -> sell, add -> remove, mint -> burn, and any custom wrapper or router path that economically substitutes for those verbs.
  • If buy-side and sell-side math intentionally differ, prove reserve-boundedness directly across extreme ranges instead of only checking monotonicity or fee intuition.
  • Bound user-controlled amounts before any large intermediate multiplication, exponentiation, or scaling composition, and keep checked arithmetic enabled on every solvency-critical quote path.
  • Review fallback and depleted-inventory branches as primary attack surface. If honest inventory disappears, the residual path still must not mint more claim value than the reserve can safely honor.
  • Disable or explicitly revert unsupported inherited standard verbs, legacy contracts, and migration-era selectors that can still reach live reserve or issuance logic.
  • Add reserve-outflow caps, per-block or per-transaction issuance limits, and operator pause rails sized for quote is wrong right now, not just for normal user behavior.
  • Differential-test live quote helpers against redemption reality on the same state snapshot; if a helper can overstate mintable claim value, downstream payout paths must not trust it blindly.
  • Treat helper outputs as payment-certification surfaces when live minting trusts them. If a preview, quote, or estimator says this much payment is enough, review its final composed output with the same rigor as the external mint or liquidity-add function that consumes it.
  • Treat thin-liquidity and near-empty states as required test fixtures, including depleted pools, one-position books, dust supply, max-amount inputs, and legacy reserves with lingering balances.
  • Keep downstream monetization rails in scope: external AMMs, lending adapters, treasury exchanges, and redemption modules are what turn a local issuance mistake into a realized drain.

Audit prompts

  • Under the same reserve snapshot, can any reachable path make mint then redeem, buy then sell, or add then remove profitable without market movement?
  • Which exact code path proves assets arrived before claims are issued, and does every sibling entrypoint actually hit that proof boundary?
  • Are buy-side and sell-side formulas truly inverse, or merely intended to feel compatible in design docs? If they are intentionally asymmetric, where is reserve-boundedness tested instead?
  • What are the largest intermediate arithmetic terms in issuance math, and what happens at max user input, extreme decimals, fee edge cases, and legacy parameter settings?
  • Which exact helper output is being treated as the payment certificate for minting or liquidity issuance, and does its final composed step still preserve the same reserve bound the redemption side later honors?
  • Do any preview helpers, cached quotes, pool-local state variables, or integration assumptions let the issuance side undercharge while the redemption side still settles against live reserve truth?
  • If internal inventory, fast-unstake liquidity, or pool reserves are first consumed and residual claims are then minted, what prevents the residual branch from acting as a free-issuance path once the honest inventory is gone?
  • Which old contracts, inherited selectors, migration shims, or dormant treasury surfaces can still mint claims or pay reserves even though product teams think they are obsolete?
  • What independent cap, haircut, or pause condition limits damage if the issue-side quote is wrong by 10x or 10,000x for one block?
  • Which external or sibling liquidity surfaces would still honor freshly minted claims as money-good immediately after issuance, and have those rails been considered part of the exploit path?
  • If the team claims the spread or fee schedule makes round trips safe, where is the explicit proof that same-state round trips remain reserve-bounded across all reachable paths?

Lessons that should already be standard

  • Buy-side and sell-side logic are one balance-sheet surface even when code owners, formulas, or products are split across modules.

  • Published quote, issued claim, and reserve-paying liability are three different authority levels. Collapsing them into one step is how local pricing bugs become treasury drains.

  • A spread is not a safety proof. If one side can mint fiction and the other side still pays reality, the protocol’s own asymmetry becomes the drain path.

  • Preview and quote code are inside the security boundary once reserve-paying paths trust the claims those helpers help create.

  • A helper stops being just a preview the moment live minting trusts it as sufficient payment truth. The naming does not reduce its custody significance.

  • Fallback branches deserve the same audit energy as the primary route. Many of the cleanest drains start only after honest inventory is exhausted.

  • Inherited or dormant issuance paths are not dead just because users are told not to use them. Public reachability is enough.

  • Extreme-input arithmetic testing is not optional for reserve contracts. The decisive bug is often in the last scaling or overflow edge, not the obvious headline formula.

  • If a claim can be created cheaply and redeemed honestly in the same state, the protocol does not have a pricing quirk; it has a solvency bug.

  • Invariant tests worth preserving:

    • Round-trip non-profitability — for any reachable state, redeemValue(issueCost(x)) or sell(buy(q)) should not return more reserve value than the attacker just paid in, once explicit fees and tolerated rounding are applied.
    • Reserve-bounded issuance — the issuance side should never mint claims whose immediate redemption value exceeds the external value that entered on that path.
    • Entrypoint-equivalence under shared standards — if a standard exposes multiple issuance entrypoints such as deposit() and mint(), each one must either prove backing assets arrived itself or be hard-disabled; no inherited path may mint claims against a primitive whose receipt semantics were moved elsewhere.
    • Single-sided arithmetic cannot outrun the reserve — if one side uses approximations, caches, or special-case math, compare it against the reserve-paying side on the same state snapshot and fail whenever the quote side creates more claim value than the payout side can safely honor.
    • Edge-case symmetry — one-leaf, max-amount, near-overflow, zero-fee, and dust states should all preserve the same buy then sell / mint then redeem inequality; security cannot rely on the assumption that only ordinary trade sizes matter.
    • Legacy contract liveness — any reserve-holding contract left callable after migrations or deprecation should keep passing the same round-trip and reserve-boundedness tests as current production paths.
  • High-signal incidents:

    • zero-cost mint ETH drain (2026-01-08) — the clean legacy-arithmetic exemplar: a buy-side overflow let huge TRU mints cost zero or near-zero ETH, while the sell-side path still redeemed against the real ETH reserve.
    • [[../../../incidents/accounting-issuance-claims/fantasm-finance-xftm-mint-missing-ftm-input-validation-collateral-pool-drain-2022-03-09|Fantasm Finance XFTM mint() missing FTM-input validation / collateral-pool drain (2022-03-09)]] — the clean fractional-synthetic exemplar: Fantasm’s mint path priced XFTM as if both FTM and FSM had entered, but only enforced the FSM leg, so the attacker printed undercollateralized synthetic claims and immediately dumped them into the still-real FTM reserve.
    • [[../../../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 clean inherited-standard cousin: Meta Pool moved the receipt check out of the shared ERC-4626 issuance primitive, left inherited mint() live, and let attackers print unbacked mpETH once the fast-unstake pool was emptied.
    • [[../../../incidents/approvals-wallets-integrations/forcedao-xforce-minime-transferfrom-false-return-unbacked-share-mint-drain-2021-04-04|ForceDAO xFORCE MiniMe transferFrom false-return / unbacked-share mint drain (2021-04-04)]] — the clean token-semantics cousin: the vault minted xFORCE shares after a FORCE transfer failed silently with false, then the redeem side still paid out real vault inventory.
    • unbacked mint drain (2024-09-26) — the chain-surface deployment cousin: Bedrock left a payable mint() path meant for native-BTC environments live on non-BTC chains, so the contract converted msg.value into uniBTC by decimal scaling alone and downstream liquidity still honored the BTC-like claim.
    • [[../../../incidents/accounting-issuance-claims/cetus-protocol-clmm-checked-shlw-truncation-liquidity-overmint-pool-drain-2025-05-22|Cetus Protocol CLMM checked_shlw truncation / liquidity-overmint pool drain (2025-05-22)]] — the concentrated-liquidity cousin: Cetus undercharged the add liquidity side because a bad checked_shlw() guard let the token-A requirement wrap down to about 1 unit, while the remove liquidity side still honored the resulting pool-scale position against real reserves.
    • [[../../../incidents/accounting-issuance-claims/velocore-v2-constantproductpool-unauthenticated-velocore-execute-feemultiplier-overflow-liquidity-overmint-drain-2024-06-02|Velocore V2 ConstantProductPool unauthenticated velocore__execute() / feeMultiplier overflow liquidity-overmint drain (2024-06-02)]] — the fee-state corruption cousin: Velocore let direct callers push a pool-local fee multiplier past the math’s safe domain, so a tiny withdrawal-side input minted an outsized LP claim that ordinary pool exits still honored against real reserves.
    • Balancer drain (2021-05-12) — the self-priced issuance exemplar: xToken inferred SNX value for minting from the attacker’s own manipulated Kyber-routed acquisition result, then still let the attacker exit through redemption and Balancer liquidity priced on firmer rails.
    • treasury drain (2022-07-28) — the protocol-native treasury cousin: Nirvana’s own postmortem said ANA could be bought at an artificially low internal price, yet the same protocol still let ANA exit against real treasury stablecoins, so a large flash-loan-funded buy both printed a higher ANA mark and monetized it through reserve payouts.
    • [[../../../incidents/accounting-issuance-claims/belt-finance-strategyalpaca-balancesnapshot-undercount-share-overmint-near-miss-2021-08-10|Belt Finance StrategyAlpaca balanceSnapshot undercount / share-overmint near miss (2021-08-10)]] — the useful yield-strategy near miss where a direct transfer changed the withdrawal branch, balanceSnapshot was decremented as if strategy assets had been unwound when they had not, and the next depositor could buy too many vault shares before redemption math normalized back to real reserves.
    • [[../../../incidents/accounting-issuance-claims/thala-v1-farming-unstake-max-withdraw-amount-validation-omission-lp-over-withdrawal-2024-11-15|Thala V1 farming unstake_max withdraw-amount validation omission / LP over-withdrawal (2024-11-15)]] — the escrow-release cousin: a farming helper did not literally mint a new token, but it released far more redeemable LP claims than the attacker had ever staked, and downstream liquidity-removal plus swap paths still honored those counterfeit withdrawal rights against real pool reserves.
    • [[../../../incidents/accounting-issuance-claims/solv-protocol-bitcoinreserveoffering-erc3525-onerc721received-double-mint-reserve-drain-2026-03-05|Solv Protocol BitcoinReserveOffering ERC-3525 onERC721Received() double mint / reserve drain (2026-03-05)]] — the callback-issuance cousin: receipt delivery fired before the reserve-offering slot was irreversibly consumed, so one bond deposit minted the same reserve-backed claim twice and downstream redemption rails still paid out against real reserves.

Internal linkages

Control surface