Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

participation.gno

16.98 Kb · 493 lines
  1package game_vault
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"strconv"
  7
  8	"gno.land/p/akkadia/beta/accesscontrol"
  9	"gno.land/r/akkadia/beta/admin"
 10)
 11
 12type ParticipationProcessStatus int
 13
 14const (
 15	ParticipationProcessStatusInvalid ParticipationProcessStatus = iota
 16	ParticipationProcessStatusOpen
 17	ParticipationProcessStatusClosed
 18	ParticipationProcessStatusCommitted
 19	ParticipationProcessStatusCanceled
 20)
 21
 22type ParticipationFundStatus int
 23
 24const (
 25	ParticipationFundStatusInvalid ParticipationFundStatus = iota
 26	ParticipationFundStatusHeld
 27	ParticipationFundStatusCommitted
 28	ParticipationFundStatusRefunded
 29)
 30
 31type ParticipationProcess struct {
 32	ID               uint64
 33	ProcessKey       string
 34	GameWorldID      uint64
 35	RewardPoolID     uint64
 36	ParticipationFee int64
 37	Status           ParticipationProcessStatus
 38	HeldBalance      int64
 39	CommittedTotal   int64
 40	RefundedTotal    int64
 41	FundCount        uint64
 42}
 43
 44type ParticipationProcessView struct {
 45	ID               uint64
 46	ProcessKey       string
 47	GameWorldID      uint64
 48	RewardPoolKey    string
 49	ParticipationFee int64
 50	Status           ParticipationProcessStatus
 51	HeldBalance      int64
 52	CommittedTotal   int64
 53	RefundedTotal    int64
 54	FundCount        uint64
 55}
 56
 57type ParticipationFund struct {
 58	ID               uint64
 59	ProcessID        uint64
 60	ParticipationKey string
 61	PaymentKey       string
 62	Owner            address
 63	Amount           int64
 64	Refunded         bool
 65}
 66
 67func CreateParticipationProcess(cur realm, processKey string, gameWorldID uint64, rewardPoolKey string, participationFeeUgnot int64) uint64 {
 68	accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator)
 69	assertNoAttachedCoins()
 70	assertKeyLength(processKey, "process key")
 71	assertKeyLength(rewardPoolKey, "reward pool key")
 72	if participationProcessIDs.Has(processKey) {
 73		panic("participation process key already exists")
 74	}
 75	if participationFeeUgnot <= 0 {
 76		panic("participation fee must be positive")
 77	}
 78	assertGameWorldExists(gameWorldID)
 79	rewardPoolID := GetRewardPoolID(rewardPoolKey)
 80	if rewardPoolID == 0 {
 81		panic("reward pool not found: " + rewardPoolKey)
 82	}
 83	pool := GetRewardPool(rewardPoolID)
 84	if pool.GameWorldID != gameWorldID {
 85		panic("reward pool belongs to another game world")
 86	}
 87	if pool.Status != RewardPoolStatusOpen {
 88		panic("reward pool is not open")
 89	}
 90	if latestParticipationProcessID == maxUint64 {
 91		panic("participation process id exhausted")
 92	}
 93
 94	latestParticipationProcessID++
 95	processID := latestParticipationProcessID
 96	treeSet(participationProcesses, processID, ParticipationProcess{
 97		ID:               processID,
 98		ProcessKey:       processKey,
 99		GameWorldID:      gameWorldID,
100		RewardPoolID:     rewardPoolID,
101		ParticipationFee: participationFeeUgnot,
102		Status:           ParticipationProcessStatusOpen,
103	})
104	participationProcessIDs.Set(processKey, processID)
105	getOrCreateIDSet(gameWorldParticipationProcessIDs, gameWorldID).Set(uint64Key(processID), true)
106	return processID
107}
108
109func CloseParticipationProcess(cur realm, processID uint64) {
110	accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator)
111	assertNoAttachedCoins()
112	process := GetParticipationProcess(processID)
113	if process.Status == ParticipationProcessStatusClosed {
114		return
115	}
116	if process.Status != ParticipationProcessStatusOpen {
117		panic("participation process cannot be closed")
118	}
119	process.Status = ParticipationProcessStatusClosed
120	treeSet(participationProcesses, processID, process)
121}
122
123func CancelParticipationProcess(cur realm, processID uint64) {
124	accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator)
125	assertNoAttachedCoins()
126	process := GetParticipationProcess(processID)
127	if process.Status == ParticipationProcessStatusCanceled {
128		return
129	}
130	if process.Status != ParticipationProcessStatusOpen && process.Status != ParticipationProcessStatusClosed {
131		panic("participation process cannot be canceled")
132	}
133	process.Status = ParticipationProcessStatusCanceled
134	treeSet(participationProcesses, processID, process)
135}
136
137func DepositParticipation(cur realm, processID uint64, participationKey, paymentKey string) uint64 {
138	owner := accesscontrol.MustGetUserCaller(0, cur)
139	assertKeyLength(participationKey, "participation key")
140	assertKeyLength(paymentKey, "payment key")
141	process := GetParticipationProcess(processID)
142	if process.Status != ParticipationProcessStatusOpen {
143		panic("participation process is not open")
144	}
145	paymentIndexKey := keyedOwnerKey(processID, owner, paymentKey)
146	if participationFundIDs.Has(paymentIndexKey) {
147		panic("payment key already processed")
148	}
149	assertExactUgnotPayment(process.ParticipationFee)
150	if latestParticipationFundID == maxUint64 || process.FundCount == maxUint64 {
151		panic("participation fund id exhausted")
152	}
153	if process.HeldBalance > maxInt64-process.ParticipationFee || accountedUgnotTotal > maxInt64-process.ParticipationFee {
154		panic("vault fund amount overflow")
155	}
156
157	latestParticipationFundID++
158	fundID := latestParticipationFundID
159	treeSet(participationFunds, fundID, ParticipationFund{
160		ID:               fundID,
161		ProcessID:        processID,
162		ParticipationKey: participationKey,
163		PaymentKey:       paymentKey,
164		Owner:            owner,
165		Amount:           process.ParticipationFee,
166	})
167	participationFundIDs.Set(paymentIndexKey, fundID)
168	addStringSetID(participationOwnerFundIDs, ownerKey(processID, owner), fundID)
169	addStringSetID(participationGroupFundIDs, keyedOwnerKey(processID, owner, participationKey), fundID)
170	process.HeldBalance += process.ParticipationFee
171	process.FundCount++
172	treeSet(participationProcesses, processID, process)
173	accountedUgnotTotal += process.ParticipationFee
174	return fundID
175}
176
177func RefundParticipationFund(cur realm, fundID uint64, operationKey string) uint64 {
178	executor := accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator)
179	assertNoAttachedCoins()
180	assertNewOperationKey(operationKey)
181	fund := GetParticipationFund(fundID)
182	if fund.Refunded {
183		panic("participation fund already refunded")
184	}
185	process := GetParticipationProcess(fund.ProcessID)
186	if process.Status != ParticipationProcessStatusOpen && process.Status != ParticipationProcessStatusClosed && process.Status != ParticipationProcessStatusCanceled {
187		panic("participation fund cannot be refunded")
188	}
189	if process.HeldBalance < fund.Amount {
190		panic("insufficient participation fund balance")
191	}
192
193	process.HeldBalance -= fund.Amount
194	process.RefundedTotal += fund.Amount
195	treeSet(participationProcesses, process.ID, process)
196	fund.Refunded = true
197	treeSet(participationFunds, fund.ID, fund)
198	return recordAndSendRefund(cross(cur), process.RewardPoolID, operationKey, fund.PaymentKey, fund.Owner, fund.Amount, "participation", executor)
199}
200
201func CommitParticipationProcess(cur realm, processID uint64, operationKey string) {
202	accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator)
203	assertNoAttachedCoins()
204	assertNewOperationKey(operationKey)
205	process := GetParticipationProcess(processID)
206	if process.Status != ParticipationProcessStatusClosed {
207		panic("participation process must be closed before commit")
208	}
209	pool := GetRewardPool(process.RewardPoolID)
210	if pool.Status != RewardPoolStatusOpen {
211		panic("reward pool is not open")
212	}
213	amount := process.HeldBalance
214	if pool.Balance > maxInt64-amount || pool.ParticipationCommittedTotal > maxInt64-amount || process.CommittedTotal > maxInt64-amount {
215		panic("vault fund amount overflow")
216	}
217
218	process.HeldBalance = 0
219	process.CommittedTotal += amount
220	process.Status = ParticipationProcessStatusCommitted
221	treeSet(participationProcesses, processID, process)
222	pool.ParticipationCommittedTotal += amount
223	pool.Balance += amount
224	treeSet(rewardPools, pool.ID, pool)
225	processedOperationKeys.Set(operationKey, processID)
226}
227
228func GetParticipationProcessID(processKey string) uint64 {
229	assertKeyLength(processKey, "process key")
230	value, found := stringGet(participationProcessIDs, processKey)
231	if !found {
232		return 0
233	}
234	return value.(uint64)
235}
236
237func GetParticipationProcess(processID uint64) ParticipationProcess {
238	value, found := treeGet(participationProcesses, processID)
239	if !found {
240		panic("participation process not found: " + strconv.FormatUint(processID, 10))
241	}
242	return value.(ParticipationProcess)
243}
244
245func GetParticipationProcessByKey(processKey string) ParticipationProcessView {
246	processID := GetParticipationProcessID(processKey)
247	if processID == 0 {
248		panic("participation process not found: " + processKey)
249	}
250	process := GetParticipationProcess(processID)
251	return ParticipationProcessView{
252		ID:               process.ID,
253		ProcessKey:       process.ProcessKey,
254		GameWorldID:      process.GameWorldID,
255		RewardPoolKey:    GetRewardPool(process.RewardPoolID).PoolKey,
256		ParticipationFee: process.ParticipationFee,
257		Status:           process.Status,
258		HeldBalance:      process.HeldBalance,
259		CommittedTotal:   process.CommittedTotal,
260		RefundedTotal:    process.RefundedTotal,
261		FundCount:        process.FundCount,
262	}
263}
264
265func ListParticipationProcessStates(processKeys ...string) []map[string]string {
266	if len(processKeys) > gameVaultList {
267		panic("count exceeds game vault list limit")
268	}
269
270	result := []map[string]string{}
271	for _, processKey := range processKeys {
272		processID := GetParticipationProcessID(processKey)
273		state := map[string]string{
274			"requestedProcessKey": processKey,
275			"processId":           "0",
276			"processKey":          "",
277			"gameWorldId":         "0",
278			"rewardPoolKey":       "",
279			"participationFee":    "0",
280			"processStatus":       "0",
281			"heldBalance":         "0",
282			"committedTotal":      "0",
283			"refundedTotal":       "0",
284			"fundCount":           "0",
285		}
286		if processID != 0 {
287			process := GetParticipationProcess(processID)
288			state["processId"] = strconv.FormatUint(process.ID, 10)
289			state["processKey"] = process.ProcessKey
290			state["gameWorldId"] = strconv.FormatUint(process.GameWorldID, 10)
291			state["rewardPoolKey"] = GetRewardPool(process.RewardPoolID).PoolKey
292			state["participationFee"] = strconv.FormatInt(process.ParticipationFee, 10)
293			state["processStatus"] = strconv.Itoa(int(process.Status))
294			state["heldBalance"] = strconv.FormatInt(process.HeldBalance, 10)
295			state["committedTotal"] = strconv.FormatInt(process.CommittedTotal, 10)
296			state["refundedTotal"] = strconv.FormatInt(process.RefundedTotal, 10)
297			state["fundCount"] = strconv.FormatUint(process.FundCount, 10)
298		}
299		result = append(result, state)
300	}
301	return result
302}
303
304func ListParticipationPaymentStates(processKeysCSV, ownersCSV, paymentKeysCSV string) []map[string]string {
305	result := []map[string]string{}
306	if processKeysCSV == "" && ownersCSV == "" && paymentKeysCSV == "" {
307		return result
308	}
309	processStart, ownerStart, paymentStart := 0, 0, 0
310	for {
311		processKey, processNext, processDone := readQueryCSVEntry(processKeysCSV, processStart)
312		ownerValue, ownerNext, ownerDone := readQueryCSVEntry(ownersCSV, ownerStart)
313		paymentKey, paymentNext, paymentDone := readQueryCSVEntry(paymentKeysCSV, paymentStart)
314		if processDone != ownerDone || ownerDone != paymentDone {
315			panic("participation payment value count mismatch")
316		}
317		if len(result) == gameVaultList {
318			panic("count exceeds game vault list limit")
319		}
320		processID := GetParticipationProcessID(processKey)
321		owner := address(ownerValue)
322		assertValidOwner(owner)
323		assertKeyLength(paymentKey, "payment key")
324		state := map[string]string{
325			"requestedProcessKey": processKey,
326			"owner":               owner.String(),
327			"paymentKey":          paymentKey,
328			"processId":           "0",
329			"processKey":          "",
330			"participationFee":    "0",
331			"processStatus":       "0",
332			"fundId":              "0",
333			"fundProcessId":       "0",
334			"participationKey":    "",
335			"fundPaymentKey":      "",
336			"fundOwner":           "",
337			"fundAmount":          "0",
338			"fundRefunded":        "false",
339			"fundEffectiveStatus": "0",
340		}
341		if processID != 0 {
342			process := GetParticipationProcess(processID)
343			state["processId"] = strconv.FormatUint(process.ID, 10)
344			state["processKey"] = process.ProcessKey
345			state["participationFee"] = strconv.FormatInt(process.ParticipationFee, 10)
346			state["processStatus"] = strconv.Itoa(int(process.Status))
347			fundID, found := stringGet(participationFundIDs, keyedOwnerKey(processID, owner, paymentKey))
348			if found {
349				fund := GetParticipationFund(fundID.(uint64))
350				state["fundId"] = strconv.FormatUint(fund.ID, 10)
351				state["fundProcessId"] = strconv.FormatUint(fund.ProcessID, 10)
352				state["participationKey"] = fund.ParticipationKey
353				state["fundPaymentKey"] = fund.PaymentKey
354				state["fundOwner"] = fund.Owner.String()
355				state["fundAmount"] = strconv.FormatInt(fund.Amount, 10)
356				state["fundRefunded"] = strconv.FormatBool(fund.Refunded)
357				state["fundEffectiveStatus"] = strconv.Itoa(int(GetParticipationFundEffectiveStatus(fund.ID)))
358			}
359		}
360		result = append(result, state)
361		if processDone {
362			return result
363		}
364		processStart, ownerStart, paymentStart = processNext, ownerNext, paymentNext
365	}
366}
367
368func GetLatestParticipationProcessID() uint64 { return latestParticipationProcessID }
369
370func GetParticipationFund(fundID uint64) ParticipationFund {
371	value, found := treeGet(participationFunds, fundID)
372	if !found {
373		panic("participation fund not found: " + strconv.FormatUint(fundID, 10))
374	}
375	return value.(ParticipationFund)
376}
377
378func GetLatestParticipationFundID() uint64 { return latestParticipationFundID }
379
380func GetParticipationFundEffectiveStatus(fundID uint64) ParticipationFundStatus {
381	fund := GetParticipationFund(fundID)
382	if fund.Refunded {
383		return ParticipationFundStatusRefunded
384	}
385	if GetParticipationProcess(fund.ProcessID).Status == ParticipationProcessStatusCommitted {
386		return ParticipationFundStatusCommitted
387	}
388	return ParticipationFundStatusHeld
389}
390
391func GetParticipationFundID(processID uint64, owner address, paymentKey string) uint64 {
392	GetParticipationProcess(processID)
393	assertValidOwner(owner)
394	assertKeyLength(paymentKey, "payment key")
395	value, found := stringGet(participationFundIDs, keyedOwnerKey(processID, owner, paymentKey))
396	if !found {
397		return 0
398	}
399	return value.(uint64)
400}
401
402func ListParticipationFundStates(values ...string) []map[string]string {
403	if len(values)%3 != 0 {
404		panic("participation fund value count mismatch")
405	}
406	if len(values)/3 > gameVaultList {
407		panic("count exceeds game vault list limit")
408	}
409
410	result := []map[string]string{}
411	for index := 0; index < len(values); index += 3 {
412		processID, err := strconv.ParseUint(values[index], 10, 64)
413		if err != nil || processID == 0 {
414			panic("invalid participation process id")
415		}
416		owner := address(values[index+1])
417		paymentKey := values[index+2]
418		fundID := GetParticipationFundID(processID, owner, paymentKey)
419		state := map[string]string{
420			"processId":           strconv.FormatUint(processID, 10),
421			"owner":               owner.String(),
422			"paymentKey":          paymentKey,
423			"fundId":              "0",
424			"fundProcessId":       "0",
425			"participationKey":    "",
426			"fundPaymentKey":      "",
427			"fundOwner":           "",
428			"fundAmount":          "0",
429			"fundRefunded":        "false",
430			"fundEffectiveStatus": "0",
431		}
432		if fundID != 0 {
433			fund := GetParticipationFund(fundID)
434			state["fundId"] = strconv.FormatUint(fund.ID, 10)
435			state["fundProcessId"] = strconv.FormatUint(fund.ProcessID, 10)
436			state["participationKey"] = fund.ParticipationKey
437			state["fundPaymentKey"] = fund.PaymentKey
438			state["fundOwner"] = fund.Owner.String()
439			state["fundAmount"] = strconv.FormatInt(fund.Amount, 10)
440			state["fundRefunded"] = strconv.FormatBool(fund.Refunded)
441			state["fundEffectiveStatus"] = strconv.Itoa(int(GetParticipationFundEffectiveStatus(fundID)))
442		}
443		result = append(result, state)
444	}
445	return result
446}
447
448func GetParticipationFundCount(processID uint64, owner address, participationKey string) int {
449	GetParticipationProcess(processID)
450	assertValidOwner(owner)
451	assertKeyLength(participationKey, "participation key")
452	ids, found := getStringSet(participationGroupFundIDs, keyedOwnerKey(processID, owner, participationKey))
453	if !found {
454		return 0
455	}
456	return ids.Size()
457}
458
459func ListParticipationFundIDs(processID uint64, owner address, participationKey string, page, count int) []uint64 {
460	GetParticipationProcess(processID)
461	assertValidOwner(owner)
462	assertKeyLength(participationKey, "participation key")
463	ids, _ := getStringSet(participationGroupFundIDs, keyedOwnerKey(processID, owner, participationKey))
464	return listIDs(ids, page, count)
465}
466
467func GetParticipationFundCountByOwner(processID uint64, owner address) int {
468	GetParticipationProcess(processID)
469	assertValidOwner(owner)
470	ids, found := getStringSet(participationOwnerFundIDs, ownerKey(processID, owner))
471	if !found {
472		return 0
473	}
474	return ids.Size()
475}
476
477func ListParticipationFundIDsByOwner(processID uint64, owner address, page, count int) []uint64 {
478	GetParticipationProcess(processID)
479	assertValidOwner(owner)
480	ids, _ := getStringSet(participationOwnerFundIDs, ownerKey(processID, owner))
481	return listIDs(ids, page, count)
482}
483
484func assertValidOwner(owner address) {
485	if !owner.IsValid() {
486		panic("invalid participation owner")
487	}
488}
489
490func sendUgnot(cur realm, recipient address, amount int64) {
491	accountedUgnotTotal -= amount
492	banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(cur.Address(), recipient, chain.Coins{{"ugnot", amount}})
493}