Skip to content

Tutorial: verifiable tournament seeding ​

Source: verity-examples/contracts/src/TournamentSeeding.sol.

The organizer registers up to 128 participants and requests a seeding. One random word drives a Fisher–Yates shuffle, and anyone can recompute the bracket from the public random word.

Freeze, then request ​

solidity
function requestSeeding() external onlyOwner returns (uint256 requestId) {
    frozen = true;                                 // no more entrants
    requestId = s_coordinator.requestRandomWords(keyHash, subId, confirmations, 60_000, 1);
}

Store the word, derive in a view ​

The callback stores only the random word, at a fixed cost of ~25k gas whatever the field size:

solidity
function fulfillRandomWords(uint256 requestId, uint256[] memory words) internal override {
    if (requestId != seedingRequestId || seeded) return;
    seeded = true;
    randomWord = words[0];
}

function seeding() external view returns (string[] memory) {
    return shuffle(s_participants, randomWord);   // computed on read
}

Our first version wrote the shuffled list into storage inside the callback. With 128 entrants that needs millions of gas, more than any sensible callback limit. A callback that runs out of gas is final, and the tournament would have no seeding. Keeping the callback tiny avoids that whole class of failure.

The shuffle ​

solidity
for (uint256 i = list.length - 1; i > 0; i--) {
    uint256 j = uint256(keccak256(abi.encode(word, i))) % (i + 1);
    (list[i], list[j]) = (list[j], list[i]);
}

Each step draws from keccak256(word, i), so one 256-bit word covers any number of participants. shuffle is public pure: paste the word from the Seeded event and verify the bracket yourself.

Testnet: PulseChain testnet v4. Unaudited software; do not use on mainnet yet.