Why `selfdestruct` Is Not Used in Upgradable Smart Contracts
The selfdestruct
function in Solidity allows a smart contract to be permanently removed from the blockchain, transferring its remaining Ether to a specified address. While this feature can be useful in traditional contracts, it is never used in upgradable smart contracts because it introduces serious security risks and disrupts the upgrade process.
This article explores why selfdestruct
is avoided in upgradable contracts, the technical issues it causes, and alternative approaches for handling contract deactivation.
How Upgradable Contracts Work
Upgradable smart contracts rely on the proxy pattern, where the proxy contract remains deployed while different implementation contracts can be swapped in when upgrades are needed.
If selfdestruct
is used on an implementation contract, it gets removed from the blockchain, but the proxy contract still points to the now-deleted contract, breaking all functionality.
Why selfdestruct
Is a Problem in Upgradable Contracts
1. Proxy Contracts Rely on Persistent Implementation Logic
The whole idea behind upgradable contracts is that users interact with a permanent proxy contract, which delegates function calls to a separate implementation contract. If the implementation contract is destroyed, the proxy is left pointing to an empty address, making it unusable.
Example of a Self-Destructing Implementation Contract
contract Implementation {
function destroy() public {
selfdestruct(payable(msg.sender));
}
}
If someone calls destroy()
, the implementation contract disappears, leaving the proxy pointing to an address with no code, effectively breaking the entire contract system.
2. delegatecall
Cannot Be Used to Destroy the Proxy
In a proxy contract setup, calls to the implementation contract are made using delegatecall
. However, delegatecall
executes the logic in the proxy’s context, meaning if selfdestruct
is used inside the implementation contract, it does not destroy the proxy—only the implementation.
This means:
The proxy contract remains deployed.
The implementation contract disappears.
Future calls to the proxy result in failures because there is no longer any logic to execute.
3. Permanent Loss of Upgradeability
One of the key benefits of upgradable contracts is that they can be updated to fix bugs or add features. However, if selfdestruct
is used on an implementation contract, it removes the logic entirely, making upgrades impossible.
A developer might deploy a new implementation, but the old proxy is still pointing to a deleted contract, requiring a new proxy deployment—which defeats the purpose of an upgradable system.
Alternative: Deactivating Instead of Destroying
Instead of using selfdestruct
, a deactivation function can be implemented. This allows a contract to be disabled without permanently breaking the proxy.
Better Approach: Disabling Instead of Destroying
contract Implementation {
bool public active = true;
modifier onlyActive() {
require(active, "Contract is deactivated");
_;
}
function disableContract() public {
active = false;
}
}
With this method, functions can be disabled without destroying the contract, ensuring that the proxy system remains intact.
Conclusion
The selfdestruct
function is not used in upgradable smart contracts because it breaks the proxy contract by removing the implementation, leading to an unusable system. Instead of destruction, developers should implement deactivation mechanisms that preserve contract integrity while disabling functionality when needed.