block.gno
8.19 Kb ยท 397 lines
1package block
2
3import (
4 "chain"
5 "chain/runtime"
6
7 "gno.land/p/nt/avl"
8 "gno.land/p/nt/ufmt"
9)
10
11const (
12 CreateBlockEvent = "CreateBlock"
13 CreateSystemBlockEvent = "CreateSystemBlock"
14 DeleteBlockEvent = "DeleteBlock"
15 DeleteSystemBlockEvent = "DeleteSystemBlock"
16 SetBlockPropertyEvent = "SetBlockProperty"
17)
18
19var (
20 blocks = avl.NewTree()
21 nextBlockID uint32 = 100000
22 typeIndex = avl.NewTree() // blockType -> map[blockID]bool
23)
24
25func CreateBlock(
26 cur realm,
27 name, blockType, layer, drawType, textureLayer, textureURL, previewURL string,
28 maxSupply, mintPrice, usePrice, installerBPS, shape, state uint32,
29 emission uint8,
30 option string,
31) uint32 {
32 if name == "" {
33 panic("name cannot be empty")
34 }
35
36 if maxSupply == 0 {
37 panic("max supply must be greater than 0")
38 }
39
40 if installerBPS > 10000 {
41 panic("installer bps must be between 0 and 10000")
42 }
43
44 caller := runtime.OriginCaller()
45
46 blockID := getNextBlockID()
47 blockIDStr := blockIDToString(blockID)
48 blockIDKey := blockIDToKey(blockID)
49
50 maxSupplyStr := ufmt.Sprintf("%d", maxSupply)
51 mintPriceStr := ufmt.Sprintf("%d", mintPrice)
52 usePriceStr := ufmt.Sprintf("%d", usePrice)
53 installerBPSStr := ufmt.Sprintf("%d", installerBPS)
54 shapeStr := ufmt.Sprintf("%d", shape)
55 stateStr := ufmt.Sprintf("%d", state)
56 emissionStr := ufmt.Sprintf("%d", emission)
57
58 properties := map[string]string{
59 "id": blockIDStr,
60 "name": name,
61 "blockType": blockType,
62 "layer": layer,
63 "drawType": drawType,
64 "textureLayer": textureLayer,
65 "textureURL": textureURL,
66 "previewURL": previewURL,
67 "maxSupply": maxSupplyStr,
68 "mintPrice": mintPriceStr,
69 "usePrice": usePriceStr,
70 "installerBps": installerBPSStr,
71 "shape": shapeStr,
72 "state": stateStr,
73 "emission": emissionStr,
74 "option": option,
75 "creator": caller.String(),
76 }
77
78 blocks.Set(blockIDKey, properties)
79
80 if blockType != "" {
81 addToTypeIndex(uint32(blockID), blockType)
82 }
83
84 chain.Emit(
85 CreateBlockEvent,
86 "id", blockIDStr,
87 "name", name,
88 "creator", caller.String(),
89 )
90
91 return blockID
92}
93
94func CreateSystemBlock(
95 cur realm,
96 blockID uint32,
97 name, blockType, layer, drawType, textureLayer, textureURL, previewURL string,
98 maxSupply int64, mintPrice, usePrice, installerBPS, shape, state uint32,
99 emission uint8,
100 option string,
101) {
102 caller := runtime.OriginCaller()
103 assertIsAdmin(caller)
104
105 if name == "" {
106 panic("name cannot be empty")
107 }
108
109 blockIDStr := blockIDToString(blockID)
110 blockIDKey := blockIDToKey(blockID)
111
112 if !isSystemBlockTid(blockID) {
113 panic("block ID must be less than 100000")
114 }
115
116 if _, found := blocks.Get(blockIDKey); found {
117 panic("block already exists: " + blockIDStr)
118 }
119
120 if maxSupply < -1 {
121 panic("max supply must be greater than -2")
122 }
123
124 if installerBPS > 10000 {
125 panic("installer bps must be between 0 and 10000")
126 }
127
128 maxSupplyStr := ufmt.Sprintf("%d", maxSupply)
129 mintPriceStr := ufmt.Sprintf("%d", mintPrice)
130 usePriceStr := ufmt.Sprintf("%d", usePrice)
131 installerBPSStr := ufmt.Sprintf("%d", installerBPS)
132 shapeStr := ufmt.Sprintf("%d", shape)
133 stateStr := ufmt.Sprintf("%d", state)
134 emissionStr := ufmt.Sprintf("%d", emission)
135
136 properties := map[string]string{
137 "id": blockIDStr,
138 "name": name,
139 "blockType": blockType,
140 "layer": layer,
141 "drawType": drawType,
142 "textureLayer": textureLayer,
143 "textureURL": textureURL,
144 "previewURL": previewURL,
145 "maxSupply": maxSupplyStr,
146 "mintPrice": mintPriceStr,
147 "usePrice": usePriceStr,
148 "installerBps": installerBPSStr,
149 "shape": shapeStr,
150 "state": stateStr,
151 "emission": emissionStr,
152 "option": option,
153 "creator": caller.String(),
154 }
155
156 blocks.Set(blockIDKey, properties)
157
158 if blockType != "" {
159 addToTypeIndex(blockID, blockType)
160 }
161
162 chain.Emit(
163 CreateSystemBlockEvent,
164 "id", blockIDStr,
165 "name", name,
166 "creator", caller.String(),
167 )
168}
169
170func DeleteBlock(cur realm, blockID uint32) {
171 caller := runtime.OriginCaller()
172 assertIsAdmin(caller)
173
174 if isSystemBlockTid(blockID) {
175 panic("cannot delete system block: " + blockIDToString(blockID))
176 }
177
178 deleteBlock(blockID)
179
180 chain.Emit(
181 DeleteBlockEvent,
182 "id", blockIDToString(blockID),
183 "caller", caller.String(),
184 )
185}
186
187func DeleteSystemBlock(cur realm, blockID uint32) {
188 caller := runtime.OriginCaller()
189 assertIsAdmin(caller)
190
191 if !isSystemBlockTid(blockID) {
192 panic("not a system block: " + blockIDToString(blockID))
193 }
194
195 deleteBlock(blockID)
196
197 chain.Emit(
198 DeleteSystemBlockEvent,
199 "id", blockIDToString(blockID),
200 "caller", caller.String(),
201 )
202}
203
204func deleteBlock(blockID uint32) {
205 blockIDKey := blockIDToKey(blockID)
206 block, found := blocks.Get(blockIDKey)
207 if !found {
208 panic("block not found: " + blockIDToString(blockID))
209 }
210
211 blockType := block.(map[string]string)["blockType"]
212 if blockType != "" {
213 removeFromTypeIndex(blockID, blockType)
214 }
215
216 blocks.Remove(blockIDKey)
217}
218
219func SetBlockProperty(cur realm, blockID uint32, key string, value string) {
220 caller := runtime.OriginCaller()
221 assertIsAdmin(caller)
222
223 if key == "id" {
224 panic("reserved key: id")
225 }
226
227 blockIDStr := blockIDToString(blockID)
228 blockIDKey := blockIDToKey(blockID)
229 block, found := blocks.Get(blockIDKey)
230 if !found {
231 panic("block not found: " + blockIDStr)
232 }
233
234 block.(map[string]string)[key] = value
235
236 chain.Emit(
237 SetBlockPropertyEvent,
238 "id", blockIDStr,
239 "key", key,
240 "value", value,
241 )
242}
243
244func GetCurrentBlockID() uint32 {
245 return nextBlockID
246}
247
248func GetTotalBlockSize() int {
249 return blocks.Size()
250}
251
252func ListBlocks(page, count int) []map[string]string {
253 if page < 1 {
254 panic("page must be at least 1")
255 }
256 if count < 1 {
257 panic("count must be at least 1")
258 }
259 if count > listLimit {
260 panic("count exceeds listLimit")
261 }
262
263 result := []map[string]string{}
264
265 offset := (page - 1) * count
266
267 blocks.IterateByOffset(offset, count, func(_ string, value interface{}) bool {
268 properties := value.(map[string]string)
269 result = append(result, properties)
270 return false
271 })
272
273 return result
274}
275
276func ListBlocksFromTo(from, to, limit uint32) []map[string]string {
277 if limit > uint32(listLimit) {
278 panic("limit exceeds listLimit")
279 }
280
281 result := []map[string]string{}
282
283 if from > to {
284 panic("from is greater than to")
285 }
286
287 size := uint32(blocks.Size())
288 if to >= size {
289 to = size - 1
290 }
291
292 for ; from <= to; from++ {
293 blockIDKey := blockIDToKey(from)
294 blockVal, found := blocks.Get(blockIDKey)
295 if !found {
296 continue
297 }
298
299 block := blockVal.(map[string]string)
300 result = append(result, block)
301
302 if len(result) >= int(limit) {
303 break
304 }
305 }
306
307 return result
308}
309
310func ListBlocksByIDs(blockIDs ...uint32) []map[string]string {
311 if len(blockIDs) > listLimit {
312 panic("blockIDs exceeds listLimit")
313 }
314
315 result := []map[string]string{}
316
317 if len(blockIDs) == 0 {
318 return result
319 }
320
321 for _, blockID := range blockIDs {
322 blockIDKey := blockIDToKey(blockID)
323 blockVal, found := blocks.Get(blockIDKey)
324 if !found {
325 continue
326 }
327
328 block := blockVal.(map[string]string)
329 result = append(result, block)
330 }
331
332 return result
333}
334
335func ListBlocksByType(blockType string) []map[string]string {
336 result := []map[string]string{}
337
338 blockIDsVal, found := typeIndex.Get(blockType)
339 if !found {
340 return result
341 }
342
343 blockIDs := blockIDsVal.(map[uint32]bool)
344
345 for blockID := range blockIDs {
346 blockIDKey := blockIDToKey(blockID)
347 blockVal, found := blocks.Get(blockIDKey)
348 if !found {
349 continue
350 }
351
352 block := blockVal.(map[string]string)
353 result = append(result, block)
354 }
355
356 return result
357}
358
359// addToTypeIndex adds a block to the type index
360func addToTypeIndex(blockID uint32, blockType string) {
361 blockIDsVal, found := typeIndex.Get(blockType)
362
363 var blockIDs map[uint32]bool
364 if found {
365 blockIDs = blockIDsVal.(map[uint32]bool)
366 } else {
367 blockIDs = make(map[uint32]bool)
368 typeIndex.Set(blockType, blockIDs)
369 }
370
371 blockIDs[blockID] = true
372}
373
374// removeFromTypeIndex removes a block from the type index
375func removeFromTypeIndex(blockID uint32, blockType string) {
376 blockIDsVal, found := typeIndex.Get(blockType)
377 if !found {
378 return
379 }
380
381 blockIDs := blockIDsVal.(map[uint32]bool)
382 delete(blockIDs, blockID)
383
384 if len(blockIDs) == 0 {
385 typeIndex.Remove(blockType)
386 }
387}
388
389func isSystemBlockTid(blockID uint32) bool {
390 return blockID < 100000
391}
392
393func getNextBlockID() uint32 {
394 id := nextBlockID
395 nextBlockID++
396 return id
397}