---
title: "EIP-6780 — SELFDESTRUCT Restriction"
description: "EIP-6780 — SELFDESTRUCT Restriction mapped to Neo N3."
---
# EIP-6780 — SELFDESTRUCT Restriction
[Back to Protocol-Level EIPs](/standards-mirror/protocol-eips)
<StandardsMirror>
<StandardEntry
id="eip-6780"
title="EIP-6780 — SELFDESTRUCT Restriction"
eip="6780"
status="Final"
neoMapping="ContractManagement.Destroy (explicit)"
category="Lifecycle"
parityLabel="Native"
parityClass="sm-pill-native"
>
<template #spec>
## EIP-6780: Reduced SELFDESTRUCT Functionality
`SELFDESTRUCT` originally:
1. Destroyed the contract's bytecode and storage.
2. Forwarded the contract's ETH balance to a designated recipient.
3. Refunded gas to the caller (incentive to clean up unused contracts).
This created multiple footguns: re-deploying via CREATE2 to the same address with
different code (because storage was wiped), unintended data loss in proxy contracts
when the implementation called `selfdestruct`, MEV opportunities around
"refund the gas" mechanics.
EIP-6780 (Cancun) reduced `SELFDESTRUCT` to:
- **If called in the same transaction as the contract was created**: full original
behavior (destroy + forward funds + refund gas).
- **Otherwise**: only forward the balance. The bytecode and storage stay intact.
In effect, `SELFDESTRUCT` is now a **balance-sweep** opcode for already-deployed
contracts, not a destruction primitive.
### Neo Equivalent
Neo's `ContractManagement.Destroy()` has always been:
- An explicit method call (no opcode).
- Authorized by the contract's witness.
- Removes the contract's storage and manifest.
- Has no automatic fund-forwarding or gas refund.
The "destroy" path is intentionally narrow: contracts must explicitly opt in by
implementing a callable destroy method, witnesses must check, and any token
balances held at the contract address must be transferred out beforehand by
contract logic — there's no implicit sweep.
::: tip Live on Neo TestNet
Both implementations are deployed on Neo N3 TestNet (network magic `894710606`).
| **Solidity** (`neo-solc`) | `0x67a59c179448a3769d5559a691a4c118c7de9930` | [`0x35d83590…980e4e`](https://dora.coz.io/transaction/neo3/testnet/0x35d83590e411100bb14e15567994ba516f101e8c82eaea055d62cd458b980e4e) |
| **Neo C#** (`nccs`) | `0x8acf52e9a1f696965480cd40046c9c3de020f8eb` | [`0x5bf67208…6d1473`](https://dora.coz.io/transaction/neo3/testnet/0x5bf67208dbb2e76f5bf9c1e48314d4b6d96f4eb979bc423d16f6be9a606d1473) |
Checked-in snapshot: Solidity 1 / 2 assertions pass; Neo C# 2 / 2 assertions pass. This is a validation snapshot, not an all-green parity certification; see [TestNet Results](/standards-mirror/deployments/RESULTS) for failure details.
Source pairs: [`docs/standards-mirror/deployments/eip-6780/`](https://github.com/r3e-network/neo-devpack-solidity/tree/main/docs/standards-mirror/deployments/eip-6780).
:::
</template>
<template #solidity>
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract OldStyle {
function destroy(address payable recipient) external {
// Pre-Cancun: this destroyed the bytecode AND swept ETH.
// Post-EIP-6780: only sweeps ETH unless this is a same-tx-deploy contract.
// A surprising change for many existing contracts.
selfdestruct(recipient);
}
}
contract Sweeper {
/// New idiomatic pattern: explicit balance transfer instead of selfdestruct.
function sweep(address payable recipient) external {
recipient.transfer(address(this).balance);
}
}
```
</template>
<template #csharp>
```csharp
using System;
using System.ComponentModel;
using Neo;
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Attributes;
using Neo.SmartContract.Framework.Native;
using Neo.SmartContract.Framework.Services;
namespace R3E.Examples;
[DisplayName("DestroyableContract")]
[ContractPermission("*", "*")]
public class DestroyableContract : SmartContract
{
private static readonly byte[] OwnerKey = { 0xff };
[DisplayName("Destroyed")]
public static event Action<UInt160> OnDestroyed;
/// <summary>
/// NEP-31 destroy. Explicit, witness-gated, removes storage and manifest.
/// No automatic fund forwarding — handle that separately.
/// </summary>
public static void Destroy()
{
var owner = (UInt160)Storage.Get(Storage.CurrentContext, OwnerKey);
if (!Runtime.CheckWitness(owner)) throw new Exception("owner only");
// Sweep any token balances held at this contract before destruction
// (user must list which tokens to sweep — there's no implicit sweep).
// Example for GAS:
var gasBal = (BigInteger)Contract.Call(GAS.Hash, "balanceOf", CallFlags.ReadOnly,
new object[] { Runtime.ExecutingScriptHash });
if (gasBal > 0)
Contract.Call(GAS.Hash, "transfer", CallFlags.All,
new object[] { Runtime.ExecutingScriptHash, owner, gasBal, "destroy-sweep" });
OnDestroyed(Runtime.ExecutingScriptHash);
ContractManagement.Destroy();
}
}
```
### What's Different
| Trigger | Opcode in middle of execution | Explicit method call |
| Auth | Implicit (whoever holds caller authority) | Explicit `Runtime.CheckWitness` check |
| Bytecode removal | Conditional (post-6780) | Always |
| Fund handling | Implicit ETH transfer | Application code transfers out before destroy |
| Gas refund | Was a thing pre-EIP-3529 | Never existed |
| Same-tx redeploy | Possible via CREATE2 | Not applicable — script hash is content-derived |
</template>
</StandardEntry>
</StandardsMirror>