Ethernaut Lvl 7 Force Walkthrough — How to selfdestruct and create an Ether blackhole

This is a in-depth series around Zeppelin team’s smart contract security puzzles. We learn key Solidity concepts to solve the puzzles 100% on your own.

Nicole Zhu
Coinmonks
Published in
4 min readAug 23, 2018

--

This levels requires you to send ethers to an empty contract.

What is selfdestruct

selfdestruct is big red button that lets you abandon your smart contract and move all remaining Ethers to another address

selfdestruct(address) is a low-level opcode call, an alias for the former, not-so-greatly-named, suicide() function. Unlike other transactions, selfdestruct() consumes negative gas — so think of it as a garbage collection incentive to clean up void contracts.

When to selfdestruct

Contract owners typically include a selfdestruct option to be called in the following scenarios:

  • To deprecate buggy contracts: When there is a bug or undesired aspect of a smart contract, the creators can then destroy the contract and forward remaining Ethers to a backup address.
  • To clean up used contracts that become obsolete. This is seen as a best practice to free up storage on Ethereum blockchain.

A example of how selfdestruct is often implemented:

function close() public onlyOwner {
//recommended: emit an event as well
selfdestruct(owner);
}
The selfdestruct option makes everything ok when it might not be

Fun fact: selfdestruct is currently 1 of 3 methods for your contract to receive ether

Method 1 — via payable functions: Earlier, we discussed that the fallback function is to intentionally allow your contract to receive Ether from other contracts and external wallets. But if no such payable function exists, your contract still has 2 more indirect ways of receiving funds:

Method 2 — receiving mining reward: contract addresses can be designated as the recipients of mining block rewards.

Method 3 — from a destroyed contract: As discussed, selfdestruct lets you designate a backup address to receive the remaining ethers from the contract you are destroying.

Caution: be careful about forwarding the selfdestructed ethers to any smart contract.

In this level, we forward Ethers to an empty contract with no withdrawal or transfer capabilities. This means we’re effectively dumping test ethers into a blackhole — never to be used again. Never do this on main-net, because it would tie up the universally-limited number of Ethers (and your own money) forever!

Detailed Walkthrough

  1. Notice Force.sol is an empty contract incapable of receiving money through a direct transfer. This leaves us with method 2 and 3 left. Let’s selfdestruct an aribitrary contract and forward the remaining ether to Force.sol!
  2. In Remix IDE or using truffle framework, initiate the following selfdestructing contract:
contract SelfDestructingContract {
}

3. Allow this contract to receive Ether so it can have a balance:

function collect() public payable returns(uint) {
return address(this).balance;
}

At this point, forward your contract some ethers!

4. Allow this contract to self destruct and forward all remaining ethers to Force.sol.

function selfDestroy() public {
address addr = //your Force.sol instance here
selfdestruct(addr);
}

5. Invoke selfDestroy(). Your Force.sol instance has now become a permanent sink for all Ethers!

Key Security Takeaways

  • Never trust your own accounting: Even as an owner of the contract, you do not control your contract’s balance. Never use contract balance as an accounting or auth check.
  • Even if you didn’t implement a selfdestruct(), it is still possible through any delegatecall() vulnerabilities.
  • If you implement a selfdestruct, i) authenticate that the msg.sender = owner and ii) emit an event for external dependencies on this contract and for future reference.

More Levels

Get Best Software Deals Directly In Your Inbox

--

--