‘Self-Destruct’ a Function in Smart Contracts

Dorian Szafranski
8 min readJul 17, 2023

--

selfdestruct

The Rundown

In the world of smart contracts, a feature known as the “self-destruct” function serves as an interesting and potentially risky tool. Imagine it like a self-imploding building. When the order is given, the building crumbles in a controlled implosion. Similarly, when a smart contract’s self-destruct function is triggered, it ceases to exist. In layman’s terms, the self-destruct function is the “big red button” of smart contracts — the final kill-switch.

What It Does

The self-destruct function is part of the Solidity programming language, which is used to write smart contracts for the Ethereum blockchain. The self-destruct function does exactly what it sounds like — it “kills” the smart contract, removing it from the blockchain and transferring any remaining ether (Ethereum’s native cryptocurrency) to a designated address.

Why Use It?

// Specifies the version of Solidity
pragma solidity ^0.8.0;

contract SelfDestructible {
// The address of the contract owner
address payable owner;

// Constructor to initialize the owner
constructor() {
owner = payable(msg.sender);
}

// Modifier to limit function access to the contract owner
modifier onlyOwner {
require(msg.sender == owner, "Only owner can call this function.");
_;
}

// Function to deposit ether into the contract
function deposit() public payable {}

// Check the contract's balance
function getBalance() public view returns (uint) {
return address(this).balance;
}

// Self-destruct function
function destroy() public onlyOwner {
selfdestruct(owner); -
}
}

The self-destruct function can serve multiple purposes. It can act as an emergency exit in case of detected vulnerabilities, a planned lifecycle event for temporary contracts, or a cost-saving measure, as Ethereum refunds part of the gas used when a contract is destroyed.

The Risks and How to Exploit Them

The power and finality of the self-destruct function make it a prime target for hackers. The function can be exploited in several ways. For instance, if a hacker gains control over the owner’s account, they could call the self-destruct function and direct the remaining funds to their own account.

Another common way of exploiting this function is through a re-entrancy attack. This occurs when a contract interacts with another one before it has resolved its own state. If the second contract contains malicious code, it can call the original contract back and repeat the process, draining its funds. It’s akin to a pickpocket returning to a victim who hasn’t yet realized they’ve been robbed.

Self-Destruct Immunity

Achieving immunity to the self-destruct function often involves refraining from using it entirely. Just like you wouldn’t install a self-destruction mechanism in a building unless absolutely necessary, it’s often safer not to incorporate the self-destruct function into a smart contract.

multiple owners with multi-signature requirements is the key

In cases where the function is necessary, careful contract architecture can help. This involves having multiple owners with multi-signature requirements, similar to a nuclear launch protocol where multiple keys are needed to authorize a launch. This way, even if a hacker gains access to one owner’s account, they won’t be able to activate the self-destruct function without the other keys.

For simplicity, we will assume that we require signatures from all owners to proceed with the self-destruction.

// Specifies the version of Solidity
pragma solidity ^0.8.0;

contract MultiSigSelfDestructible {
// List of owners and confirmation status
mapping(address => bool) public owners;
mapping(address => bool) public confirmations;

// The address of the receiver after self-destruction
address payable public receiver;

// Total owners count
uint public ownersCount;

constructor(address[] memory _owners, address payable _receiver) {
require(_owners.length > 0, "Owners required");
require(_receiver != address(0), "Valid receiver required");

for (uint i = 0; i < _owners.length; i++) {
owners[_owners[i]] = true;
}

receiver = _receiver;
ownersCount = _owners.length;
}

modifier onlyOwner() {
require(owners[msg.sender] == true, "Not an owner");
_;
}

// Each owner must call this function to confirm self-destruction
function confirmSelfDestruct() public onlyOwner {
confirmations[msg.sender] = true;
}

// The self-destruct function
function destroy() public onlyOwner {
for (uint i = 0; i < ownersCount; i++) {
require(confirmations[msg.sender] == true, "All owners must confirm");
}

selfdestruct(receiver);
}
}

In this contract, owners is a mapping that stores the status of each owner. The onlyOwner modifier ensures that only owners can call the confirmSelfDestruct and destroy functions.

Each owner must call the confirmSelfDestruct function, which records their agreement to destroy the contract.

The destroy function can only be called by an owner and will only succeed if all owners have confirmed their agreement. If these conditions are met, the selfdestruct function is called, and all Ether stored in the contract is sent to the receiver address.

This contract thus provides additional protection against unauthorized self-destruction. However, it’s still crucial to handle this powerful function with great care.

Another immunity measure involves implementing safeguards that require the contract to be paused and all pending transactions to be cleared before the self-destruct function can be activated. This acts as a buffer zone, allowing time to identify and stop any unauthorized attempts at self-destruction.

Alright, let’s extend the previous contract to incorporate a ‘pausable’ feature. We’ll also add a check that all pending transactions have been cleared before allowing self-destruction. Note that the concept of ‘pending transactions’ isn’t naturally applicable to Ethereum smart contracts because transactions are either processed successfully or not at all. However, you could have a system of ‘pending withdrawals’ which I’ll illustrate here.

// Specifies the version of Solidity
pragma solidity ^0.8.0;

contract PausableSelfDestructible {
// The address of the contract owner
address payable owner;

// Contract paused state
bool isPaused;

// Pending withdrawals
mapping(address => uint) pendingWithdrawals;

// Constructor to initialize the owner
constructor() {
owner = payable(msg.sender);
}

// Modifier to limit function access to the contract owner
modifier onlyOwner {
require(msg.sender == owner, "Only owner can call this function.");
_;
}

// Modifier to check if contract is paused
modifier whenNotPaused {
require(!isPaused, "Contract is paused.");
_;
}

// Function to deposit ether into the contract
function deposit() public payable whenNotPaused {}

// Function to request a withdrawal
function requestWithdrawal(uint amount) public whenNotPaused {
require(amount <= address(this).balance, "Not enough balance.");
pendingWithdrawals[msg.sender] += amount;
}

// Function to process a withdrawal
function processWithdrawal() public whenNotPaused {
uint amount = pendingWithdrawals[msg.sender];
require(amount > 0, "No withdrawal pending.");

pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}

// Pause the contract
function pause() public onlyOwner {
isPaused = true;
}

// Resume the contract
function resume() public onlyOwner {
isPaused = false;
}

// Check the contract's balance
function getBalance() public view returns (uint) {
return address(this).balance;
}

// Self-destruct function
function destroy() public onlyOwner {
// Check that there are no pending withdrawals
for (uint i = 0; i < pendingWithdrawals.length; i++) {
require(pendingWithdrawals[i] == 0, "Pending withdrawals exist.");
}

// Check that contract is paused
require(isPaused, "Contract is not paused.");

selfdestruct(owner);
}
}

In this updated version of the contract, we have a boolean isPaused to represent the contract’s paused state, and a mapping to hold ‘pending withdrawals’.

The deposit and processWithdrawal functions can only be executed when the contract is not paused, due to the whenNotPaused modifier.

We’ve introduced two new functions: pause and resume that allow the owner to pause or resume the contract.

Finally, the destroy function checks that there are no pending withdrawals and that the contract is in a paused state before calling selfdestruct.

The Detailed Walkthrough

Let’s dive deeper into the mechanics. The self-destruct function is called using the command selfdestruct(recipient_address). When triggered, it removes the contract's bytecode from the Ethereum state, making it impossible to interact with it further. The contract essentially becomes a ghost, with no presence or functionality on the blockchain.

This operation, while highly impactful, is surprisingly cheap in terms of computational cost, or ‘gas’. Ethereum incentivizes the freeing up of storage by offering a ‘gas refund’ for such operations, making it a somewhat economical, if drastic, operation.

When to Use, When Not to Use

When should you use the self-destruct function? Generally, it should be used sparingly, if at all. It’s a useful tool in some specific circumstances, like temporary contracts meant to exist only for a short period, or for cleaning up after testing and development on the Ethereum network.

However, the self-destruct function is a ticking time bomb in the wrong hands. The majority of modern smart contracts are designed to be upgradable or pausable in case of detected vulnerabilities, which offers a safer and more controlled way to manage contract lifecycles.

Defense Measures

Prevention is the best defense against any exploitation. Regular audits, thorough testing, and careful design can prevent most of the vulnerabilities associated with the self-destruct function. It’s like constructing a building with high-quality materials and a robust architectural design; it significantly reduces the risk of any collapse.

In conclusion, the self-destruct function in smart contracts is a powerful tool that carries potential risks. It should be handled with the same care as any dangerous equipment — wisely, sparingly, and with a good understanding of its potential for destruction.

That’s all bros!

https://www.buymeacoffee.com/travilabs.offical

# little self-promotion

Hi, I’m Dorian and I run my small software house. My offer for you.

— Online stores and websites
— Web and mobile applications
— Computer graphics
— Process automation
— Bots, API and infrastructure
— Branding
— Artificial intelligence (AI) implementation
— WordPress and PrestaShop plugins, etc.
— Smart contracts / launching your own cryptocurrency / NFT.
— Implementation of WEB3 / Blockchain technology in business.

Do you want to create your own crypto project basing it on some application or just want to create your cryptocurrency or NFT?
Do you want to implement WEB3 and blockchain technologies into your business?

— Website: www.travilabs.com
Contact us:
Facebook: https://www.facebook.com/profile.php?id=100091601081883
Email: office.travilabs@gmail.com

--

--