What We Learned from Fomo3D — Part 1

Martin Derka
quantstamp
6 min readDec 8, 2018

--

Fomo3D is a Ponzi scheme smart contract on Ethereum address 0xa62142888aba8370742be823c1782d17a0389da1. The scheme runs in rounds and convinces users to invest Ether in the game while hoping for high returns — the last investor in each round gets the jackpot. In order to become an investor, the users purchase so-called “keys’’ that signify their participation in the game. Every purchase of a key prolongs the duration of the round.

The Fomo3D smart contract was published on Ethereum on July 6, 2018, and very quickly became popular. When its balance exceeded 17,000 ETH, the Ethereum community became interested in the impact of Fomo3D on the network, the Ethereum ecosystem, and formulated some predictions about the future of this scheme. The most viable predictions were that Fomo3D will keep accumulating Ether until the moment when the balance becomes too appealing to the large mining pools, and that those will start colluding in the effort to win the game. Another notable prediction was that the game had the potential of destroying the Ethereum mainnet by consuming all (or at least a great majority of) the Ether in the world that will eventually be all transferred to the hands of a single winner. None of the above happened though. The game was won through an elaborate Ethereum mainnet attack on August 22, 2018, yielding the winner 10,469.66 ETH.

There are two notable events that happened during the first round of Fomo3D that are very interesting from the smart contract security perspective. The first relates to a vulnerability that was introduced in the Fomo3D by the development team. In order to encourage participants to purchase keys, the smart contract would with every purchase of a key transfer 1% of the incoming Ether into a pool dedicated to airdrops. With every purchase over 0.1 ETH, it would “randomly” assess whether the purchaser is eligible for an airdrop from this pool. Unfortunately, the “randomness” was not very random. Rather than generating a true random number, the smart contract performed a deterministic computation based on several seeds taken from the current state of the blockchain.

Before we go any further, let us state that there is no such thing as random numbers on Ethereum. The existence of random numbers would contradict the fundamental principles of the network. Ethereum computations are performed by every node locally (while following the definition of the Ethereum Virtual Machine), and every such node locally maintains the state of the network. The global state of Ethereum is maintained through a decentralized consensus on what computations should have been performed and what the inputs for those computations were. If there was an option for the nodes to generate random numbers, there would be no consensus of the global state. Therefore, all computations on Ethereum must be deterministic and randomness a feature cannot exist.

As mentioned, the authors of Fomo3D decided to derive “randomness’’ from the current state of the blockchain by performing computations on block.timestamp, block.difficulty, block.coinbase, block.gaslimit, the timestamp of the block, the block number and msg.sender. It is true that some of these values are hard to predict when a user submits a transaction to the network (because the user does not know in which block the transaction will be mined), but they are all known at the moment when the transaction computations are being carried out. This means that if the user creates a proxy smart contract, such it can first evaluate all these seeds, replicate the deterministic computations that produce the “randomness,” and based on the outcome decide whether it should make an external call and trigger the transaction for real.

We can demonstrate the issue on a game contract (simpler than Fomo3D) shown below. The contract allows two players to enter in a game, each with 1 ether. After this happens, the first player to enter is expected to call a method for rewarding the winner. The winner is selected based on the hash the current block: the first player wins if the block hash is even, otherwise the other player wins. The winner receives the balance held by the smart contract (2 ether).

A wrong implementation of the EvenGame smart contract.

The first player can implement a proxy contract shown below that allows for both signing up as a player, as well as calling the rewardWinner() function. However, before calling the rewardWinner() function, the contract can check the block hash, determine who the winner will be, and whether it is beneficial to trigger the function now, or leave it for a later block. Thus, this contract never loses.

A proxy contract that never loses the EvenGame.

In order to battle the possible exploit, the developers of Fomo3D decided to prohibit purchases that do not come directly from an externally owned account (i.e., not a smart contract).

For this purpose, they used the EVM assembly instruction extcodesize(_address) that returns the size of code associated with an Ethereum address. They assumed that code size of a smart contract is never 0. Unfortunately, this assumption is not valid throughout the entire lifecycle of a

smart contract. Specifically, if extcodesize(_address) is invoked from the constructor of the contract on address _address, it returns 0. This is because the contract is still being constructed and does not exist on the blockchain yet (even though it is already executing some code). Therefore, require(extcodesize(msg.sender) == 0) is not sufficient to guarantee that msg.sender is not a smart contract.

This is everything that one needs to attack the airdrop feature of Fomo3D. By implementing a smart contract that inside of its constructor checks that the seeds yield the desired “random” outcome, one can purchase keys only when airdrops are guaranteed. If the airdrop value exceeds the cost of purchasing the key, the whole purchase results in a positive amount of Ether arriving at the user’s account.

Let us for a moment think about how one would implement such a feature correctly. The authors of Fomo3D attempted to resolve the lack of randomness by prohibiting the interactions with smart contracts, but failed to do so. While there is a way of restricting method from being called by other contracts (this method is require(msg.sender == tx.origin)), we should realize that this is not a way to go in general. If you ever find yourself in a situation where calls by other contracts can hurt and you would like to avoid them in the name of security, it is time to rethink your design. The capability of smart contracts acting on behalf of users is an intended feature of Ethereum. Preventing such interactions limits the usability of your smart contract. For example, since Ethereum does not have native support for multisig accounts, any multisignature

wallet needs to be implemented as a smart contract. By prohibiting calls by external smart contracts, one effectively prohibits calls from multisignature wallets (and all other similar smart-contract based dapps).

But back to correcting Fomo3D. We already know that there is no such thing as random numbers on Ethereum. Fortunately, there is an architectural pattern that one can resort to and that gets fairly close to randomness. In this pattern, we break the interaction with the feature that requires random numbers into two steps. In the first step, the caller (possibly a smart contract!) commits to an action. Then, at some point later in the future, they make another call. This future call will be the source of randomness for the event that the caller already committed to.

So, unless the user can read the future (which in the blockchain language mean having enough computing power to be able to produce proof of work for several blocks in advance), they do not have any means of predicting the value beforehand. Furthermore, the number of blocks that have to be mined between the commitment and resolving the event is variable. The more blocks the developer requires, the more computing power an attacker would need to learn the state of the future state blockchain at the time of the commitment.

A safe implementation of the EvenGame using commitment and future resolution.

Next…

Check back next week as we get into the second lesson that Fomo3D has taught us. And for more content like this, be sure to get on our mailing list and join our Telegram.

--

--

Martin Derka
quantstamp

Senior research engineer at Quantstamp with a Ph.D. from UWaterloo. Enjoys traveling, sports and rock-metal music. www.linkedin.com/in/mderka