Smart Contract
FixesAssetMeta
A.d2abb5dbf5e08666.FixesAssetMeta
1/**
2
3> Author: Fixes Lab <https://github.com/fixes-world/>
4
5# FixesFungibleTokenGenes
6
7This is a sub-feature contract for Fixes Asset, which implements a way to generate on-chain genes.
8It is used by all Fixes' Asset.
9
10*/
11import FungibleToken from 0xf233dcee88fe0abe
12import StringUtils from 0xa340dc0a4ec828ab
13import FixesTraits from 0xd2abb5dbf5e08666
14
15/// The Fixes Asset Genes contract
16///
17access(all) contract FixesAssetMeta {
18
19 // ----------------- DNA and Gene -----------------
20
21 /// The gene quality level
22 ///
23 access(all) enum GeneQuality: UInt8 {
24 // Quality scopes 0~4
25 access(all) case Nascent
26 access(all) case Basic
27 access(all) case Enhanced
28 access(all) case Augmented
29 access(all) case Empowered
30 // Quality scopes 5~9
31 access(all) case Breakthrough
32 access(all) case Advanced
33 access(all) case Potent
34 access(all) case Exemplary
35 access(all) case Evolution
36 // Quality scopes 10~15
37 access(all) case Mystic
38 access(all) case Arcane
39 access(all) case Miraculous
40 access(all) case Celestial
41 access(all) case Cosmic
42 access(all) case Eternal
43 }
44
45 /// Get the quality level up threshold
46 /// @param from The quality level
47 access(all)
48 view fun getQualityLevelUpThreshold(_ from: GeneQuality): UInt64 {
49 // The quality up threshold, using a 0~15 scale
50 if from.rawValue < GeneQuality.Breakthrough.rawValue {
51 // 0~4 Basic linear: Threshold = QualityLevel * 100 + 100 -> 100, 200, 300, 400, 500
52 return UInt64(from.rawValue) * 100 + 100
53 } else if from.rawValue < GeneQuality.Mystic.rawValue {
54 // 5~9 Square: Threshold = (QualityLevel - 4) ^ 2 * 1000 + 1000 -> 2000, 5000, 10000, 17000, 26000
55 let argValue = from.rawValue - GeneQuality.Empowered.rawValue
56 return UInt64(argValue * argValue) * 1000 + 1000
57 } else {
58 // 10~15 Cubic: Threshold = (QualityLevel - 9) ^ 3 * 10000 + 20000
59 let argValue = from.rawValue - GeneQuality.Evolution.rawValue
60 return UInt64(argValue * argValue * argValue) * 10000 + 20000
61 }
62 }
63
64 /// Get the quality level down threshold
65 /// @param from The quality level
66 /// @param to The quality level
67 access(all)
68 view fun getGeneMergeLossOrGain(_ from: GeneQuality, _ to: GeneQuality): UFix64 {
69 // Larger quality level merge to lower quality level means gain rate
70 // The gain rate = LevelDiff * 6% + 10%
71 if from.rawValue > to.rawValue {
72 let levelDiff = from.rawValue.saturatingSubtract(to.rawValue)
73 return 1.1 + UFix64(levelDiff) * 0.06
74 } else if from.rawValue < to.rawValue {
75 // Otherwise, loss rate
76 // The loss rate = LevelDiff * 3% + 5%
77 let levelDiff = to.rawValue.saturatingSubtract(from.rawValue)
78 return 0.95 - UFix64(levelDiff) * 0.03
79 } else {
80 return 1.0
81 }
82 }
83
84 /// The gene data structure
85 ///
86 access(all) struct Gene: FixesTraits.MergeableData {
87 access(all) let id: [Character;4]
88 access(all) var quality: GeneQuality
89 access(all) var exp: UInt64
90
91 init(
92 id: [Character;4]?,
93 quality: GeneQuality?,
94 exp: UInt64?
95 ) {
96 if id != nil {
97 self.id = id!
98 } else {
99 let dnaKeys: [Character] = ["A", "C", "G", "T"]
100 self.id = [
101 dnaKeys[revertibleRandom<UInt32>(modulo: 4)],
102 dnaKeys[revertibleRandom<UInt32>(modulo: 4)],
103 dnaKeys[revertibleRandom<UInt32>(modulo: 4)],
104 dnaKeys[revertibleRandom<UInt32>(modulo: 4)]
105 ]
106 }
107 self.quality = quality ?? GeneQuality.Nascent
108 self.exp = exp ?? 0
109 // Log the gene creation
110 log("Created Gene["
111 .concat(self.id[0].toString()).concat(self.id[1].toString()).concat(self.id[2].toString()).concat(self.id[3].toString())
112 .concat("]: quality=").concat(self.quality.rawValue.toString()).concat(", exp=").concat(self.exp.toString())
113 )
114 }
115
116 /// Get the id of the data
117 ///
118 access(all)
119 view fun getId(): String {
120 return String.fromCharacters([self.id[0], self.id[1], self.id[2], self.id[3]])
121 }
122
123 /// Get the string value of the data
124 ///
125 access(all)
126 view fun toString(): String {
127 return self.getId().concat("|")
128 .concat(self.quality.rawValue.toString()).concat("=")
129 .concat(self.exp.toString())
130 }
131
132 /// Get the data keys
133 ///
134 access(all)
135 view fun getKeys(): [String] {
136 return ["id", "quality", "exp"]
137 }
138
139 /// Get the value of the data
140 ///
141 access(all)
142 view fun getValue(_ key: String): AnyStruct? {
143 if key == "id" {
144 return self.getId()
145 } else if key == "quality" {
146 return self.quality
147 } else if key == "exp" {
148 return self.exp
149 }
150 return nil
151 }
152
153 /// Split the data into another instance
154 access(FixesTraits.Write)
155 fun split(_ perc: UFix64): {FixesTraits.MergeableData} {
156 post {
157 self.getId() == result.getId(): "The gene id is not the same so cannot split"
158 }
159 let withdrawexp = UInt64(UInt256(self.exp) * UInt256(perc * 100000.0) / 100000)
160 if withdrawexp > 0 {
161 self.exp = self.exp - withdrawexp
162 }
163
164 log("Split Gene["
165 .concat(self.id[0].toString()).concat(self.id[1].toString()).concat(self.id[2].toString()).concat(self.id[3].toString())
166 .concat("]: A=").concat(self.exp.toString()).concat(", B=").concat(withdrawexp.toString())
167 .concat(", quality=").concat(self.quality.rawValue.toString()))
168
169 // Create a new struct
170 let newGenes: FixesAssetMeta.Gene = Gene(
171 id: self.id,
172 quality: self.quality, // same quality
173 exp: withdrawexp, // splited the exp
174 )
175 return newGenes
176 }
177
178 /// Merge the data from another instance
179 /// From and Self must have the same id and same type(Ensured by interface)
180 ///
181 access(FixesTraits.Write)
182 fun merge(_ from: {FixesTraits.MergeableData}): Void {
183 pre {
184 self.getId() == from.getId(): "The gene id is not the same so cannot merge"
185 }
186 let oldExp = self.exp
187 let fromGenes = from as! Gene
188 let convertRate = FixesAssetMeta.getGeneMergeLossOrGain(fromGenes.quality, self.quality)
189 let convertexp = UInt64(UInt128(fromGenes.exp) * UInt128(convertRate * 10000.0) / 10000)
190 self.exp = self.exp + convertexp
191
192 log("Merge Gene["
193 .concat(self.id[0].toString()).concat(self.id[1].toString()).concat(self.id[2].toString()).concat(self.id[3].toString())
194 .concat("]: ExpOld=").concat(oldExp.toString()).concat(", ExpNew=").concat(self.exp.toString()
195 .concat(", quality=").concat(self.quality.rawValue.toString())))
196
197 // check if the quality can be upgraded
198 var upgradeThreshold = FixesAssetMeta.getQualityLevelUpThreshold(self.quality)
199 var isUpgradable = upgradeThreshold <= self.exp && self.quality.rawValue < GeneQuality.Eternal.rawValue
200 while isUpgradable {
201 self.quality = GeneQuality(rawValue: self.quality.rawValue + 1)!
202 self.exp = self.exp - upgradeThreshold
203 // check upgrade again
204 upgradeThreshold = FixesAssetMeta.getQualityLevelUpThreshold(self.quality)
205 isUpgradable = upgradeThreshold <= self.exp && self.quality.rawValue < GeneQuality.Eternal.rawValue
206
207 log("Upgrade Gene["
208 .concat(self.id[0].toString()).concat(self.id[1].toString()).concat(self.id[2].toString()).concat(self.id[3].toString())
209 .concat("]: Exp=").concat(self.exp.toString()).concat(", quality=").concat(self.quality.rawValue.toString()))
210 }
211 }
212 }
213
214 /// The DNA data structure
215 ///
216 access(all) struct DNA: FixesTraits.MergeableData {
217 access(all) let identifier: String
218 access(all) let owner: Address
219 access(all) let genes: {String: Gene}
220 access(all) var mutatableAmount: UInt64
221
222 view init(
223 _ identifier: String,
224 _ owner: Address,
225 _ mutatableAmount: UInt64?
226 ) {
227 self.owner = owner
228 self.identifier = identifier
229 self.genes = {}
230 self.mutatableAmount = mutatableAmount ?? 0
231 }
232
233 /// Get the id of the data
234 ///
235 access(all)
236 view fun getId(): String {
237 return self.identifier.concat("@").concat(self.owner.toString())
238 }
239
240 /// Get the string value of the data
241 ///
242 access(all)
243 view fun toString(): String {
244 return "genes.count=".concat(self.genes.length.toString())
245 .concat(", mutatableAmount=").concat(self.mutatableAmount.toString())
246 }
247
248 /// Get the data keys
249 ///
250 access(all)
251 view fun getKeys(): [String] {
252 return ["identifier", "owner", "genes", "mutatableAmount"]
253 }
254
255 /// Get the value of the data
256 /// It means genes keys of the DNA
257 ///
258 access(all)
259 view fun getValue(_ key: String): AnyStruct? {
260 if key == "identifier" {
261 return self.identifier
262 } else if key == "owner" {
263 return self.owner
264 } else if key == "genes" {
265 return self.genes.keys
266 } else if key == "mutatableAmount" {
267 return self.mutatableAmount
268 }
269 return nil
270 }
271
272 /// Get the writable keys
273 ///
274 access(all)
275 view fun getWritableKeys(): [String] {
276 return ["mutatableAmount"]
277 }
278
279 /// Set the value of the data
280 ///
281 access(FixesTraits.Write)
282 fun setValue(_ key: String, _ value: AnyStruct) {
283 if key == "mutatableAmount" {
284 self.mutatableAmount = value as! UInt64
285 }
286 }
287
288 /// Split the data into another instance
289 ///
290 access(FixesTraits.Write)
291 fun split(_ perc: UFix64): {FixesTraits.MergeableData} {
292 post {
293 self.getId() == result.getId(): "The result id is not the same so cannot split"
294 }
295 let newDna = DNA(self.identifier, self.owner, nil)
296 // No need to split
297 if perc == 0.0 {
298 return newDna
299 }
300 // Mutate the mutatable amount
301 if perc >= 1.0 {
302 newDna.mutatableAmount = self.mutatableAmount
303 self.mutatableAmount = 0
304 }
305 // Split the genes
306 for key in self.genes.keys {
307 // Split the gene, use reference to ensure data consistency
308 if let geneRef = &self.genes[key] as auth(FixesTraits.Write) &Gene? {
309 let geneId = geneRef.getId()
310 // add the new gene to the new DNA
311 let newGene = geneRef.split(perc) as! Gene
312 // Don't add the gene if the exp is 0
313 if newGene.exp > 0 {
314 newDna.genes[geneId] = newGene
315 }
316 }
317 }
318 return newDna
319 }
320
321 /// Merge the data from another instance
322 /// The type of the data must be the same(Ensured by interface)
323 ///
324 access(FixesTraits.Write)
325 fun merge(_ from: {FixesTraits.MergeableData}): Void {
326 let fromDna = from as! DNA
327 // identifer must be the same
328 assert(
329 self.identifier == fromDna.identifier,
330 message: "The DNA identifier is not the same so cannot merge"
331 )
332 for key in fromDna.genes.keys {
333 if let fromGene = fromDna.genes[key] {
334 if fromGene.exp == 0 {
335 continue
336 }
337 self.mergeGene(fromGene)
338 }
339 } // end for
340 // merge mutatable amount
341 self.mutatableAmount = self.mutatableAmount + fromDna.mutatableAmount
342 }
343
344 // ---- Customized Methods ----
345
346 /// Check if the DNA is mutatable
347 ///
348 access(all)
349 view fun isMutatable(): Bool {
350 return self.mutatableAmount > 0
351 }
352
353 /// Add a gene to the DNA
354 ///
355 access(account)
356 fun addGene(_ gene: Gene): Void {
357 pre {
358 self.isMutatable(): "The DNA is not mutatable"
359 }
360 self.mergeGene(gene)
361 // Decrease the mutatable amount
362 self.mutatableAmount = self.mutatableAmount - 1
363 }
364
365 /// Merge a gene to the DNA
366 ///
367 access(self)
368 fun mergeGene(_ gene: Gene): Void {
369 let geneId = gene.getId()
370 // Merge the gene, use reference to ensure data consistency
371 if let geneRef = &self.genes[geneId] as auth(FixesTraits.Write) &Gene? {
372 geneRef.merge(gene)
373 } else {
374 // If the gene is not exist, just copy it
375 self.genes[geneId] = gene
376 }
377 }
378 }
379
380 /// Try mutate or generate a new gene
381 ///
382 access(all)
383 fun attemptToGenerateGene(): Gene? {
384 // 30% to generate a gene
385 let rand = revertibleRandom<UInt64>(modulo: 10000)
386 var quality = GeneQuality.Nascent
387 if rand < 2 {
388 // 0.02% to generate a gene with quality Miraculous
389 quality = GeneQuality.Miraculous
390 } else if rand < 5 {
391 // 0.03% to generate a gene with quality Arcane
392 quality = GeneQuality.Arcane
393 } else if rand < 10 {
394 // 0.05% to generate a gene with quality Mystic
395 quality = GeneQuality.Mystic
396 } else if rand < 20 {
397 // 0.10% to generate a gene with quality Evolution
398 quality = GeneQuality.Evolution
399 } else if rand < 40 {
400 // 0.20% to generate a gene with quality Exemplary
401 quality = GeneQuality.Exemplary
402 } else if rand < 70 {
403 // 0.30% to generate a gene with quality Potent
404 quality = GeneQuality.Potent
405 } else if rand < 120 {
406 // 0.50% to generate a gene with quality Advanced
407 quality = GeneQuality.Advanced
408 } else if rand < 200 {
409 // 0.80% to generate a gene with quality Breakthrough
410 quality = GeneQuality.Breakthrough
411 } else if rand < 350 {
412 // 1.50% to generate a gene with quality Empowered
413 quality = GeneQuality.Empowered
414 } else if rand < 600 {
415 // 2.50% to generate a gene with quality Augmented
416 quality = GeneQuality.Augmented
417 } else if rand < 1000 {
418 // 4.00% to generate a gene with quality Enhanced
419 quality = GeneQuality.Enhanced
420 } else if rand < 1800 {
421 // 8.00% to generate a gene with quality Basic
422 quality = GeneQuality.Basic
423 } else if rand < 3000 {
424 // 12.00% to generate a gene with quality Nascent
425 quality = GeneQuality.Nascent
426 } else {
427 return nil
428 }
429 let threshold = FixesAssetMeta.getQualityLevelUpThreshold(quality)
430 let exp = revertibleRandom<UInt64>(modulo: threshold / 5) // random exp from 20% of the threshold
431 return Gene(id: nil, quality: quality, exp: exp)
432 }
433
434 // ----------------- End of DNA and Gene -----------------
435
436 // ----------------- Deposit Tax (Deprecated) -----------------
437
438 /// The DepositTax data structure
439 ///
440 access(all) struct DepositTax: FixesTraits.MergeableData {
441 access(all) var flags: {String: Bool}
442
443 init(
444 _ flags: {String: Bool}?,
445 ) {
446 self.flags = flags ?? { "enabled": true }
447 }
448
449 /// Get the id of the data
450 ///
451 access(all)
452 view fun getId(): String {
453 return "DepositTax"
454 }
455
456 /// Get the string value of the data
457 ///
458 access(all)
459 view fun toString(): String {
460 var flags: String = ""
461 for key in self.flags.keys {
462 let str = key.concat("=").concat(self.flags[key] == true ? "1" : "0")
463 if flags != "" {
464 flags = flags.concat(",")
465 }
466 flags = flags.concat(str)
467 }
468 return flags
469 }
470
471 /// Get the data keys
472 ///
473 access(all)
474 view fun getKeys(): [String] {
475 return ["enabled"]
476 }
477
478 /// Get the value of the data
479 /// It means genes keys of the DNA
480 ///
481 access(all)
482 view fun getValue(_ key: String): AnyStruct? {
483 if key == "enabled" {
484 return self.flags["enabled"]
485 }
486 return nil
487 }
488
489 /// Get the writable keys
490 ///
491 access(all)
492 view fun getWritableKeys(): [String] {
493 return ["enabled"]
494 }
495
496 /// Set the value of the data
497 ///
498 access(FixesTraits.Write)
499 fun setValue(_ key: String, _ value: AnyStruct) {
500 if key == "enabled" {
501 self.flags["enabled"] = value as! Bool
502 }
503 }
504
505 /// Split the data into another instance
506 ///
507 access(FixesTraits.Write)
508 fun split(_ perc: UFix64): {FixesTraits.MergeableData} {
509 post {
510 self.getId() == result.getId(): "The result id is not the same so cannot split"
511 }
512 return DepositTax(self.flags)
513 }
514
515 /// Merge the data from another instance
516 /// The type of the data must be the same(Ensured by interface)
517 ///
518 access(FixesTraits.Write)
519 fun merge(_ from: {FixesTraits.MergeableData}): Void {
520 // Nothing to merge
521 }
522 }
523
524 // ----------------- End of Deposit Tax -----------------
525
526 // ----------------- ExclusiveMeta -----------------
527
528 /// The DepositTax data structure
529 ///
530 access(all) struct ExclusiveMeta: FixesTraits.MergeableData {
531
532 /// Get the id of the data
533 ///
534 access(all)
535 view fun getId(): String {
536 return "ExclusiveMeta"
537 }
538
539 /// Get the string value of the data
540 ///
541 access(all)
542 view fun toString(): String {
543 return self.getId()
544 }
545
546 /// Get the data keys
547 ///
548 access(all)
549 view fun getKeys(): [String] {
550 return []
551 }
552
553 /// Get the value of the data
554 /// It means genes keys of the DNA
555 ///
556 access(all)
557 view fun getValue(_ key: String): AnyStruct? {
558 return nil
559 }
560
561 /// Split the data into another instance
562 ///
563 access(FixesTraits.Write)
564 fun split(_ perc: UFix64): {FixesTraits.MergeableData} {
565 return ExclusiveMeta()
566 }
567
568 /// Merge the data from another instance
569 /// The type of the data must be the same(Ensured by interface)
570 ///
571 access(FixesTraits.Write)
572 fun merge(_ from: {FixesTraits.MergeableData}): Void {
573 // Nothing to merge
574 }
575 }
576
577 // ----------------- End of ExclusiveMeta -----------------
578
579}
580