Smart Contract
PrizeWinnerTracker
A.6a3c3dad7074bca5.PrizeWinnerTracker
1/*
2PrizeWinnerTracker - Pluggable Winner History Tracking
3
4A separate contract that can be optionally plugged into PrizeVaultModular
5to track lottery winners without coupling the core logic.
6
7Design Principles:
8- Interface-based (can swap implementations)
9- Ring buffer (fixed size, efficient memory)
10- Read-only public access
11- Optional (PrizeVault works without it)
12*/
13
14access(all) contract PrizeWinnerTracker {
15
16 // ========================================
17 // Storage Paths
18 // ========================================
19
20 access(all) let TrackerStoragePath: StoragePath
21 access(all) let TrackerPublicPath: PublicPath
22
23 // ========================================
24 // Events
25 // ========================================
26
27 access(all) event WinnerRecorded(
28 poolID: UInt64,
29 round: UInt64,
30 winnerReceiverID: UInt64,
31 amount: UFix64,
32 nftIDs: [UInt64],
33 timestamp: UFix64,
34 blockHeight: UInt64
35 )
36
37 // ========================================
38 // Winner Record Structure
39 // ========================================
40
41 access(all) struct WinnerRecord {
42 access(all) let poolID: UInt64
43 access(all) let round: UInt64
44 access(all) let winnerReceiverID: UInt64
45 access(all) let amount: UFix64
46 access(all) let nftIDs: [UInt64]
47 access(all) let timestamp: UFix64
48 access(all) let blockHeight: UInt64
49
50 init(
51 poolID: UInt64,
52 round: UInt64,
53 winnerReceiverID: UInt64,
54 amount: UFix64,
55 nftIDs: [UInt64],
56 timestamp: UFix64,
57 blockHeight: UInt64
58 ) {
59 self.poolID = poolID
60 self.round = round
61 self.winnerReceiverID = winnerReceiverID
62 self.amount = amount
63 self.nftIDs = nftIDs
64 self.timestamp = timestamp
65 self.blockHeight = blockHeight
66 }
67 }
68
69 // ========================================
70 // Winner Tracker Interface
71 // ========================================
72
73 /// Interface that any winner tracking implementation must follow
74 access(all) resource interface WinnerTrackerPublic {
75 access(all) fun recordWinner(
76 poolID: UInt64,
77 round: UInt64,
78 winnerReceiverID: UInt64,
79 amount: UFix64,
80 nftIDs: [UInt64]
81 )
82
83 access(all) fun getRecentWinners(poolID: UInt64, limit: Int): [WinnerRecord]
84 access(all) fun getAllRecentWinners(limit: Int): [WinnerRecord]
85 access(all) fun getWinnerCount(poolID: UInt64): Int
86 access(all) fun getTotalWinnerCount(): Int
87 access(all) fun getNFTWinnersCount(poolID: UInt64): Int
88 access(all) fun getAllNFTWinnersCount(): Int
89 }
90
91 // ========================================
92 // Ring Buffer Implementation
93 // ========================================
94
95 /// Efficient ring buffer that stores last N winners per pool
96 access(all) resource RingBufferTracker: WinnerTrackerPublic {
97 access(self) let maxSize: Int
98 access(self) let winnersByPool: {UInt64: [WinnerRecord]}
99 access(self) var allWinners: [WinnerRecord] // Global recent winners
100 access(self) var nextIndex: Int // For ring buffer rotation
101
102 init(maxSize: Int) {
103 pre {
104 maxSize > 0 && maxSize <= 1000: "Max size must be between 1 and 1000"
105 }
106 self.maxSize = maxSize
107 self.winnersByPool = {}
108 self.allWinners = []
109 self.nextIndex = 0
110 }
111
112 access(all) fun recordWinner(
113 poolID: UInt64,
114 round: UInt64,
115 winnerReceiverID: UInt64,
116 amount: UFix64,
117 nftIDs: [UInt64]
118 ) {
119 let record = WinnerRecord(
120 poolID: poolID,
121 round: round,
122 winnerReceiverID: winnerReceiverID,
123 amount: amount,
124 nftIDs: nftIDs,
125 timestamp: getCurrentBlock().timestamp,
126 blockHeight: getCurrentBlock().height
127 )
128
129 // Add to pool-specific list
130 if self.winnersByPool[poolID] == nil {
131 self.winnersByPool[poolID] = []
132 }
133
134 var poolWinners = self.winnersByPool[poolID]!
135 poolWinners.append(record)
136
137 // Keep only last maxSize winners per pool
138 if poolWinners.length > self.maxSize {
139 let _ = poolWinners.remove(at: 0)
140 }
141
142 self.winnersByPool[poolID] = poolWinners
143
144 // Add to global list (ring buffer)
145 if self.allWinners.length < self.maxSize {
146 // Still filling up
147 self.allWinners.append(record)
148 } else {
149 // Replace oldest (ring buffer)
150 self.allWinners[self.nextIndex] = record
151 self.nextIndex = (self.nextIndex + 1) % self.maxSize
152 }
153
154 emit WinnerRecorded(
155 poolID: poolID,
156 round: round,
157 winnerReceiverID: winnerReceiverID,
158 amount: amount,
159 nftIDs: nftIDs,
160 timestamp: record.timestamp,
161 blockHeight: record.blockHeight
162 )
163 }
164
165 access(all) fun getRecentWinners(poolID: UInt64, limit: Int): [WinnerRecord] {
166 let winners = self.winnersByPool[poolID] ?? []
167
168 if limit <= 0 || limit >= winners.length {
169 return winners
170 }
171
172 // Return last 'limit' winners
173 let startIndex = winners.length - limit
174 var result: [WinnerRecord] = []
175
176 var i = startIndex
177 while i < winners.length {
178 result.append(winners[i])
179 i = i + 1
180 }
181
182 return result
183 }
184
185 access(all) fun getAllRecentWinners(limit: Int): [WinnerRecord] {
186 if limit <= 0 || limit >= self.allWinners.length {
187 return self.allWinners
188 }
189
190 // Return last 'limit' winners
191 let startIndex = self.allWinners.length - limit
192 var result: [WinnerRecord] = []
193
194 var i = startIndex
195 while i < self.allWinners.length {
196 result.append(self.allWinners[i])
197 i = i + 1
198 }
199
200 return result
201 }
202
203 access(all) fun getWinnerCount(poolID: UInt64): Int {
204 return (self.winnersByPool[poolID] ?? []).length
205 }
206
207 access(all) fun getTotalWinnerCount(): Int {
208 return self.allWinners.length
209 }
210
211 access(all) fun getNFTWinnersCount(poolID: UInt64): Int {
212 let winners = self.winnersByPool[poolID] ?? []
213 var count = 0
214 for winner in winners {
215 if winner.nftIDs.length > 0 {
216 count = count + 1
217 }
218 }
219 return count
220 }
221
222 access(all) fun getAllNFTWinnersCount(): Int {
223 var count = 0
224 for winner in self.allWinners {
225 if winner.nftIDs.length > 0 {
226 count = count + 1
227 }
228 }
229 return count
230 }
231 }
232
233 // ========================================
234 // Factory Functions
235 // ========================================
236
237 access(all) fun createRingBufferTracker(maxSize: Int): @RingBufferTracker {
238 return <- create RingBufferTracker(maxSize: maxSize)
239 }
240
241 // ========================================
242 // Helper Functions
243 // ========================================
244
245 /// Get tracker from an account (if it exists)
246 access(all) fun borrowTracker(account: Address): &{WinnerTrackerPublic}? {
247 return getAccount(account)
248 .capabilities.borrow<&{WinnerTrackerPublic}>(
249 PrizeWinnerTracker.TrackerPublicPath
250 )
251 }
252
253 // ========================================
254 // Initialization
255 // ========================================
256
257 init() {
258 self.TrackerStoragePath = /storage/PrizeWinnerTracker
259 self.TrackerPublicPath = /public/PrizeWinnerTracker
260 }
261}
262
263