TournamentBracket (Tamper-proof Bracket Resolution)
VRF-seeded bracket with autonomous round advancement — players cannot stall rounds when losing, and no one sees bracket seeding before it's committed.
Overview
TournamentBracket manages a single-elimination tournament for a fixed player count (4, 8, or 16). Players register and pay into a prize pool. Once registration is full, an admin starts the tournament — the bracket is seeded by VRF on the first AutoLoop tick. Subsequent ticks resolve one round each: VRF picks winners for all active matches, the prize (minus 3% protocol fee) pays the champion when one player remains.
| Property | Value |
|---|---|
| Base contract | AutoLoopVRFCompatible |
| VRF usage | Bracket seeding + per-match winner selection |
| Gas per tick | ~80k–200k (scales with active matches per round) |
| Source | autoloop/src/agents/TournamentBracket.sol |
| Tests | 35 passing (unit + fuzz) |
Why AutoLoop Is Structurally Required
Bracket timing and seeding are both attack surfaces. Without AutoLoop:
- Stall-when-losing: Any participant who controls the trigger can delay round advancement indefinitely when their bracket position is unfavorable. They wait for opponents to rage-quit or until the prize pool economics change.
- Seeding front-run: If the operator seeds the bracket manually (or the seeding transaction is visible in the mempool), a well-connected player can snipe favorable placement — e.g., position themselves away from the strongest opponent in round 1.
AutoLoop enforces that round advancement fires at roundInterval regardless of participant action. The first tick uses VRF to seed the bracket — the worker cannot see placement before it commits. Each subsequent tick resolves all matches in the current round via VRF.
The pattern appears in any on-chain tournament: esports ladders, NFT battle leagues, poker tournaments.
Mechanics
- register() payable: adds
msg.senderto players list, addsmsg.valueto prizePool. Requiresphase == Registration && players.length < maxPlayers. - startTournament() (admin): moves phase to Active once
players.length == maxPlayers. First AutoLoop tick seeds the bracket. - withdrawIfNoStart(): refunds registrants if admin never calls
startTournament(). - shouldProgressLoop():
phase == Active && (bracket needs seeding || roundInterval elapsed) - progressLoop(progressWithData): first tick →
_seedBracket(randomness)shuffles registered players into bracket slots. Subsequent ticks →_resolveRound(randomness)pairs active slots, VRF picks winner per match, loser is eliminated. When one player remains:_crownChampion()— 3% fee to protocol, remainder to winner,phase = Complete. - getMatchHistory(): returns all
MatchResultstructs (winner, loser, round, matchIndex, seed).
Revenue Model
- 3% protocol fee on prize pool at tournament conclusion
- AutoLoop gas fee on each tick (seeding tick + one tick per round)
Deploy
forge create src/agents/TournamentBracket.sol:TournamentBracket \
--constructor-args 3600 8 \
--rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcastArgs:
roundInterval(seconds between rounds) — 3600 = 1 hour per roundmaxPlayers— must be 4, 8, or 16
After deploy, register with AutoLoop and ensure VRF keys are registered. Players call register() with ETH entry fee; admin calls startTournament() when full.
Dashboard
View TournamentBracket on the AutoLoop Dashboard.