Eleven Finance ElevenNeverSellVault emergencyBurn() Missing Share Burn / Double-Withdrawal Drain (2021-06-22)

  • Name: Eleven Finance ElevenNeverSellVault emergencyBurn() missing share burn / double-withdrawal drain
  • Date: 2021-06-22
  • Protocol: Eleven Finance
  • Chain: BNB Chain
  • Category: duplicate claim / replayable redemption / burn omission / yield-vault accounting failure
  • Loss: about 4.6M was drained across multiple vaults; later reporting said roughly 93.3% was recovered, leaving a net loss around $301.5k
  • Summary: Starting around 2021-06-22 22:58:39 UTC, Eleven Finance’s ElevenNeverSellVault let users exit LP positions through emergencyBurn(), but the function transferred underlying LP tokens out without actually burning the user’s vault shares. That turned one vault position into a replayable redemption right. In the first attack leg, the attacker used a flashswap-assisted BUSD borrow, built an NRV-BUSD LP position, deposited 411,515.29 Pancake LP tokens into vault 0x27DD6E51BF715cFc0e2fe96Af26fC9DED89e4BE8, called emergencyBurn() to pull those same LP tokens back out while still keeping the 11nrvBUSD shares, and then called withdrawAll() to redeem the unchanged share balance a second time. The vault thus paid the same position twice.
  • Trigger / entry point:
    • Eleven exposed an emergencyBurn() path on ElevenNeverSellVault for pulling user funds out of the strategy stack.
    • That branch returned underlying LP tokens but failed to consume the corresponding user shares.
    • The normal withdrawal branch still treated the unchanged share balance as valid redemption authority.
    • Because the vault held real LP-backed value, reusing the same share claim immediately monetized into drainable assets.
  • Root cause:
    • Eleven violated the one-position-one-redemption invariant: after emergencyBurn() finished, the user’s claim on vault assets should have been extinguished or reduced to zero.
    • Instead, the function transferred value first and left the accounting object that authorized later withdrawal untouched.
    • This was not mainly a flash-loan pricing trick. The flash liquidity only made it cheap to build a large temporary LP position; the real bug was branch-specific consumed-state omission.
    • In pattern terms, emergencyBurn() created a cross-branch replay: one exit path paid out, but another exit path still believed the same shares had never been spent.
  • Exploit path:
    1. The attacker funded the operation from address 0xc71e2f581b77de945c8a7a191b0b238c81f11ed6 and used a flashswap against the ApeSwap pair at 0x51e6D27FA57373d8d4C256231241053a70Cb1d93 to borrow 953,869.62 BUSD.
    2. They swapped 340,631.23 BUSD for 474,378.75 NRV.
    3. They added 474,378.75 NRV plus 366,962.02 BUSD into the NRV-BUSD Pancake pool and minted 411,515.29 LP tokens.
    4. They deposited those LP tokens into Eleven’s ElevenNeverSellVault, receiving 411,515.29 vault shares (11nrvBUSD).
    5. They called emergencyBurn(), which returned the underlying LP tokens but did not burn or decrement the user’s vault-share balance.
    6. With the same 11nrvBUSD shares still live, they immediately called withdrawAll() and redeemed a second copy of the same vault position.
    7. They removed liquidity, swapped the recovered NRV back into BUSD, repaid the flash loan plus fee, and kept the surplus.
    8. The attacker repeated the same primitive across at least four ElevenNeverSellVault instances via additional attack contracts, draining multiple vaults before the protocol could halt the damage.
  • What the attacker needed to be true:
    • emergencyBurn() had to transfer or unwrap real vault value without clearing the user’s share entitlement.
    • The ordinary withdrawal path had to trust raw share balance as sufficient redemption authority, with no additional already emergency exited guard.
    • The vaults had to hold enough redeemable LP-backed assets for the duplicate withdrawal to matter economically.
    • Flash liquidity had to be available so the attacker could scale the initial LP deposit quickly and atomically.
  • Technical anatomy worth remembering:
    • This is the yield-vault version of duplicate-claim failure: the replayed object was not a Merkle proof or lock ID, but a share balance that should have been consumed on the first exit path.
    • The crucial bug was asymmetric branch behavior. emergencyBurn() and withdrawAll() were meant to be alternate ways to exit one position, but only one of them updated consumed state.
    • The exploit path was tiny at the contract-call level — effectively depositAll(); emergencyBurn(); withdrawAll(); after preparing the LP position — which is exactly why branch-convergence review matters more than surface-level strategy complexity.
    • The exploit also illustrates why emergency paths deserve especially hostile review. Teams often relax invariants to make exits easier under stress, but that branch still has to preserve the economic identity of redeemed claims.
    • Flash liquidity amplified the attack, but the vault would still have been logically broken without it. The core failure was assets moved out no longer implying shares moved down.
  • Representative transactions and contracts:
    • Main exploit transaction: 0x6450d8f4db09972853e948bee44f2cb54b9df786dace774106cd28820e906789
    • One later exploit transaction cited by public exploit catalogs: 0xeaaa8f4d33b1035a790f0d7c4eb6e38db7d6d3b580e0bbc9ba39a9d6b80dd250
    • Primary attacker address: 0xc71e2f581b77de945c8a7a191b0b238c81f11ed6
    • Vulnerable ElevenNeverSellVault instance from the first exploit leg: 0x27DD6E51BF715cFc0e2fe96Af26fC9DED89e4BE8
    • Flashswap pair used in one public proof of concept: 0x51e6D27FA57373d8d4C256231241053a70Cb1d93
    • NRV-BUSD Pancake LP token used in the first exploit leg: 0x401479091d0F7b8AE437Ee8B054575cd33ea72Bd
  • Response / mitigation:
    • Eleven Finance paused affected vaults after the exploit and investigated the vault logic failure.
    • Public postmortem coverage converged on the missing-share-burn bug in emergencyBurn() as the decisive root cause, with PeckShield describing the attack as a flashswap-assisted drain that hit at least four ElevenNeverSellVault pools.
    • Later exploit-catalog reporting said most funds were recovered, though the incident still remains a strong accounting-path failure exemplar.

Pattern tags

  • duplicate-claim-idempotency-failure
  • issuance-redemption-solvency-asymmetry
  • emergencyBurn() paid underlying LP value without consuming the vault-share entitlement that still authorized withdrawAll()
  • The exploitable state transition was branch-specific: depositAll() minted 11nrvBUSD, emergencyBurn() returned the backing LP, and only the later ordinary withdrawal branch actually burned the shares
  • Flashswap liquidity only scaled the temporary LP position; it did not create the accounting bug that made one share balance redeemable twice
  • The same primitive repeated cleanly across multiple ElevenNeverSellVault instances, which is exactly what you expect when the bug is exit-right consumed state missing on one branch rather than pool-specific pricing distortion

Why the exploit worked

  • PeckShield’s RCA makes the decisive predicate blunt: emergencyBurn() was intended to let the user withdraw funds and burn shares, but after transferring the underlying LP tokens it never actually burned the user’s 11nrvBUSD balance.
  • The exploit therefore did not need to trick a price oracle or skew vault exchange rate math. The attacker only had to create a large enough LP position, deposit it once, and then exercise two exit branches against the same entitlement object.
  • The public proof-of-concept captures the exploit surface almost embarrassingly well: after preparing the LP tokens, the core path is effectively vault.depositAll(); vault.emergencyBurn(); vault.withdrawAll();. That is useful corpus material because it shows how little strategy complexity is required once one branch stops consuming the right that another branch still honors.
  • This is also a strong emergency path is production code case. The bug lived in the escape hatch intended to help users exit stressed positions; the ordinary withdrawal path stayed economically live and honest enough to cash the stale share balance out a second time.
  • The flashswap mattered only because it let the attacker rent a large temporary position atomically. The vault would still have been structurally broken without flash liquidity, because assets transferred out had stopped implying shares extinguished.

Detection and audit pivots

  • Test economic equivalence across all exit verbs for the same position: withdraw, withdrawAll, emergencyBurn, migration helpers, rescue paths, and any router wrapper should all consume the same share entitlement object.
  • Property-test the minimal replay sequence directly: after deposit(x), any one successful exit path should make every sibling exit path worth zero incremental value for that same shares balance.
  • Track post-exit supply invariants, not just asset movement. If a vault returns LP tokens but total shares and user shares do not fall by the redeemed amount, the protocol has already created replayable withdrawal authority.
  • Review emergency branches with the same hostility as ordinary redemptions. Faster or simpler exit logic is not allowed to fork consumed-state semantics from the main withdrawal path.
  • Ask whether flash liquidity is only capital amplification. If the same exploit would still work with slower self-funded deposits, the real issue is usually accounting-path replayability rather than price manipulation.

Failure-prevention checklist

  • Force every exit verb that can realize one vault position — withdraw, withdrawAll, emergencyBurn, migration helpers, and rescue paths — through one shared entitlement-consumption primitive so assets out and shares down cannot diverge by branch.
  • Assert supply conservation on every redemption path. If an exit returns backing LP tokens, the corresponding user share balance and total vault share supply should fall in the same state transition.
  • Property-test the exact replay sequence from the public proof of concept: depositAll(); emergencyBurn(); withdrawAll(); should leave the second exit worth zero additional value.
  • Treat emergency logic as full-production accounting code, not as a lighter operational shortcut. Escape hatches should simplify routing, not fork the consumed-state model.
  • Make sibling exit paths economically equivalent even when they use different helper flows or unwind strategies. Alternate redemptions may differ operationally, but they must not differ on which claim object is consumed.
  • Reconcile outstanding share supply against redeemable backing after every exit branch, including paused-mode, migration-mode, or strategy-rescue paths.
  • Fuzz branch orderings and mixed exit attempts for the same position in one transaction and across repeated calls. Reviewers should expect attackers to exercise normal -> emergency, emergency -> normal, and helper-wrapped variants until some branch forgets to burn.
  • Keep incident monitoring focused on value left the vault while shares stayed live rather than only on price anomalies. Duplicate-withdrawal vault bugs can look economically calm right until reserves are gone.

Audit prompts

  • What exact storage object represents this vault position has already been redeemed, and do all exit verbs consume that same object?
  • If a user exits through emergencyBurn(), what precise state transition makes a later withdrawAll() economically inert for the same share balance?
  • Can any branch transfer LP tokens or underlying assets before user shares and total supply are reduced, and if so what stops replay?
  • Are emergency, migration, or rescue paths reusing the main redemption primitive, or did they fork their own share-burning semantics?
  • Does testing assert branch equivalence at the economic level, not just branch-local correctness? For one position, do withdraw, withdrawAll, and emergencyBurn all reduce claimable value by the same amount?
  • If the vault unwinds strategy positions differently across exit modes, where is the invariant that the same claim cannot survive two different unwind routes?
  • Could a flash loan, flash swap, or temporary self-funded deposit merely amplify a bug that would still exist at smaller scale, and are reviewers explicitly separating amplification from root cause?
  • After any redemption, can a monitor or invariant check detect backing returned but shares unchanged before the next withdrawal branch cashes out again?

Lessons that should already be standard

  • An emergency exit is still an exit. If it pays real value, it has to consume the same entitlement object as the ordinary withdrawal path.
  • Assets transferred out is not enough; for vault systems, the real invariant is assets out implies shares down in the same redemption state machine.
  • Branch-local correctness is a trap. The right review question is whether all sibling paths that honor one position remain economically equivalent after any one of them executes.
  • Flash liquidity often only rents attack size. When the exploit core is deposit -> exit path A -> exit path B, the real bug is replayable redemption authority.
  • Escape-hatch code is exactly where teams are most likely to relax invariants under operational pressure. That is why those branches deserve more hostility, not less.
  • A share balance that survives one successful redemption is already a printing press, even if the first payout path was intended only for emergencies.

Why this incident matters

  • It preserves an important subfamily where a vault does not need an oracle bug or reentrancy window to fail; it only needs one exit path that pays out without consuming the right being redeemed.
  • It is a clean example of how emergency escape hatches can silently become duplicate-withdrawal branches if they are not forced back into the same share-burning invariant as ordinary exits.
  • It also broadens the duplicate-claim corpus beyond batched Merkle claims and lock unlocks into vault-share redemption logic.
  • It is a particularly reusable review case because the exploit path is so compact. When deposit -> emergency exit -> ordinary exit works in one transaction, the system is advertising that its economic rights are defined by branch behavior rather than by one canonical consumed-state object.

Internal linkages

Control surface