Photo by Trym Nilsen / Unsplash

Fomo3d is a popular Ethereum war of attrition game. Game mechanics work as follows:

The gambling app Fomo3D was designed to let players buy keys from a contract and their money goes into a pot. The game is played in rounds and at the beginning of each round, a time counter is initiated which starts counting back from 24 hours. Each key purchase extended the time counter by 30 seconds, and the game ended once the time counter hits 0. The game will reward the last player that purchased a “key”. The winner (i.e., the last player) gets the majority of the pot and the rest is distributed to others. The way the pot is distributed depends on the team that the winner belongs to.

Smart Contract Stuffing: Fomo3D

At the peak of the first round, it has more than 20k ETH in the prize pool. Despite people predicting the first round lasting forever, it has ended on Aug 22nd. The attacker managed to secure 10'469 ETH or around $3'000'000 dollars at the time. Given his expense was on the level of $10'000 dollars which gives approximately 30'000 % ROI for the entire operation.

How did it happened?

Did people run out of money to bet? Quite the opposite. It was a calculated move to grab the prize.

Block stuffing is a type of attack in blockchains where an attacker submits transactions that deliberately fill up the block’s gas limit and stall other transactions. To ensure the inclusion of their transactions by miners, the attacker can choose to pay higher transaction fees. By controlling the amount of gas spent by their transactions, the attacker can influence the number of transactions that get to be included in the block.

The Anatomy of a Block Stuffing Attack

The attacker carried out the successful block stuffing attack on Fomo3d essentially blocking access for all other players to the game. It took him 17 blocks to claim the victory. From block 6191896 to block 6191909. Long 175 seconds.

He used a block stuffing contract to fill all the required blocks. Source code for a contract is not available so we can only guess how it works. Decompiled bytecode has a few assert instructions which lead me to think he used assert instruction to burn gas.

Surprisingly I haven't found any practical examples of Fomo3d attack so I've decided to write an ultra-simple example myself.

Fomo3d contract models only one aspect of a game. It has winner field and corresponding method to claim it. It also has gameover field to indicate whenever the game is running.

BlockPacker contract is used to execute an attack. It accepts Fomo3d contract address as a constructor parameter and uses stuffBlock method to execute an actual attack.

stuffBlock function first checks if the game is running because we don't want to spend any gas if the game is over. I suspect that the actual attack contract has far more conditions, like something related to a block number and it is probably cancelable. After checking that condition are right we are ready to burn gas!

assert(winner != fomo3d.winner()) will burn all the gas available for the transaction as long as we are winning the game. There is still the risk here that transaction claiming the winning key will be mined the last in the block and the burned gas will be wasted. That is why it is critical to occupy an entire block.

You can play with the example yourself at Remix editor. It takes only a couple of minutes to make it work. Once you set yourself as a winner and run stuffBlock function it will consume an entire gas limit.

All your blocks belong to us

As you can see my transaction consumed 8'000'000 gas. The attacker used transactions with smaller and different limits.

Example of block stuffing transaction

As you can see the transaction has a gas limit of 4'200'000 but other transactions had limits as low as 200'000 gas.

In fact, it doesn't matter will you stuff a block with a lot of small transactions or a few big ones as long as it will be full. Miners certainly have their ways to maximize profit so I am pretty sure the attacker spent some time figuring out the optimal strategy to get the highest chance to stuff a block.

It also makes sense instead of mindlessly burning gas to mint gas tokens which will further improve the efficiency of an attack.

Why it is important?

As we can see on a practical 3 million dollar example that block stuffing attack is capable to turn a fair game into one side feast where an attacker takes it all.

How to avoid it?

  • Use intervals which make an attack unprofitable
  • Prevent an attacking smart contract from accessing the game so an attacker can't be certain to burn gas or not.

References: