Force — Ethernaut Level 7

Joshua Moore
DraftKings Engineering
4 min readNov 14, 2022

The Force problem of the Ethernaut series introduces the concept of how to allow your contract to receive funds, as well as the limitations of what you can and can’t prevent. This article will explain how to allow your contract the choice to receive or not receive native tokens, how the selfdestruct call works, and finally how to solve Ethernaut problem 7 — Force.

Native Tokens vs ERC20 Tokens

Before diving in, it’s important to understand what native tokens are. Native tokens represent a token that is known to a chain, and is used to complete transactions. On Ethereum, the native token is ether, however on Polygon, the native token is matic. Because of this, we’re able to track and trigger events like receive(), and mark functions as payable to be paid in the chain’s native token.

Unlike native tokens, ERC20 tokens are deployed as a smart contract and hold balances in the smart contract’s storage. All transfer events are done through that smart contract, and there is no native functionality to trigger receive() events.

This Ethernaut problem, as well as the rest of the article, focuses on native token transactions.

Allowing Contracts to Receive Funds

When deploying smart contracts, there are three main ways of funding the contract. If you are relying on receiving funds either initially or after the contract has been deployed, it is important to include one or more of these implementations.

Using a Payable Constructor

The first way of initially funding your contract is to use a payable constructor. By marking your constructor as payable, you can send ether (or the EVM chain’s native token) on deployment.

constructor() public payable { ... }

Although this will initially fund your contract, you will not be able to easily receive funds further from this point without implementing either a payable fallback or a payable function.

Implementing a Payable Fallback (receive)

In order to receive funds directly (sending funds directly to the contract address) you must implement a payable receive or fallback function.

receive()

receive() external payable { ... }

The receive function has been implemented since Solidity 0.6.x and is specifically meant to be called when receiving native funds. It can include additional logic and can even revert if certain logic is not met.

fallback()

fallback() external payable { ... }

The fallback function also includes a fallback for receive if it is not implemented. This will allow for direct funds, as well as funds being sent with a function signature that isn’t found within the contract.

You can read more on fallback and receive in our previous article on Fallback and Receive — Ethernaut Level 1.

Include a Payable Function

To receive funds through a public or external function call, simply mark the call as payable. This will allow the EOA or other contracts to call the function with a native token amount.

function sendFunds() public payable{ ... }

This opens the contract up to receiving funds in the form of a native token. Additional business logic can be implemented in any of the methods above.

By not including any of these methods, your smart contract is not able to receive funds from any EOA or smart contract unless that contract transfers funds by self destructing.

Understanding Self Destruct

The selfdestruct(address) function specifically in Solidity is a way to delete a contract’s bytecode from the EVM chain. Self destruction is typically used when cleaning up old contracts that are no longer used, or contracts that have been depreciated.

An important parameter in selfdestruct(address) is the address, which allocates where the balance of the contract being destroyed will be sent to. An example implementation is shown below:

function endOfLife() public onlyOwner {
// sends balance of contract to msg.sender
selfdestruct(msg.sender);
}

Solving Ethernaut Level 7 — Force

The goal of this problem is to make the instance contract have a non-zero balance. Let’s take a look at the instance contract:

Ethernuat Level 7 — Force

Because the source of the contract is empty, there is no payable function to send funds through. Further, if attempting to utilize a fallback or send funds directly to the instance address, the transaction will fail. This indicates there is only one way to force funds to the contract.

In Comes selfdestruct

Because selfdestruct will transfer funds anywhere, and destroy the ability to receive funds, it is possible to force send the balance of a contract to the instance address.

To do this, write and deploy a simple smart contract using Remix IDE. The contract will initially fund the contract using the payable constructor described in the first section, then destroy itself and transfer the balance to the instance address using the selfdestruct(address) call learned in the second section. This will look similar to the following:

Implementation of Force Solution

By deploying the contract with an initial amount of funds, along with the instance address, the contract will both create and destroy itself, while sending the funds to the instance address and allowing completion of level 7 in a single transaction. To deploy with initial funds and constructor arguments through the Remix IDE, you can fill in the value and arguments fields as shown here:

Deploying Contract Solution with Arguments & Value

Your contract will deploy, transfer funds, and self-destruct all within the constructor. An example transaction can be found here. This completes Ethernaut Level 7.

--

--