Smart Contract
Questing
A.35f9e7ac86111b22.Questing
1import QuestReward from 0x35f9e7ac86111b22
2import NonFungibleToken from 0x1d7e57aa55817448
3import RewardAlgorithm from 0x35f9e7ac86111b22
4
5access(all)
6contract Questing{
7
8 // -----------------------------------------------------------------------
9 // Events
10 // -----------------------------------------------------------------------
11 access(all)
12 event ContractInitialized()
13
14 access(all)
15 event QuestStarted(
16 questID: UInt64,
17 typeIdentifier: String,
18 questingResourceID: UInt64,
19 quester: Address
20 )
21
22 access(all)
23 event QuestEnded(
24 questID: UInt64,
25 typeIdentifier: String,
26 questingResourceID: UInt64,
27 quester: Address?
28 )
29
30 access(all)
31 event RewardAdded(
32 questID: UInt64,
33 typeIdentifier: String,
34 questingResourceID: UInt64,
35 rewardID: UInt64,
36 rewardTemplateID: UInt32,
37 rewardTemplate: QuestReward.RewardTemplate
38 )
39
40 access(all)
41 event RewardBurned(
42 questID: UInt64,
43 typeIdentifier: String,
44 questingResourceID: UInt64,
45 rewardID: UInt64,
46 rewardTemplateID: UInt32,
47 minterID: UInt64
48 )
49
50 access(all)
51 event RewardMoved(
52 questID: UInt64,
53 typeIdentifier: String,
54 fromQuestingResourceID: UInt64,
55 toQuestingResourceID: UInt64,
56 rewardID: UInt64,
57 rewardTemplateID: UInt32,
58 minterID: UInt64
59 )
60
61 access(all)
62 event RewardPerSecondChanged(questID: UInt64, typeIdentifier: String, rewardPerSecond: UFix64)
63
64 access(all)
65 event RewardRevealed(
66 questID: UInt64,
67 typeIdentifier: String,
68 questingResourceID: UInt64,
69 questRewardID: UInt64,
70 rewardTemplateID: UInt32
71 )
72
73 access(all)
74 event AdjustedQuestingStartDateUpdated(
75 questID: UInt64,
76 typeIdentifier: String,
77 questingResourceID: UInt64,
78 newAdjustedQuestingStartDate: UFix64
79 )
80
81 access(all)
82 event QuestCreated(questID: UInt64, type: Type, questCreator: Address)
83
84 access(all)
85 event QuestDeposited(
86 questID: UInt64,
87 type: Type,
88 questCreator: Address,
89 questReceiver: Address?
90 )
91
92 access(all)
93 event QuestWithdrawn(
94 questID: UInt64,
95 type: Type,
96 questCreator: Address,
97 questProvider: Address?
98 )
99
100 access(all)
101 event QuestDestroyed(questID: UInt64, type: Type, questCreator: Address)
102
103 access(all)
104 event MinterDeposited(minterID: UInt64, name: String, receiverAddress: Address?)
105
106 access(all)
107 event MinterWithdrawn(minterID: UInt64, name: String, providerAddress: Address?)
108
109 // -----------------------------------------------------------------------
110 // Paths
111 // -----------------------------------------------------------------------
112 access(all)
113 let QuestManagerStoragePath: StoragePath
114
115 access(all)
116 let QuestManagerPublicPath: PublicPath
117
118 access(all)
119 let QuestManagerPrivatePath: PrivatePath
120
121 access(all)
122 let QuesterStoragePath: StoragePath
123
124 // -----------------------------------------------------------------------
125 // Contract Fields
126 // -----------------------------------------------------------------------
127 access(all)
128 var totalSupply: UInt64
129
130 // -----------------------------------------------------------------------
131 // Future Contract Extensions
132 // -----------------------------------------------------------------------
133 access(self)
134 var metadata:{ String: AnyStruct}
135
136 access(self)
137 var resources: @{String: AnyResource}
138
139 access(all)
140 resource interface Public{
141 access(all)
142 let id: UInt64
143
144 access(all)
145 let type: Type
146
147 access(all)
148 let questCreator: Address
149
150 access(all)
151 var rewardPerSecond: UFix64
152
153 access(contract)
154 fun quest(questingResource: @AnyResource, address: Address): @AnyResource
155
156 access(contract)
157 fun unquest(questingResource: @AnyResource, address: Address): @AnyResource
158
159 access(contract)
160 fun revealQuestReward(questingResource: @AnyResource, questRewardID: UInt64): @AnyResource
161
162 access(all)
163 fun getQuesters(): [Address]
164
165 access(all)
166 fun getAllQuestingStartDates():{ UInt64: UFix64}
167
168 access(all)
169 fun getQuestingStartDate(questingResourceID: UInt64): UFix64?
170
171 access(all)
172 fun getAllAdjustedQuestingStartDates():{ UInt64: UFix64}
173
174 access(all)
175 fun getAdjustedQuestingStartDate(questingResourceID: UInt64): UFix64?
176
177 access(all)
178 fun getQuestingResourceIDs(): [UInt64]
179
180 access(all)
181 fun borrowRewardCollection(questingResourceID: UInt64): &QuestReward.Collection?
182 }
183
184 access(all)
185 resource Quest: Public{
186 access(all)
187 let id: UInt64
188
189 access(all)
190 let type: Type
191
192 access(all)
193 let questCreator: Address
194
195 access(self)
196 var questers: [Address]
197
198 access(self)
199 var questingStartDates:{ UInt64: UFix64}
200
201 access(self)
202 var adjustedQuestingStartDates:{ UInt64: UFix64}
203
204 /*
205 Questing Rewards
206 */
207
208 access(all)
209 var rewardPerSecond: UFix64
210
211 access(self)
212 var rewards: @{UInt64: QuestReward.Collection}
213
214 /*
215 Future extensions
216 */
217
218 access(self)
219 var metadata:{ String: AnyStruct}
220
221 access(self)
222 var resources: @{String: AnyResource}
223
224 init(type: Type, questCreator: Address){
225 self.id = self.uuid
226 self.type = type
227 self.questCreator = questCreator
228 self.questers = []
229 self.questingStartDates ={}
230 self.adjustedQuestingStartDates ={}
231 self.rewardPerSecond = 604800.0
232 self.rewards <-{}
233 self.metadata ={}
234 self.resources <-{}
235 Questing.totalSupply = Questing.totalSupply + 1
236 emit QuestCreated(questID: self.id, type: type, questCreator: questCreator)
237 }
238
239 // access(contract) to make sure user verifies with their actual address using the quest function from the quester resource
240 access(contract)
241 fun quest(questingResource: @AnyResource, address: Address): @AnyResource{
242 pre{
243 questingResource.getType() == self.type:
244 "Cannot quest: questingResource type does not match type required by quest"
245 }
246 var uuid: UInt64? = nil
247 var container: @{UInt64: AnyResource} <-{}
248 container[0] <-! questingResource
249 if container[0]?.isInstance(Type<@{NonFungibleToken.NFT}>()) == true{
250 let ref = &container[0] as &AnyResource?
251 let _resource = (ref as! &{NonFungibleToken.NFT}?)!
252 uuid = _resource.uuid
253 }
254
255 // ensure we always have a UUID by this point
256 assert(uuid != nil, message: "UUID should not be nil")
257
258 // check if already questing
259 assert(!self.questingStartDates.keys.contains(uuid!), message: "Cannot quest: questingResource is already questing")
260
261 // add quester to the list of questers
262 if !self.questers.contains(address){
263 self.questers.append(address)
264 }
265
266 // add timers
267 self.questingStartDates[uuid!] = getCurrentBlock().timestamp
268 self.adjustedQuestingStartDates[uuid!] = getCurrentBlock().timestamp
269 emit QuestStarted(questID: self.id, typeIdentifier: self.type.identifier, questingResourceID: uuid!, quester: address)
270 let returnResource <- container.remove(key: 0)!
271 destroy container
272 return <-returnResource
273 }
274
275 access(contract)
276 fun unquest(questingResource: @AnyResource, address: Address): @AnyResource{
277 pre{
278 questingResource.getType() == self.type:
279 "Cannot unquest: questingResource type does not match type required by quest"
280 }
281 var uuid: UInt64? = nil
282 var container: @{UInt64: AnyResource} <-{}
283 container[0] <-! questingResource
284 if container[0]?.isInstance(Type<@{NonFungibleToken.NFT}>()) == true{
285 let ref = &container[0] as &AnyResource?
286 let _resource = (ref as! &{NonFungibleToken.NFT}?)!
287 uuid = _resource.uuid
288 }
289
290 // ensure we always have a UUID by this point
291 assert(uuid != nil, message: "UUID should not be nil")
292
293 // check if questingResource is questing
294 assert(self.questingStartDates.keys.contains(uuid!), message: "Cannot unquest: questingResource is not currently questing")
295 self.unquestResource(questingResourceID: uuid!)
296 let returnResource <- container.remove(key: 0)!
297 destroy container
298 return <-returnResource
299 }
300
301 access(contract)
302 fun revealQuestReward(questingResource: @AnyResource, questRewardID: UInt64): @AnyResource{
303 var uuid: UInt64? = nil
304 var container: @{UInt64: AnyResource} <-{}
305 container[0] <-! questingResource
306 if container[0]?.isInstance(Type<@{NonFungibleToken.NFT}>()) == true{
307 let ref = &container[0] as &AnyResource?
308 let _resource = (ref as! &{NonFungibleToken.NFT}?)!
309 uuid = _resource.uuid
310 }
311
312 // ensure we always have a UUID by this point
313 assert(uuid != nil, message: "UUID should not be nil")
314 self.revealReward(questingResourceID: uuid!, questRewardID: questRewardID)
315 let returnResource <- container.remove(key: 0)!
316 destroy container
317 return <-returnResource
318 }
319
320 /*
321 Public functions
322 */
323
324 access(all)
325 fun getQuesters(): [Address]{
326 return self.questers
327 }
328
329 access(all)
330 fun getAllQuestingStartDates():{ UInt64: UFix64}{
331 return self.questingStartDates
332 }
333
334 access(all)
335 fun getQuestingStartDate(questingResourceID: UInt64): UFix64?{
336 return self.questingStartDates[questingResourceID]
337 }
338
339 access(all)
340 fun getAllAdjustedQuestingStartDates():{ UInt64: UFix64}{
341 return self.adjustedQuestingStartDates
342 }
343
344 access(all)
345 fun getAdjustedQuestingStartDate(questingResourceID: UInt64): UFix64?{
346 return self.adjustedQuestingStartDates[questingResourceID]
347 }
348
349 access(all)
350 fun getQuestingResourceIDs(): [UInt64]{
351 return self.rewards.keys
352 }
353
354 access(all)
355 fun borrowRewardCollection(questingResourceID: UInt64): &QuestReward.Collection?{
356 return &self.rewards[questingResourceID] as &QuestReward.Collection?
357 }
358
359 /*
360 QuestManager functions
361 */
362
363 access(all)
364 fun unquestResource(questingResourceID: UInt64){
365 // remove timers
366 self.questingStartDates.remove(key: questingResourceID)
367 self.adjustedQuestingStartDates.remove(key: questingResourceID)
368 emit QuestEnded(questID: self.id, typeIdentifier: self.type.identifier, questingResourceID: questingResourceID, quester: nil)
369 }
370
371 access(all)
372 fun addReward(questingResourceID: UInt64, minter: &QuestReward.Minter, rewardAlgo: &{RewardAlgorithm.Algorithm}, rewardMapping:{ Int: UInt32}){
373 //check if resource is questing
374 if let adjustedQuestingStartDate = self.adjustedQuestingStartDates[questingResourceID]{
375 let timeQuested = getCurrentBlock().timestamp - adjustedQuestingStartDate
376
377 //check if resource is eligible for reward
378 if timeQuested >= self.rewardPerSecond{
379 let rewardTemplateID = rewardMapping[rewardAlgo.randomAlgorithm()] ?? panic("RewardMapping does not contain a reward for the random algorithm")
380 var newReward <- minter.mintReward(rewardTemplateID: rewardTemplateID)
381 let rewardID = newReward.id
382 let toRef: &QuestReward.Collection? = &self.rewards[questingResourceID] as &QuestReward.Collection?
383 if toRef == nil{
384 let newCollection <- QuestReward.createEmptyCollection(nftType: Type<@QuestReward.Collection>())
385 newCollection.deposit(token: <-newReward)
386 self.rewards[questingResourceID] <-! newCollection as! @QuestReward.Collection
387 } else{
388 (toRef!).deposit(token: <-newReward)
389 }
390 self.updateAdjustedQuestingStartDate(questingResourceID: questingResourceID, rewardPerSecond: self.rewardPerSecond)
391 emit RewardAdded(questID: self.id, typeIdentifier: self.type.identifier, questingResourceID: questingResourceID, rewardID: rewardID, rewardTemplateID: rewardTemplateID, rewardTemplate: minter.getRewardTemplate(id: rewardTemplateID)!)
392 }
393 }
394 }
395
396 access(all)
397 fun burnReward(questingResourceID: UInt64, rewardID: UInt64){
398 let collectionRef = &self.rewards[questingResourceID] as auth(NonFungibleToken.Withdraw) &QuestReward.Collection?
399 assert(collectionRef != nil, message: "Cannot burn reward: questingResource does not have any rewards")
400 let reward <- (collectionRef!).withdraw(withdrawID: rewardID) as! @QuestReward.NFT
401 emit RewardBurned(questID: self.id, typeIdentifier: self.type.identifier, questingResourceID: questingResourceID, rewardID: rewardID, rewardTemplateID: reward.rewardTemplateID, minterID: reward.minterID)
402 destroy reward
403 }
404
405 access(all)
406 fun moveReward(fromID: UInt64, toID: UInt64, rewardID: UInt64){
407 let fromRef = &self.rewards[fromID] as auth(NonFungibleToken.Withdraw) &QuestReward.Collection?
408 assert(fromRef != nil, message: "Cannot move reward: fromID does not have any rewards")
409 let toRef: &QuestReward.Collection? = &self.rewards[toID]
410 assert(toRef != nil, message: "Cannot move reward: toID does not have any rewards")
411 let reward <- (fromRef!).withdraw(withdrawID: rewardID) as! @QuestReward.NFT
412 emit RewardMoved(questID: self.id, typeIdentifier: self.type.identifier, fromQuestingResourceID: fromID, toQuestingResourceID: toID, rewardID: rewardID, rewardTemplateID: reward.rewardTemplateID, minterID: reward.minterID)
413 (toRef!).deposit(token: <-reward)
414 }
415
416 access(all)
417 fun changeRewardPerSecond(seconds: UFix64){
418 self.rewardPerSecond = seconds
419 emit RewardPerSecondChanged(questID: self.id, typeIdentifier: self.type.identifier, rewardPerSecond: seconds)
420 }
421
422 access(all)
423 fun revealReward(questingResourceID: UInt64, questRewardID: UInt64){
424 let collectionRef = &self.rewards[questingResourceID] as &QuestReward.Collection?
425 assert(collectionRef != nil, message: "Cannot reveal reward: questingResource does not have any rewards")
426 let rewardRef = (collectionRef!).borrowEntireQuestReward(id: questRewardID)!
427 rewardRef.reveal()
428 emit RewardRevealed(questID: self.id, typeIdentifier: self.type.identifier, questingResourceID: questingResourceID, questRewardID: questRewardID, rewardTemplateID: rewardRef.rewardTemplateID)
429 }
430
431 access(contract)
432 fun updateAdjustedQuestingStartDate(questingResourceID: UInt64, rewardPerSecond: UFix64){
433 if self.adjustedQuestingStartDates[questingResourceID] != nil{
434 self.adjustedQuestingStartDates[questingResourceID] = self.adjustedQuestingStartDates[questingResourceID]! + rewardPerSecond
435 emit AdjustedQuestingStartDateUpdated(questID: self.id, typeIdentifier: self.type.identifier, questingResourceID: questingResourceID, newAdjustedQuestingStartDate: self.adjustedQuestingStartDates[questingResourceID]!)
436 }
437 }
438 }
439
440 access(all)
441 resource interface MinterReceiver{
442 access(all)
443 fun depositMinter(minter: @QuestReward.Minter)
444 }
445
446 access(all)
447 resource interface QuestReceiver{
448 access(all)
449 fun depositQuest(quest: @Quest)
450 }
451
452 access(all)
453 resource interface QuestManagerPublic{
454 access(all)
455 fun getIDs(): [UInt64]
456
457 access(all)
458 fun getMinterIDs(): [UInt64]
459
460 access(all)
461 fun borrowQuest(id: UInt64): &Quest?{
462 post{
463 result == nil || result?.id == id:
464 "Cannot borrow Quest reference: The ID of the returned reference is incorrect"
465 }
466 }
467
468 access(all)
469 fun borrowMinter(id: UInt64): &QuestReward.Minter?{
470 post{
471 result == nil || result?.id == id:
472 "Cannot borrow Minter reference: The ID of the returned reference is incorrect"
473 }
474 }
475 }
476
477 access(all)
478 resource QuestManager: QuestManagerPublic, QuestReceiver, MinterReceiver{
479 access(self)
480 var quests: @{UInt64: Quest}
481
482 access(self)
483 var minters: @{UInt64: QuestReward.Minter}
484
485 init(){
486 self.quests <-{}
487 self.minters <-{}
488 }
489
490 access(all)
491 fun getIDs(): [UInt64]{
492 return self.quests.keys
493 }
494
495 access(all)
496 fun getMinterIDs(): [UInt64]{
497 return self.minters.keys
498 }
499
500 access(all)
501 fun createQuest(type: Type){
502 let quest <- create Quest(type: type, questCreator: (self.owner!).address)
503 let id = quest.id
504 self.quests[id] <-! quest
505 }
506
507 access(all)
508 fun depositQuest(quest: @Quest){
509 emit QuestDeposited(questID: quest.id, type: quest.type, questCreator: quest.questCreator, questReceiver: self.owner?.address)
510 self.quests[quest.id] <-! quest
511 }
512
513 access(all)
514 fun withdrawQuest(id: UInt64): @Quest{
515 let quest <- self.quests.remove(key: id) ?? panic("Quest does not exist")
516 emit QuestWithdrawn(questID: id, type: quest.type, questCreator: quest.questCreator, questProvider: self.owner?.address)
517 return <-quest
518 }
519
520 access(all)
521 fun destroyQuest(id: UInt64){
522 let quest <- self.quests.remove(key: id) ?? panic("Quest does not exist")
523 emit QuestDestroyed(questID: id, type: quest.type, questCreator: quest.questCreator)
524 destroy quest
525 }
526
527 access(all)
528 fun borrowQuest(id: UInt64): &Questing.Quest?{
529 return &self.quests[id] as &Questing.Quest?
530 }
531
532 access(all)
533 fun borrowEntireQuest(id: UInt64): &Questing.Quest?{
534 return &self.quests[id] as &Questing.Quest?
535 }
536
537 access(all)
538 fun depositMinter(minter: @QuestReward.Minter){
539 emit MinterDeposited(minterID: minter.id, name: minter.name, receiverAddress: self.owner?.address)
540 self.minters[minter.id] <-! minter
541 }
542
543 access(all)
544 fun withdrawMinter(id: UInt64): @QuestReward.Minter{
545 let minter <- self.minters.remove(key: id) ?? panic("Minter does not exist")
546 emit MinterWithdrawn(minterID: id, name: minter.name, providerAddress: self.owner?.address)
547 return <-minter
548 }
549
550 access(all)
551 fun borrowMinter(id: UInt64): &QuestReward.Minter?{
552 return &self.minters[id] as &QuestReward.Minter?
553 }
554
555 access(all)
556 fun borrowEntireMinter(id: UInt64): &QuestReward.Minter?{
557 return &self.minters[id] as &QuestReward.Minter?
558 }
559 }
560
561 access(all)
562 resource Quester{
563 access(all)
564 fun quest(
565 questManager: Address,
566 questID: UInt64,
567 questingResource: @AnyResource
568 ): @AnyResource{
569 let questRef = Questing.getQuest(questManager: questManager, id: questID)
570 assert(questRef != nil, message: "Quest reference should not be nil")
571 return <-(questRef!).quest(
572 questingResource: <-questingResource,
573 address: (self.owner!).address
574 )
575 }
576
577 access(all)
578 fun unquest(
579 questManager: Address,
580 questID: UInt64,
581 questingResource: @AnyResource
582 ): @AnyResource{
583 let questRef = Questing.getQuest(questManager: questManager, id: questID)
584 assert(questRef != nil, message: "Quest reference should not be nil")
585 return <-(questRef!).unquest(
586 questingResource: <-questingResource,
587 address: (self.owner!).address
588 )
589 }
590
591 access(all)
592 fun revealReward(
593 questManager: Address,
594 questID: UInt64,
595 questingResource: @AnyResource,
596 questRewardID: UInt64
597 ): @AnyResource{
598 let questRef = Questing.getQuest(questManager: questManager, id: questID)
599 assert(questRef != nil, message: "Quest reference should not be nil")
600 return <-(questRef!).revealQuestReward(
601 questingResource: <-questingResource,
602 questRewardID: questRewardID
603 )
604 }
605 }
606
607 access(all)
608 fun getQuest(questManager: Address, id: UInt64): &Quest?{
609 if let questManagerRef =
610 getAccount(questManager).capabilities.get<&QuestManager>(
611 Questing.QuestManagerPublicPath
612 ).borrow(){
613 return questManagerRef.borrowQuest(id: id)
614 } else{
615 return nil
616 }
617 }
618
619 access(all)
620 fun createQuestManager(): @QuestManager{
621 return <-create QuestManager()
622 }
623
624 access(all)
625 fun createQuester(): @Quester{
626 return <-create Quester()
627 }
628
629 init(){
630 self.QuestManagerStoragePath = /storage/WonderlandQuestManager_2
631 self.QuestManagerPublicPath = /public/WonderlandQuestManager_2
632 self.QuestManagerPrivatePath = /private/WonderlandQuestManager_2
633 self.QuesterStoragePath = /storage/WonderlandQuester_2
634 self.totalSupply = 0
635 self.metadata ={}
636 self.resources <-{}
637 emit ContractInitialized()
638 }
639}
640