← Back to Realms

Realm

gno.land/r/akkadia/beta/game_vault

Overview

Realm Path
gno.land/r/akkadia/beta/game_vault
Exported Functions
73
State Entries
80
Source Files
13
Total Package Entries
184

Exported Functions

73 exported functions

State

80 state entries

Source Code

FILES
distribution.gno
go
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
12const (
13	SetDistributionBatchMaxEvent   = "SetDistributionBatchMax"
14	distributionBatchMaxDefault    = 50
15	distributionBatchMaxUpperBound = 100
16)
17
18var distributionBatchMax int
19
20type RewardDistribution struct {
21	ID             uint64
22	PoolID         uint64
23	GameWorldID    uint64
24	OperationKey   string
25	RecipientCount uint32
26	TotalAmount    int64
27	Executor       address
28}
29
30type RewardDistributionView struct {
31	ID             uint64
32	PoolKey        string
33	GameWorldID    uint64
34	OperationKey   string
35	RecipientCount uint32
36	TotalAmount    int64
37	Executor       address
38}
39
40type VaultRefund struct {
41	ID           uint64
42	PoolID       uint64
43	OperationKey string
44	SourceKey    string
45	Recipient    address
46	Amount       int64
47	Kind         string
48	Executor     address
49}
50
51func SetDistributionBatchMax(cur realm, value int) {
52	executor := accesscontrol.MustGetAdminCaller(0, cur, admin.IsAdmin)
53	assertNoAttachedCoins()
54	if value < 1 {
55		panic("limit must be at least 1")
56	}
57	if value > distributionBatchMaxUpperBound {
58		panic("distribution batch max exceeds limit")
59	}
60	if value == distributionBatchMax {
61		return
62	}
63	oldValue := distributionBatchMax
64	distributionBatchMax = value
65	chain.Emit(SetDistributionBatchMaxEvent, "executor", executor.String(), "oldValue", strconv.Itoa(oldValue), "newValue", strconv.Itoa(value))
66}
67
68func GetDistributionBatchMax() int { return distributionBatchMax }
69
70func DistributeReward(cur realm, poolKey string, operationKey, recipientCSV, ugnotAmountCSV string) uint64 {
71	executor := accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator)
72	assertNoAttachedCoins()
73	assertNewOperationKey(operationKey)
74	poolID, pool := mustGetRewardPoolByKey(poolKey)
75	if pool.Status != RewardPoolStatusLocked {
76		panic("reward pool must be locked before distribution")
77	}
78	if latestRewardDistributionID == maxUint64 {
79		panic("reward distribution id exhausted")
80	}
81	if recipientCSV == "" || ugnotAmountCSV == "" {
82		panic("payout list must not be empty")
83	}
84
85	count, total := validatePayouts(recipientCSV, ugnotAmountCSV, cur.Address(), pool.Balance)
86	if pool.DistributedTotal > maxInt64-total {
87		panic("reward pool distributed amount overflow")
88	}
89	if pool.DistributionCount == maxUint64 {
90		panic("reward pool distribution count overflow")
91	}
92
93	latestRewardDistributionID++
94	distributionID := latestRewardDistributionID
95	pool.Balance -= total
96	pool.DistributedTotal += total
97	pool.DistributionCount++
98	treeSet(rewardPools, poolID, pool)
99	treeSet(rewardDistributions, distributionID, RewardDistribution{
100		ID:             distributionID,
101		PoolID:         poolID,
102		GameWorldID:    pool.GameWorldID,
103		OperationKey:   operationKey,
104		RecipientCount: uint32(count),
105		TotalAmount:    total,
106		Executor:       executor,
107	})
108	rewardDistributionIDs.Set(operationKey, distributionID)
109	processedOperationKeys.Set(operationKey, distributionID)
110	getOrCreateIDSet(poolDistributionIDs, poolID).Set(uint64Key(distributionID), true)
111	accountedUgnotTotal -= total
112	sendPayouts(cross(cur), recipientCSV, ugnotAmountCSV)
113	return distributionID
114}
115
116func recordAndSendRefund(cur realm, poolID uint64, operationKey, sourceKey string, recipient address, amount int64, kind string, executor address) uint64 {
117	if latestVaultRefundID == maxUint64 {
118		panic("vault refund id exhausted")
119	}
120	latestVaultRefundID++
121	refundID := latestVaultRefundID
122	treeSet(vaultRefunds, refundID, VaultRefund{
123		ID:           refundID,
124		PoolID:       poolID,
125		OperationKey: operationKey,
126		SourceKey:    sourceKey,
127		Recipient:    recipient,
128		Amount:       amount,
129		Kind:         kind,
130		Executor:     executor,
131	})
132	vaultRefundIDs.Set(operationKey, refundID)
133	processedOperationKeys.Set(operationKey, refundID)
134	if poolID != 0 {
135		getOrCreateIDSet(poolRefundIDs, poolID).Set(uint64Key(refundID), true)
136	}
137	sendUgnot(cross(cur), recipient, amount)
138	return refundID
139}
140
141func validatePayouts(recipientCSV, ugnotAmountCSV string, realmAddress address, balance int64) (int, int64) {
142	count := 0
143	total := int64(0)
144	recipientStart := 0
145	amountStart := 0
146	for {
147		_, nextRecipient, recipientDone := readPayoutRecipient(recipientCSV, recipientStart, realmAddress)
148		amount, nextAmount, amountDone := readPayoutAmount(ugnotAmountCSV, amountStart)
149		if recipientDone != amountDone {
150			panic("payout list item count mismatch")
151		}
152		count++
153		if count > distributionBatchMax {
154			panic("payout list exceeds limit")
155		}
156		if total > maxInt64-amount {
157			panic("distribution amount overflow")
158		}
159		total += amount
160		if total > balance {
161			panic("distribution amount exceeds reward pool balance")
162		}
163		if recipientDone {
164			return count, total
165		}
166		recipientStart = nextRecipient
167		amountStart = nextAmount
168	}
169}
170
171func sendPayouts(cur realm, recipientCSV, ugnotAmountCSV string) {
172	bnk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
173	recipientStart := 0
174	amountStart := 0
175	for {
176		recipient, nextRecipient, recipientDone := readPayoutRecipient(recipientCSV, recipientStart, cur.Address())
177		amount, nextAmount, amountDone := readPayoutAmount(ugnotAmountCSV, amountStart)
178		if recipientDone != amountDone {
179			panic("payout list item count mismatch")
180		}
181		bnk.SendCoins(cur.Address(), recipient, chain.Coins{{"ugnot", amount}})
182		if recipientDone {
183			return
184		}
185		recipientStart = nextRecipient
186		amountStart = nextAmount
187	}
188}
189
190func readPayoutRecipient(recipientCSV string, start int, realmAddress address) (address, int, bool) {
191	value, next, done := readPayoutCSVEntry(recipientCSV, start)
192	recipient := address(value)
193	if !recipient.IsValid() || recipient == realmAddress {
194		panic("invalid payout recipient")
195	}
196	return recipient, next, done
197}
198
199func readPayoutAmount(ugnotAmountCSV string, start int) (int64, int, bool) {
200	value, next, done := readPayoutCSVEntry(ugnotAmountCSV, start)
201	amount, err := strconv.ParseInt(value, 10, 64)
202	if err != nil || amount <= 0 {
203		panic("invalid payout amount")
204	}
205	return amount, next, done
206}
207
208func readPayoutCSVEntry(csv string, start int) (string, int, bool) {
209	end := start
210	for end < len(csv) && csv[end] != ',' {
211		end++
212	}
213	if end == start {
214		panic("payout entry must not be empty")
215	}
216	if end == len(csv) {
217		return csv[start:end], 0, true
218	}
219	next := end + 1
220	if next == len(csv) {
221		panic("payout entry must not be empty")
222	}
223	return csv[start:end], next, false
224}
225
226func GetRewardDistribution(distributionID uint64) RewardDistribution {
227	value, found := treeGet(rewardDistributions, distributionID)
228	if !found {
229		panic("reward distribution not found: " + strconv.FormatUint(distributionID, 10))
230	}
231	return value.(RewardDistribution)
232}
233
234func GetLatestRewardDistributionID() uint64 { return latestRewardDistributionID }
235
236func GetRewardDistributionByOperationKey(operationKey string) RewardDistributionView {
237	assertKeyLength(operationKey, "operation key")
238	value, found := stringGet(rewardDistributionIDs, operationKey)
239	if !found {
240		panic("reward distribution not found: " + operationKey)
241	}
242	distribution := GetRewardDistribution(value.(uint64))
243	return RewardDistributionView{
244		ID:             distribution.ID,
245		PoolKey:        GetRewardPool(distribution.PoolID).PoolKey,
246		GameWorldID:    distribution.GameWorldID,
247		OperationKey:   distribution.OperationKey,
248		RecipientCount: distribution.RecipientCount,
249		TotalAmount:    distribution.TotalAmount,
250		Executor:       distribution.Executor,
251	}
252}
253
254func GetRewardDistributionCountByPool(poolID uint64) int {
255	GetRewardPool(poolID)
256	ids, found := getIDSet(poolDistributionIDs, poolID)
257	if !found {
258		return 0
259	}
260	return ids.Size()
261}
262
263func ListRewardDistributionIDsByPool(poolID uint64, page, count int) []uint64 {
264	GetRewardPool(poolID)
265	ids, _ := getIDSet(poolDistributionIDs, poolID)
266	return listIDs(ids, page, count)
267}
268
269func GetVaultRefund(refundID uint64) VaultRefund {
270	value, found := treeGet(vaultRefunds, refundID)
271	if !found {
272		panic("vault refund not found: " + strconv.FormatUint(refundID, 10))
273	}
274	return value.(VaultRefund)
275}
276
277func GetLatestVaultRefundID() uint64 { return latestVaultRefundID }
278
279func GetVaultRefundByOperationKey(operationKey string) VaultRefund {
280	assertKeyLength(operationKey, "operation key")
281	value, found := stringGet(vaultRefundIDs, operationKey)
282	if !found {
283		panic("vault refund not found: " + operationKey)
284	}
285	return GetVaultRefund(value.(uint64))
286}
287
288func GetVaultRefundCountByPool(poolID uint64) int {
289	GetRewardPool(poolID)
290	ids, found := getIDSet(poolRefundIDs, poolID)
291	if !found {
292		return 0
293	}
294	return ids.Size()
295}
296
297func ListVaultRefundIDsByPool(poolID uint64, page, count int) []uint64 {
298	GetRewardPool(poolID)
299	ids, _ := getIDSet(poolRefundIDs, poolID)
300	return listIDs(ids, page, count)
301}
302

Raw Package Data

Raw JSON data