Smart Contract
MatchContract
A.a10dde51240c1ec7.MatchContract
1import Crypto
2
3pub contract MatchContract {
4
5 pub let AdminStoragePath: StoragePath
6
7 pub let waitingIndices: [UInt32]
8 pub let matchedIndices: [UInt32]
9 pub let finalizeIndices: [UInt32]
10
11 priv var registerActive: Bool
12 priv var matchActive: Bool
13
14 // latest not used yet index
15 priv var nextIndex: UInt32
16
17 // if using pub let other can still modify by such transaction
18 // execute {
19 // MatchContract.indexAddressMap[0]?.remove(key: MatchContract.MatchRole.host)
20 // }
21 access(account) let addressGroupMap: { String: { MatchStatus: [UInt32] } }
22 access(account) let indexAddressMap: { UInt32: { MatchRole: Address } }
23
24 // Events
25 pub event registered(
26 host: Address,
27 index: UInt32
28 )
29
30 pub event matched(
31 host: Address,
32 challenger: Address,
33 index: UInt32
34 )
35
36 pub resource Admin {
37 pub fun setActivateRegistration(_ active: Bool) {
38 MatchContract.registerActive = active
39 }
40
41 pub fun setActivateMatching(_ active: Bool) {
42 MatchContract.matchActive = active
43 }
44 }
45
46 pub init() {
47 self.registerActive = false
48 self.matchActive = false
49 self.nextIndex = 0
50 self.addressGroupMap = {}
51 self.indexAddressMap = {}
52 self.waitingIndices = []
53 self.matchedIndices = []
54 self.finalizeIndices = []
55 self.AdminStoragePath = /storage/matchAdminV1
56 let admin <- create Admin()
57 self.account.save(<- admin, to: self.AdminStoragePath)
58 }
59
60 // Script
61
62 // Return oldest waiting index as well as first index of waiting group of specific address.
63 pub fun getFirstWaitingIndex(hostAddress: Address): UInt32? {
64 let key = hostAddress.toString().toLower()
65 let matchGroups = self.addressGroupMap[key] ?? {}
66 let waitingGroup = matchGroups[MatchStatus.waiting] ?? []
67 if waitingGroup.length > 0 {
68 return waitingGroup[0]
69 } else {
70 return nil
71 }
72 }
73
74 // Return oldest waiting index as well as first index of waitingIndices.
75 pub fun getRandomWaitingIndex(): UInt32? {
76 if self.waitingIndices.length > 0 {
77 var iterationIndex = 0
78 for waitingIndex in self.waitingIndices {
79 assert(self.indexAddressMap.keys.contains(waitingIndex), message: "IndexAddressMap should contain index ".concat(waitingIndex.toString()))
80 if let addressGroups = self.indexAddressMap[waitingIndex] {
81 if addressGroups[MatchRole.challenger] == nil {
82 return waitingIndex
83 } else {
84 continue
85 }
86 }
87 }
88 return nil
89 } else {
90 return nil
91 }
92 }
93
94 pub fun getNextIndex(): UInt32 {
95 return self.nextIndex
96 }
97
98 pub fun getWaiting(by address: Address): [UInt32] {
99 let key = address.toString().toLower()
100 let addressGroup = self.addressGroupMap[key] ?? {}
101 return addressGroup[MatchStatus.waiting] ?? []
102 }
103
104 pub fun getMatched(by address: Address): [UInt32] {
105 let key = address.toString().toLower()
106 let addressGroup = self.addressGroupMap[key] ?? {}
107 return addressGroup[MatchStatus.matched] ?? []
108 }
109
110 pub fun getFinished(by address: Address): [UInt32] {
111 let key = address.toString().toLower()
112 let addressGroup = self.addressGroupMap[key] ?? {}
113 return addressGroup[MatchStatus.finished] ?? []
114 }
115
116 pub fun getHostAddress(by index: UInt32): Address? {
117 let roleAddressMap = self.indexAddressMap[index] ?? {}
118 return roleAddressMap[MatchRole.host]
119 }
120
121 pub fun getChallengerAddress(by index: UInt32): Address? {
122 let roleAddressMap = self.indexAddressMap[index] ?? {}
123 return roleAddressMap[MatchRole.challenger]
124 }
125
126 // Transaction
127
128 // Register a waiting match.
129 pub fun register(host: Address): UInt32 {
130 pre {
131 self.registerActive: "Registration is not active."
132 }
133 var currentIndex = self.nextIndex
134
135 let key = host.toString().toLower()
136
137 let matchGroups = self.addressGroupMap[key] ?? {}
138 let waitingGroup: [UInt32] = matchGroups[MatchStatus.waiting] ?? []
139
140 waitingGroup.append(currentIndex)
141 matchGroups[MatchStatus.waiting] = waitingGroup
142 self.addressGroupMap[key] = matchGroups
143
144 self.indexAddressMap[currentIndex] = { MatchRole.host: host }
145 self.waitingIndices.append(currentIndex)
146
147 if currentIndex == UInt32.max {
148 // Indices is using out.
149 self.registerActive = false
150 } else {
151 self.nextIndex = currentIndex + 1
152 }
153
154 emit registered(
155 host: host,
156 index: currentIndex
157 )
158
159 return currentIndex
160 }
161
162 // Must match with specific index in case host register slightly before.
163 access(account) fun match(
164 index: UInt32,
165 challengerAddress: Address
166 ): Address? {
167 pre {
168 self.matchActive: "Matching is not active."
169 self.indexAddressMap.keys.contains(index): "Index not found in indexAddressMap"
170 }
171 let roleAddressMap = self.indexAddressMap[index] ?? {}
172 assert(roleAddressMap[MatchRole.host] != nil, message: "Host not found for this index.")
173 assert(roleAddressMap[MatchRole.challenger] == nil, message: "Challenger already exist.")
174 roleAddressMap[MatchRole.challenger] = challengerAddress
175 self.indexAddressMap[index] = roleAddressMap
176
177 let hostAddress = roleAddressMap[MatchRole.host]!
178 let hostKey = hostAddress.toString().toLower()
179 let addressGroup = self.addressGroupMap[hostKey] ?? {}
180 let waitingGroup: [UInt32] = addressGroup[MatchStatus.waiting] ?? []
181 assert(waitingGroup.length > 0, message: hostAddress.toString().concat("'s waiting group length should over 0"))
182
183 if let firstIndex: Int = waitingGroup.firstIndex(of: index) {
184 let matchIndex = waitingGroup.remove(at: firstIndex)
185 addressGroup[MatchStatus.waiting] = waitingGroup
186 assert(matchIndex == index, message: "Match index not equal.")
187 let matchedGroup: [UInt32] = addressGroup[MatchStatus.matched] ?? []
188 matchedGroup.append(matchIndex)
189 addressGroup[MatchStatus.matched] = matchedGroup
190 self.addressGroupMap[hostKey] = addressGroup
191
192 let challengerAddress = challengerAddress
193 let challengerKey = challengerAddress.toString().toLower()
194 let challengerAddressGroup = self.addressGroupMap[challengerKey] ?? {}
195 let indices = challengerAddressGroup[MatchStatus.matched] ?? []
196 indices.append(index)
197 challengerAddressGroup[MatchStatus.matched] = indices
198 self.addressGroupMap[challengerKey] = challengerAddressGroup
199
200 assert(self.waitingIndices.contains(matchIndex), message: "WaitingIndices should include ".concat(matchIndex.toString()).concat(" before matched."))
201 assert(self.matchedIndices.contains(matchIndex) == false, message: "MatchedIndices should not include ".concat(matchIndex.toString()).concat(" before matched."))
202 if let waitingIndex = self.waitingIndices.firstIndex(of: matchIndex) {
203 let matchedIndex = self.waitingIndices.remove(at: waitingIndex)
204 self.matchedIndices.append(matchedIndex)
205 assert(self.waitingIndices.contains(matchIndex) == false, message: "WaitingIndices should not include ".concat(matchIndex.toString()).concat(" after matched."))
206 assert(self.matchedIndices.contains(matchIndex), message: "MatchedIndices should include ".concat(matchIndex.toString()).concat(" after matched."))
207
208 emit matched(
209 host: hostAddress,
210 challenger: challengerAddress,
211 index: index
212 )
213
214 return hostAddress
215 } else {
216 panic("MatchIndex ".concat(matchIndex.toString()).concat(" should be found in waitingIndices"))
217 }
218 } else {
219 panic(hostAddress.toString().concat(" not contain index: ").concat(index.toString()))
220 }
221 return nil
222 }
223
224 access(account) fun finish(index: UInt32) {
225 pre {
226 self.finalizeIndices.contains(index) == false: "Index already finished."
227 self.matchedIndices.firstIndex(of: index) != nil: "Index not exist in matchedIndices."
228 }
229 post {
230 self.finalizeIndices.contains(index): "Finish failed."
231 }
232
233 let indexOfMatched = self.matchedIndices.firstIndex(of: index)!
234 let finishedIndex = self.matchedIndices.remove(at: indexOfMatched)
235 assert(finishedIndex == index, message: "Finish failed.")
236 self.finalizeIndices.append(index)
237
238 let roleAddressMap = self.indexAddressMap[index] ?? {}
239 assert(roleAddressMap[MatchRole.host] != nil, message: "Host not found for this index.")
240 assert(roleAddressMap[MatchRole.challenger] != nil, message: "Challenger already exist.")
241 let hostAddress = roleAddressMap[MatchRole.host]!
242 let challengerAddress = roleAddressMap[MatchRole.challenger]!
243
244 let hostKey = hostAddress.toString().toLower()
245 let challengerKey = challengerAddress.toString().toLower()
246 self.moveMatchedToFinished(for: index, key: hostKey)
247 self.moveMatchedToFinished(for: index, key: challengerKey)
248 }
249
250 priv fun moveMatchedToFinished(for index: UInt32, key: String) {
251 assert(self.addressGroupMap.keys.contains(key), message: "Address key not found for this index.")
252
253 let addressGroup = self.addressGroupMap[key] ?? {}
254 let matchedGroup: [UInt32] = addressGroup[MatchStatus.matched] ?? []
255 assert(matchedGroup.contains(index), message: "Index not found in matchedGroup.")
256 let indexOfMatchedGroup = matchedGroup.firstIndex(of: index)!
257 let removedIndex = matchedGroup.remove(at: indexOfMatchedGroup)
258 assert(removedIndex == index, message: "Finish failed.")
259 addressGroup[MatchStatus.matched] = matchedGroup
260
261 let finishedGroup: [UInt32] = addressGroup[MatchStatus.finished] ?? []
262 finishedGroup.append(index)
263 addressGroup[MatchStatus.finished] = finishedGroup
264
265 self.addressGroupMap[key] = addressGroup
266 }
267
268 pub enum MatchStatus: UInt8 {
269 pub case waiting
270 pub case matched
271 pub case finished
272 }
273
274 pub enum MatchRole: UInt8 {
275 pub case host
276 pub case challenger
277 }
278
279}