Smart Contract

randomizeBreak

A.8d75e1dff5f8af66.randomizeBreak

Valid From

136,360,560

Deployed

5d ago
Feb 23, 2026, 01:04:04 AM UTC

Dependents

5 imports
1// TeamAssignment.cdc
2// Smart contract for randomly assigning NBA Top Shot usernames to NBA teams
3// Uses Flow's commit-reveal pattern with RandomConsumer VRF for provably fair randomness
4// Based on Flow team's recommended approach using RandomConsumer contract
5
6import RandomConsumer from 0x45caec600164c9e6
7import Burner from 0xf233dcee88fe0abe
8
9access(all) contract randomizeBreak {
10    
11    // ============ Events (Matching Flow's Pattern) ============
12    
13    // Event emitted when a commitment is made (lock phase)
14    // Note: RandomnessSourced event is automatically emitted by RandomConsumer.requestRandomness()
15    access(all) event CommitmentMade(commitmentId: UInt64, lockBlock: UInt64, usernames: [String], teams: [String], combos: [String])
16    
17    // Event emitted when an assignment is made (matches Flow's ParticipantAssigned pattern)
18    access(all) event ParticipantAssigned(username: String, team: String, assignmentIndex: UInt64, receiptID: String)
19    
20    // Event emitted when all assignments are complete (matches Flow's completion pattern)
21    // Note: RandomnessFulfilled event is automatically emitted by RandomConsumer.fulfillRandomRequest()
22    access(all) event TeamAssignmentComplete(
23        commitmentId: UInt64,
24        lockBlock: UInt64,
25        revealBlock: UInt64,
26        receiptID: String,
27        verificationNote: String,
28        assignments: {UInt64: Assignment}
29    )
30    
31    // ============ Data Structures ============
32    
33    // Assignment structure
34    access(all) struct Assignment {
35        access(all) let username: String
36        access(all) let team: String
37        access(all) let assignmentIndex: UInt64
38        access(all) let timestamp: UFix64
39        
40        init(username: String, team: String, assignmentIndex: UInt64, timestamp: UFix64) {
41            self.username = username
42            self.team = team
43            self.assignmentIndex = assignmentIndex
44            self.timestamp = timestamp
45        }
46    }
47    
48    // Receipt resource - returned from commit, submitted to reveal
49    // Conforms to RandomConsumer.RequestWrapper for VRF integration
50    access(all) resource Receipt {
51        access(all) let usernames: [String]
52        access(all) let teams: [String]
53        access(all) let combos: [String]
54        access(all) let lockBlock: UInt64  // Store block height when request is created
55        access(all) var request: @RandomConsumer.Request?
56        
57        init(
58            usernames: [String],
59            teams: [String],
60            combos: [String],
61            request: @RandomConsumer.Request
62        ) {
63            self.usernames = usernames
64            self.teams = teams
65            self.combos = combos
66            self.lockBlock = request.block  // Store block height before moving request
67            self.request <- request
68        }
69        
70        // Required by RequestWrapper interface
71        access(all) fun popRequest(): @RandomConsumer.Request? {
72            let req <- self.request <- nil
73            return <-req
74        }
75        
76        // Get the block height from the request
77        access(all) fun getRequestBlock(): UInt64? {
78            return self.lockBlock
79        }
80    }
81    
82    // ============ Contract State ============
83    
84    // Storage for all completed assignments
85    access(all) var assignments: {UInt64: Assignment}
86    
87    // Consumer resource for VRF randomness
88    access(self) let consumer: @RandomConsumer.Consumer
89    
90    // Counter for commitment IDs (for receiptID generation)
91    access(all) var nextCommitmentId: UInt64
92    
93    // ============ Contract Initialization ============
94    
95    init() {
96        self.assignments = {}
97        self.consumer <- RandomConsumer.createConsumer()
98        self.nextCommitmentId = 0
99    }
100    
101    // ============ Commit Phase ============
102    
103    // Lock in the assignment - returns a Receipt for later reveal
104    // The randomness is requested but NOT revealed yet
105    // RandomConsumer.requestRandomness() automatically emits RandomnessSourced event
106    access(all) fun commitAssignment(
107        usernames: [String],
108        teams: [String],
109        combos: [String]
110    ): @Receipt {
111        pre {
112            usernames.length > 0: "Must provide at least one username"
113            (teams.length + combos.length) == usernames.length: "Number of single teams plus combos must equal number of usernames"
114        }
115        
116        // Request randomness from the VRF
117        // This automatically emits RandomnessSourced event with block, randomSource, and requestUUID
118        let request <- self.consumer.requestRandomness()
119        let lockBlock = request.block
120        
121        // Generate commitment ID for tracking
122        let commitmentId = self.nextCommitmentId
123        self.nextCommitmentId = commitmentId + 1
124        
125        // Create the receipt
126        let receipt <- create Receipt(
127            usernames: usernames,
128            teams: teams,
129            combos: combos,
130            request: <-request
131        )
132        
133        // Emit our commitment event
134        emit CommitmentMade(
135            commitmentId: commitmentId,
136            lockBlock: lockBlock,
137            usernames: usernames,
138            teams: teams,
139            combos: combos
140        )
141        
142        return <- receipt
143    }
144    
145    // ============ Reveal Phase ============
146    
147    // Reveal the assignments using the committed randomness
148    // This consumes the receipt and returns the final assignments
149    // RandomConsumer.fulfillRandomRequest() automatically emits RandomnessFulfilled event
150    access(all) fun revealAndAssign(receipt: @Receipt): {UInt64: Assignment} {
151        // Get values from receipt before we move/destroy it
152        let lockBlock = receipt.getRequestBlock()!
153        let revealBlock = getCurrentBlock().height
154        let currentTime = getCurrentBlock().timestamp
155        let receiptUUID = receipt.uuid
156        
157        // Generate receipt ID for events (using receipt's UUID)
158        let receiptID = receiptUUID.toString()
159        
160        // Get the usernames and team options from receipt
161        var remainingUsernames = receipt.usernames
162        
163        // Combine single teams and combos
164        var allAssignments: [String] = []
165        var i = 0
166        while i < receipt.teams.length {
167            allAssignments.append(receipt.teams[i])
168            i = i + 1
169        }
170        i = 0
171        while i < receipt.combos.length {
172            allAssignments.append(receipt.combos[i])
173            i = i + 1
174        }
175        
176        var result: {UInt64: Assignment} = {}
177        var index: UInt64 = 0
178        
179        // Pop the randomness request from the receipt and get the committed random seed
180        let request <- receipt.popRequest()
181        
182        // Ensure we have a request
183        if request == nil {
184            panic("Receipt does not contain a valid request")
185        }
186        
187        // Fulfill the committed randomness to get the seed value
188        // This automatically emits RandomnessFulfilled event with randomResult and requestUUID
189        // This uses the randomness from the LOCK block, not the reveal block
190        var seedValue = self.consumer.fulfillRandomRequest(<-request!)
191        
192        // Perform the random assignments using the committed seed
193        // Use XOR shift algorithm to derive multiple random values (PRG)
194        while remainingUsernames.length > 0 {
195            // XOR shift algorithm (no multiplication, no overflow)
196            seedValue = seedValue ^ (seedValue << 13)
197            seedValue = seedValue ^ (seedValue >> 7)
198            seedValue = seedValue ^ (seedValue << 17)
199            let usernameIndex = seedValue % UInt64(remainingUsernames.length)
200            
201            // Another round for assignment index
202            seedValue = seedValue ^ (seedValue << 13)
203            seedValue = seedValue ^ (seedValue >> 7)
204            seedValue = seedValue ^ (seedValue << 17)
205            let assignmentIndex = seedValue % UInt64(allAssignments.length)
206            
207            // Get selected username and team
208            let selectedUsername = remainingUsernames[Int(usernameIndex)]
209            let selectedAssignment = allAssignments[Int(assignmentIndex)]
210            
211            // Create assignment record
212            let assignment = Assignment(
213                username: selectedUsername,
214                team: selectedAssignment,
215                assignmentIndex: index,
216                timestamp: currentTime
217            )
218            
219            // Store assignment
220            result[index] = assignment
221            self.assignments[index] = assignment
222            
223            // Emit individual assignment event (for real-time UI updates)
224            emit ParticipantAssigned(
225                username: selectedUsername,
226                team: selectedAssignment,
227                assignmentIndex: index,
228                receiptID: receiptID
229            )
230            
231            // Remove assigned username and team from pools
232            remainingUsernames = self.removeElement(arr: remainingUsernames, index: Int(usernameIndex))
233            allAssignments = self.removeElement(arr: allAssignments, index: Int(assignmentIndex))
234            
235            index = index + 1
236        }
237        
238        // Generate commitment ID for the completion event (we'll use receipt UUID as base)
239        let commitmentId = receiptUUID % 1000000
240        
241        // Destroy the receipt (it's been used)
242        Burner.burn(<-receipt)
243        
244        // Create verification note
245        let verificationNote = "This assignment used Flow's Verifiable Random Function (VRF). The participants and teams were locked at block ".concat(lockBlock.toString()).concat(" and revealed at block ").concat(revealBlock.toString()).concat(". The random seed was determined by the blockchain AFTER the lock, making it impossible for anyone to predict or manipulate the results.")
246        
247        // Emit the main completion event (shareable proof of fairness)
248        emit TeamAssignmentComplete(
249            commitmentId: commitmentId,
250            lockBlock: lockBlock,
251            revealBlock: revealBlock,
252            receiptID: receiptID,
253            verificationNote: verificationNote,
254            assignments: result
255        )
256        
257        return result
258    }
259    
260    // ============ Helper Functions ============
261    
262    // Remove element from array at index
263    access(all) fun removeElement(arr: [String], index: Int): [String] {
264        var result: [String] = []
265        var i = 0
266        while i < arr.length {
267            if i != index {
268                result.append(arr[i])
269            }
270            i = i + 1
271        }
272        return result
273    }
274    
275    // ============ View Functions ============
276    
277    // Get all completed assignments
278    access(all) fun getAllAssignments(): {UInt64: Assignment} {
279        return self.assignments
280    }
281    
282    // Get assignment by index
283    access(all) fun getAssignment(index: UInt64): Assignment? {
284        return self.assignments[index]
285    }
286}
287