Building a secure NFT gaming experience. A Herdsman’s diary

  • Wool pouch: An ERC20-emitting ERC721 token. Users can claim unlocked WOOL from pouches and trade the pouch NFTs on the secondary market.
  • Distribution / risky game: Sheep holders can choose between claiming a WOOL pouch containing a set amount of WOOL and fighting wolves for a larger WOOL payout.

Cheating randomness

Blockchain games often involve random outcomes. For example, in risky game sheep will defeat wolves with a 50% probability. It sounds simple enough but is surprisingly difficult to get right.

Users can revert unwanted outcomes

Users can attempt to revert their actions if they dislike the random result. Imagine a fixed-price mint function where users receive a regular NFT 95% of the time and a valuable, rare NFT 5% of the time. Evil users could write a smart contract that calls the mint function, checks the result and reverts whenever a regular NFT was minted, thus only receiving (and paying for) the rare NFT.

Random numbers can leak in advance

When using an external source of randomness, attackers can wait for the transaction to show up in the mempool and front-run it to take the optimal action.


A cautious herdsman must employ meticulous forethought to anticipate yet-unknown threats.

Pseudo-randomness isn’t random

Another challenge is finding a good source of randomness. For example, assume you want to generate a random value between 0 and 1024. One thing you could do is have commit users to enter the game, wait for a number of blocks, then use rand = block.blockhash(block.number — 1) % 1024 as the random number. On the surface, block hashes provide good entropy. The problem here though is that it would be relatively cheap for a miner to craft a block where rand works out to be a particular number — all they need to do is find a block where blockhash % 1024 results in the desired value.

Wolf Game implementation

Wolf Game uses the Chainlink request & receive data cycle in combination with a state variable that disables user opt-ins at the time the random number is requested. In the default configuration, Chainlink’s VRF coordinator will wait for 10 blocks until fulfilling the randomness request, which is plenty to account for possible reorgs.

RiskyGame ChainLink VRF code. initateRiskGame() sets optInEnabled to false, preventing users from taking further actions until the random value arrives.
Chainlink VRF request & receive data cycle. The random number is received via callback after a 10 blocks.

On the safety of WOOL pouches

WOOL pouches are capable of displaying their own WOOL via dynamically generated SVG. From a security perspective, it was important to consider how trading of the pouches on the secondary market would play out.

Wool pouches emit precious ERC20 tokens linearly over a predefined timespan.
Basic front-running protection: Transfers will revert if WOOL was claimed in the same block.

Integer magic

The Shepherd is determined not to waste a single bit of precious storage. Consequently, Wool Pouch data is neatly packed into a 256 bit struct. However, special care must be taken when doing computations on small types and integers in general.

Pouch storage layout in WoolPouch.sol
function function1() external view returns (uint256) {uint8 x = 128;uint256 y = x * 2;}function function2() external view returns (uint256) {uint8 x = 128;uint256 y = x * 100000;
When in doubt, always cast to uint256.
uint128 unstaked_earnings = (MIGRATION_TIMESTAMP - BARN_PAUSE_TIMESTAMP) / 1 days * 10000 ether;
(((MIGRATION_TIMESTAMP - BARN_PAUSE_TIMESTAMP) / 86400) * 10000) * 1e18)
uint128 unstaked_earnings = (MIGRATION_TIMESTAMP - BARN_PAUSE_TIMESTAMP) * 10000 ether / 1 days;

Final smart contracts and bug bounty

The audited smart contracts were published on Github and added to the bug bounty scope on Dec 4th, followed by a mainnet launch on Dec. 6th. If you spot security issues in the code please participate in the ongoing bug bounty program.

Further reading



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Bernhard Mueller

Bernhard Mueller


Hackers (1995) fan • “Best Research” Pwnie Awardee • Former degen trader • P(G(F)) = ∀y q(y, G(F))