Smart Contract
FRC20VoteCommands
A.d2abb5dbf5e08666.FRC20VoteCommands
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# FRC20VoteCommands
5
6This contract is used to manage the frc20 vote commands.
7
8*/
9import FlowToken from 0x1654653399040a61
10import Fixes from 0xd2abb5dbf5e08666
11import FixesInscriptionFactory from 0xd2abb5dbf5e08666
12import FRC20Indexer from 0xd2abb5dbf5e08666
13import FRC20FTShared from 0xd2abb5dbf5e08666
14import FRC20AccountsPool from 0xd2abb5dbf5e08666
15import FRC20StakingManager from 0xd2abb5dbf5e08666
16import FRC20StakingVesting from 0xd2abb5dbf5e08666
17import FGameLottery from 0xd2abb5dbf5e08666
18import FGameLotteryRegistry from 0xd2abb5dbf5e08666
19import FGameLotteryFactory from 0xd2abb5dbf5e08666
20
21access(all) contract FRC20VoteCommands {
22
23 /// The Proposal command type.
24 ///
25 access(all) enum CommandType: UInt8 {
26 access(all) case None;
27 access(all) case SetBurnable;
28 access(all) case BurnUnsupplied;
29 access(all) case MoveTreasuryToLotteryJackpot;
30 access(all) case MoveTreasuryToStakingReward;
31 }
32
33 /// The interface of FRC20 vote command struct.
34 ///
35 access(all) struct interface IVoteCommand {
36 access(all)
37 let inscriptionIds: [UInt64]
38
39 // ----- Readonly Mehtods -----
40
41 /// Get the command type.
42 access(all)
43 view fun getCommandType(): CommandType
44 // It is readonly, but it is not a view function.
45 access(all)
46 fun verifyVoteCommands(): Bool
47
48 /// Check if all inscriptions are extracted.
49 ///
50 access(all)
51 view fun isAllInscriptionsExtracted(): Bool {
52 let store = FRC20VoteCommands.borrowSystemInscriptionsStore()
53 for id in self.inscriptionIds {
54 if let insRef = store.borrowInscription(id) {
55 if !insRef.isExtracted() {
56 return false
57 }
58 }
59 }
60 return true
61 }
62
63 // ----- Account level methods -----
64
65 /// Refund the inscription cost for failed vote commands.
66 ///
67 access(account)
68 fun refundFailedVoteCommands(receiver: Address): Bool {
69 let recieverRef = Fixes.borrowFlowTokenReceiver(receiver)
70 if recieverRef == nil {
71 return false
72 }
73 let store = FRC20VoteCommands.borrowSystemInscriptionsStore()
74 let insRefArr = self.borrowSystemInscriptionWritableRefs()
75
76 let vault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
77 for insRef in insRefArr {
78 if !insRef.isExtracted() {
79 vault.deposit(from: <-insRef.extract())
80 }
81 }
82 // deposit to the receiver
83 recieverRef!.deposit(from: <- vault)
84 return true
85 }
86
87 // Methods: Write
88 access(account)
89 fun safeRunVoteCommands(): Bool
90
91 // ----- General Methods -----
92
93 /// Borrow the system inscriptions references from store.
94 ///
95 access(contract)
96 fun borrowSystemInscriptionWritableRefs(): [auth(Fixes.Extractable) &Fixes.Inscription] {
97 let store = FRC20VoteCommands.borrowSystemInscriptionsStore()
98 let ret: [auth(Fixes.Extractable) &Fixes.Inscription] = []
99 for id in self.inscriptionIds {
100 if let ref = store.borrowInscriptionWritableRefInAccount(id) {
101 ret.append(ref)
102 }
103 }
104 return ret
105 }
106 }
107
108 /**
109 * Command: None
110 */
111 access(all) struct CommandNone: IVoteCommand {
112 access(all)
113 let inscriptionIds: [UInt64]
114
115 init() {
116 self.inscriptionIds = []
117 }
118
119 // ----- Methods: Read -----
120
121 access(all)
122 view fun getCommandType(): CommandType {
123 return CommandType.None
124 }
125
126 access(all)
127 fun verifyVoteCommands(): Bool {
128 return true
129 }
130
131 // ---- Methods: Write ----
132
133 access(account)
134 fun safeRunVoteCommands(): Bool {
135 return true
136 }
137 }
138
139 /**
140 * Command: SetBurnable
141 */
142 access(all) struct CommandSetBurnable: IVoteCommand {
143 access(all)
144 let inscriptionIds: [UInt64]
145
146 init(_ insIds: [UInt64]) {
147 self.inscriptionIds = insIds
148
149 assert(
150 self.verifyVoteCommands(),
151 message: "Invalid vote commands"
152 )
153 }
154
155 // ----- Methods: Read -----
156
157 access(all)
158 view fun getCommandType(): CommandType {
159 return CommandType.SetBurnable
160 }
161
162 access(all)
163 fun verifyVoteCommands(): Bool {
164 var isValid = false
165 isValid = self.inscriptionIds.length == 1
166 if isValid {
167 let store = FRC20VoteCommands.borrowSystemInscriptionsStore()
168 if let ins = store.borrowInscription(self.inscriptionIds[0]) {
169 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
170 isValid = FRC20VoteCommands.isValidSystemInscription(ins)
171 && meta["op"] == "burnable" && meta["tick"] != nil && meta["v"] != nil
172 } else {
173 isValid = false
174 }
175 }
176 return isValid
177 }
178
179 // ---- Methods: Write ----
180
181 access(account)
182 fun safeRunVoteCommands(): Bool {
183 // Refs
184 let frc20Indexer = FRC20Indexer.getIndexer()
185 let insRefArr = self.borrowSystemInscriptionWritableRefs()
186
187 if insRefArr.length != 1 {
188 return false
189 }
190 frc20Indexer.setBurnable(ins: insRefArr[0])
191 return true
192 }
193 }
194
195 /**
196 * Command: BurnUnsupplied
197 */
198 access(all) struct CommandBurnUnsupplied: IVoteCommand {
199 access(all)
200 let inscriptionIds: [UInt64]
201
202 init(_ insIds: [UInt64]) {
203 self.inscriptionIds = insIds
204
205 assert(
206 self.verifyVoteCommands(),
207 message: "Invalid vote commands"
208 )
209 }
210
211 // ----- Methods: Read -----
212
213 access(all)
214 view fun getCommandType(): CommandType {
215 return CommandType.BurnUnsupplied
216 }
217
218 access(all)
219 fun verifyVoteCommands(): Bool {
220 // Refs
221 let insRefArr = self.borrowSystemInscriptionWritableRefs()
222
223 var isValid = insRefArr.length == 1
224 if isValid {
225 let ins = insRefArr[0]
226 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
227 isValid = FRC20VoteCommands.isValidSystemInscription(ins)
228 && meta["op"] == "burnUnsup" && meta["tick"] != nil && meta["perc"] != nil
229 }
230 return isValid
231 }
232
233 // ---- Methods: Write ----
234
235 access(account)
236 fun safeRunVoteCommands(): Bool {
237 // Refs
238 let frc20Indexer = FRC20Indexer.getIndexer()
239 let insRefArr = self.borrowSystemInscriptionWritableRefs()
240
241 if insRefArr.length != 1 {
242 return false
243 }
244 frc20Indexer.burnUnsupplied(ins: insRefArr[0])
245 return true
246 }
247 }
248
249 /**
250 * Command: MoveTreasuryToLotteryJackpot
251 */
252 access(all) struct CommandMoveTreasuryToLotteryJackpot: IVoteCommand {
253 access(all)
254 let inscriptionIds: [UInt64]
255
256 init(_ insIds: [UInt64]) {
257 self.inscriptionIds = insIds
258
259 assert(
260 self.verifyVoteCommands(),
261 message: "Invalid vote commands"
262 )
263 }
264
265 // ----- Methods: Read -----
266
267 access(all)
268 view fun getCommandType(): CommandType {
269 return CommandType.MoveTreasuryToLotteryJackpot
270 }
271
272 access(all)
273 fun verifyVoteCommands(): Bool {
274 var isValid = self.inscriptionIds.length == 1
275 if isValid {
276 let store = FRC20VoteCommands.borrowSystemInscriptionsStore()
277 if let ins = store.borrowInscription(self.inscriptionIds[0]) {
278 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
279 isValid = FRC20VoteCommands.isValidSystemInscription(ins)
280 && meta["op"] == "withdrawFromTreasury"
281 && meta["usage"] == "lottery"
282 && meta["tick"] != nil && meta["amt"] != nil
283 } else {
284 isValid = false
285 }
286 }
287 return isValid
288 }
289
290 // ---- Methods: Write ----
291
292 access(account)
293 fun safeRunVoteCommands(): Bool {
294 // Refs
295 let frc20Indexer = FRC20Indexer.getIndexer()
296 let insRefArr = self.borrowSystemInscriptionWritableRefs()
297
298 if insRefArr.length != 1 {
299 return false
300 }
301 let flowLotteryName = FGameLotteryFactory.getFIXESMintingLotteryPoolName()
302 let registery = FGameLotteryRegistry.borrowRegistry()
303 if let poolAddr = registery.getLotteryPoolAddress(flowLotteryName) {
304 if let poolRef = FGameLottery.borrowLotteryPool(poolAddr) {
305 let withdrawnChange <- frc20Indexer.withdrawFromTreasury(ins: insRefArr[0])
306 poolRef.donateToJackpot(payment: <- withdrawnChange)
307 return true
308 }
309 }
310 log("Failed to find the lottery pool")
311 return false
312 }
313 }
314
315 /**
316 * Command: MoveTreasuryToStakingReward
317 */
318 access(all) struct CommandMoveTreasuryToStakingReward: IVoteCommand {
319 access(all)
320 let inscriptionIds: [UInt64]
321
322 init(_ insIds: [UInt64]) {
323 self.inscriptionIds = insIds
324
325 assert(
326 self.verifyVoteCommands(),
327 message: "Invalid vote commands"
328 )
329 }
330
331 // ----- Methods: Read -----
332
333 access(all)
334 view fun getCommandType(): CommandType {
335 return CommandType.MoveTreasuryToStakingReward
336 }
337
338 access(all)
339 fun verifyVoteCommands(): Bool {
340 var isValid = self.inscriptionIds.length == 1
341 if isValid {
342 let store = FRC20VoteCommands.borrowSystemInscriptionsStore()
343 if let ins = store.borrowInscription(self.inscriptionIds[0]) {
344 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
345 isValid = FRC20VoteCommands.isValidSystemInscription(ins)
346 && meta["op"] == "withdrawFromTreasury" && meta["usage"] == "staking"
347 && meta["tick"] != nil && meta["amt"] != nil
348 && meta["batch"] != nil && meta["interval"] != nil
349 } else {
350 isValid = false
351 }
352 }
353 return isValid
354 }
355
356 // ---- Methods: Write ----
357
358 access(account)
359 fun safeRunVoteCommands(): Bool {
360 // Refs
361 let frc20Indexer = FRC20Indexer.getIndexer()
362 let insRefArr = self.borrowSystemInscriptionWritableRefs()
363
364 if insRefArr.length != 1 {
365 return false
366 }
367 let ins = insRefArr[0]
368 let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
369
370 // singleton resources
371 let acctsPool = FRC20AccountsPool.borrowAccountsPool()
372 let platformStakeTick = FRC20FTShared.getPlatformStakingTickerName()
373 let vestingBatch = UInt32.fromString(meta["batch"]!)
374 let vestingInterval = UFix64.fromString(meta["interval"]!)
375 if vestingBatch == nil || vestingInterval == nil {
376 log("Invalid vesting batch or interval")
377 return false
378 }
379 if let stakingAddress = acctsPool.getFRC20StakingAddress(tick: platformStakeTick) {
380 if let vestingVault = FRC20StakingVesting.borrowVaultRef(stakingAddress) {
381 let withdrawnChange <- frc20Indexer.withdrawFromTreasury(ins: insRefArr[0])
382 FRC20StakingManager.donateToVestingFromChange(
383 changeToDonate: <- withdrawnChange,
384 tick: platformStakeTick,
385 vestingBatchAmount: vestingBatch!,
386 vestingInterval: vestingInterval!
387 )
388 return true
389 }
390 }
391 log("Failed to find valid staking pool")
392 return false
393 }
394 }
395
396 /// Check if the given inscription is a valid system inscription.
397 ///
398 access(contract)
399 view fun isValidSystemInscription(_ ins: &Fixes.Inscription): Bool {
400 let frc20Indexer = FRC20Indexer.getIndexer()
401 return ins.owner?.address == self.account.address
402 && ins.isExtractable()
403 && frc20Indexer.isValidFRC20Inscription(ins: ins)
404 }
405
406 /// Borrow the system inscriptions store.
407 ///
408 access(all)
409 view fun borrowSystemInscriptionsStore(): &Fixes.InscriptionsStore {
410 let storePubPath = Fixes.getFixesStorePublicPath()
411 return self.account
412 .capabilities.get<&Fixes.InscriptionsStore>(storePubPath)
413 .borrow() ?? panic("Fixes.InscriptionsStore is not found")
414 }
415
416 /// Build the inscription strings by the given command type and meta.
417 ///
418 access(all)
419 view fun buildInscriptionStringsByCommand(_ type: CommandType, _ meta: {String: String}): [String] {
420 switch type {
421 case CommandType.None:
422 return []
423 case CommandType.SetBurnable:
424 return [
425 FixesInscriptionFactory.buildVoteCommandSetBurnable(
426 tick: meta["tick"] ?? panic("Missing tick in params"),
427 burnable: meta["v"] == "1"
428 )
429 ]
430 case CommandType.BurnUnsupplied:
431 return [
432 FixesInscriptionFactory.buildVoteCommandBurnUnsupplied(
433 tick: meta["tick"] ?? panic("Missing tick in params"),
434 percent: UFix64.fromString(meta["perc"] ?? panic("Missing perc in params")) ?? panic("Invalid perc")
435 )
436 ]
437 case CommandType.MoveTreasuryToLotteryJackpot:
438 return [
439 FixesInscriptionFactory.buildVoteCommandMoveTreasuryToLotteryJackpot(
440 tick: meta["tick"] ?? panic("Missing tick in params"),
441 amount: UFix64.fromString(meta["amt"] ?? panic("Missing amt in params")) ?? panic("Invalid amt")
442 )
443 ]
444 case CommandType.MoveTreasuryToStakingReward:
445 return [
446 FixesInscriptionFactory.buildVoteCommandMoveTreasuryToStaking(
447 tick: meta["tick"] ?? panic("Missing tick in params"),
448 amount: UFix64.fromString(meta["amt"] ?? panic("Missing amt in params")) ?? panic("Invalid amt"),
449 vestingBatchAmount: UInt32.fromString(meta["batch"] ?? panic("Missing batch in params")) ?? panic("Invalid batch"),
450 vestingInterval: UFix64.fromString(meta["interval"] ?? panic("Missing interval in params")) ?? panic("Invalid interval")
451 )
452 ]
453 }
454 panic("Invalid command type")
455 }
456
457 /// Create a vote command by the given type and inscriptions.
458 ///
459 access(all)
460 fun createByCommandType(_ type: CommandType, _ inscriptions: [UInt64]): {IVoteCommand} {
461 let store = self.borrowSystemInscriptionsStore()
462 // Ensure the inscriptions are valid
463 for ins in inscriptions {
464 let insRef = store.borrowInscription(ins)
465 if insRef == nil {
466 panic("Invalid inscription")
467 }
468 }
469
470 switch type {
471 case CommandType.None:
472 return CommandNone()
473 case CommandType.SetBurnable:
474 return CommandSetBurnable(inscriptions)
475 case CommandType.BurnUnsupplied:
476 return CommandBurnUnsupplied(inscriptions)
477 case CommandType.MoveTreasuryToLotteryJackpot:
478 return CommandMoveTreasuryToLotteryJackpot(inscriptions)
479 case CommandType.MoveTreasuryToStakingReward:
480 return CommandMoveTreasuryToStakingReward(inscriptions)
481 }
482 panic("Invalid command type")
483 }
484}
485