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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}