# selfdestruct may cause the funds to be lost

FixedPrice/OpenEdition minters selfdestruct on final sale; later buy() calls to the empty address succeed and lock msg.value forever.

- Payout: Contest reward
- Severity: high
- Program: Escher
- Credited: bin2chen
- Disclosed: 2023-02-22
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-12-escher-findings/issues/296
- Library page: https://bugbountylibrary.com/r/c4%3A2022-12-escher%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:race-window technique:source-review vuln:business-logic

**Target:** Solidity NFT minter contracts (FixedPrice, OpenEdition)
**Root cause:** _end() calls selfdestruct to close the sale; once code is removed, calls to the address no longer revert and payable value is accepted with no code to handle it.
**Steps:**
1. Sale reaches finalId; buyer's buy() triggers _end() and selfdestruct(saleReceiver).
2. A second buyer's buy() tx (already in mempool or UI) executes against the now-empty address.
3. No code runs, no extcodesize check at EVM tx level, tx succeeds and msg.value is stored at the address.
4. Funds are permanently locked with no recovery path.
**How found:** Code review of the sale-ending logic: noticed selfdestruct in _end() and reasoned about what happens to concurrent/pending buy() transactions after code removal, then validated on Goerli via Remix that a second call to a destroyed contract succeeds and retains value.
**Exploitation:** Parallel/front-run purchases of the last NFT IDs: all buy() transactions mined after the final successful buy send ETH to the destroyed contract address, which is unrecoverable; buyers lose funds without receiving NFTs.
**Insight:** When a contract selfdestructs to terminate a sale, pending mempool buy() calls to the dead address still succeed and trap ETH because the EVM does not enforce extcodesize on top-level calls; prefer state flags over selfdestruct for lifecycle termination.
