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