package block import ( "chain" "chain/runtime" "gno.land/p/nt/avl" "gno.land/p/nt/ufmt" ) const ( CreateBlockEvent = "CreateBlock" CreateSystemBlockEvent = "CreateSystemBlock" DeleteBlockEvent = "DeleteBlock" DeleteSystemBlockEvent = "DeleteSystemBlock" SetBlockPropertyEvent = "SetBlockProperty" ) var ( blocks = avl.NewTree() nextBlockID uint32 = 100000 typeIndex = avl.NewTree() // blockType -> map[blockID]bool ) func CreateBlock( cur realm, name, blockType, layer, drawType, textureLayer, textureURL, previewURL string, maxSupply, mintPrice, usePrice, installerBPS, shape, state uint32, emission uint8, option string, ) uint32 { if name == "" { panic("name cannot be empty") } if maxSupply == 0 { panic("max supply must be greater than 0") } if installerBPS > 10000 { panic("installer bps must be between 0 and 10000") } caller := runtime.OriginCaller() blockID := getNextBlockID() blockIDStr := blockIDToString(blockID) blockIDKey := blockIDToKey(blockID) maxSupplyStr := ufmt.Sprintf("%d", maxSupply) mintPriceStr := ufmt.Sprintf("%d", mintPrice) usePriceStr := ufmt.Sprintf("%d", usePrice) installerBPSStr := ufmt.Sprintf("%d", installerBPS) shapeStr := ufmt.Sprintf("%d", shape) stateStr := ufmt.Sprintf("%d", state) emissionStr := ufmt.Sprintf("%d", emission) properties := map[string]string{ "id": blockIDStr, "name": name, "blockType": blockType, "layer": layer, "drawType": drawType, "textureLayer": textureLayer, "textureURL": textureURL, "previewURL": previewURL, "maxSupply": maxSupplyStr, "mintPrice": mintPriceStr, "usePrice": usePriceStr, "installerBps": installerBPSStr, "shape": shapeStr, "state": stateStr, "emission": emissionStr, "option": option, "creator": caller.String(), } blocks.Set(blockIDKey, properties) if blockType != "" { addToTypeIndex(uint32(blockID), blockType) } chain.Emit( CreateBlockEvent, "id", blockIDStr, "name", name, "creator", caller.String(), ) return blockID } func CreateSystemBlock( cur realm, blockID uint32, name, blockType, layer, drawType, textureLayer, textureURL, previewURL string, maxSupply int64, mintPrice, usePrice, installerBPS, shape, state uint32, emission uint8, option string, ) { caller := runtime.OriginCaller() assertIsAdmin(caller) if name == "" { panic("name cannot be empty") } blockIDStr := blockIDToString(blockID) blockIDKey := blockIDToKey(blockID) if !isSystemBlockTid(blockID) { panic("block ID must be less than 100000") } if _, found := blocks.Get(blockIDKey); found { panic("block already exists: " + blockIDStr) } if maxSupply < -1 { panic("max supply must be greater than -2") } if installerBPS > 10000 { panic("installer bps must be between 0 and 10000") } maxSupplyStr := ufmt.Sprintf("%d", maxSupply) mintPriceStr := ufmt.Sprintf("%d", mintPrice) usePriceStr := ufmt.Sprintf("%d", usePrice) installerBPSStr := ufmt.Sprintf("%d", installerBPS) shapeStr := ufmt.Sprintf("%d", shape) stateStr := ufmt.Sprintf("%d", state) emissionStr := ufmt.Sprintf("%d", emission) properties := map[string]string{ "id": blockIDStr, "name": name, "blockType": blockType, "layer": layer, "drawType": drawType, "textureLayer": textureLayer, "textureURL": textureURL, "previewURL": previewURL, "maxSupply": maxSupplyStr, "mintPrice": mintPriceStr, "usePrice": usePriceStr, "installerBps": installerBPSStr, "shape": shapeStr, "state": stateStr, "emission": emissionStr, "option": option, "creator": caller.String(), } blocks.Set(blockIDKey, properties) if blockType != "" { addToTypeIndex(blockID, blockType) } chain.Emit( CreateSystemBlockEvent, "id", blockIDStr, "name", name, "creator", caller.String(), ) } func DeleteBlock(cur realm, blockID uint32) { caller := runtime.OriginCaller() assertIsAdmin(caller) if isSystemBlockTid(blockID) { panic("cannot delete system block: " + blockIDToString(blockID)) } deleteBlock(blockID) chain.Emit( DeleteBlockEvent, "id", blockIDToString(blockID), "caller", caller.String(), ) } func DeleteSystemBlock(cur realm, blockID uint32) { caller := runtime.OriginCaller() assertIsAdmin(caller) if !isSystemBlockTid(blockID) { panic("not a system block: " + blockIDToString(blockID)) } deleteBlock(blockID) chain.Emit( DeleteSystemBlockEvent, "id", blockIDToString(blockID), "caller", caller.String(), ) } func deleteBlock(blockID uint32) { blockIDKey := blockIDToKey(blockID) block, found := blocks.Get(blockIDKey) if !found { panic("block not found: " + blockIDToString(blockID)) } blockType := block.(map[string]string)["blockType"] if blockType != "" { removeFromTypeIndex(blockID, blockType) } blocks.Remove(blockIDKey) } func SetBlockProperty(cur realm, blockID uint32, key string, value string) { caller := runtime.OriginCaller() assertIsAdmin(caller) if key == "id" { panic("reserved key: id") } blockIDStr := blockIDToString(blockID) blockIDKey := blockIDToKey(blockID) block, found := blocks.Get(blockIDKey) if !found { panic("block not found: " + blockIDStr) } block.(map[string]string)[key] = value chain.Emit( SetBlockPropertyEvent, "id", blockIDStr, "key", key, "value", value, ) } func GetCurrentBlockID() uint32 { return nextBlockID } func GetTotalBlockSize() int { return blocks.Size() } func ListBlocks(page, count int) []map[string]string { if page < 1 { panic("page must be at least 1") } if count < 1 { panic("count must be at least 1") } if count > listLimit { panic("count exceeds listLimit") } result := []map[string]string{} offset := (page - 1) * count blocks.IterateByOffset(offset, count, func(_ string, value interface{}) bool { properties := value.(map[string]string) result = append(result, properties) return false }) return result } func ListBlocksFromTo(from, to, limit uint32) []map[string]string { if limit > uint32(listLimit) { panic("limit exceeds listLimit") } result := []map[string]string{} if from > to { panic("from is greater than to") } size := uint32(blocks.Size()) if to >= size { to = size - 1 } for ; from <= to; from++ { blockIDKey := blockIDToKey(from) blockVal, found := blocks.Get(blockIDKey) if !found { continue } block := blockVal.(map[string]string) result = append(result, block) if len(result) >= int(limit) { break } } return result } func ListBlocksByIDs(blockIDs ...uint32) []map[string]string { if len(blockIDs) > listLimit { panic("blockIDs exceeds listLimit") } result := []map[string]string{} if len(blockIDs) == 0 { return result } for _, blockID := range blockIDs { blockIDKey := blockIDToKey(blockID) blockVal, found := blocks.Get(blockIDKey) if !found { continue } block := blockVal.(map[string]string) result = append(result, block) } return result } func ListBlocksByType(blockType string) []map[string]string { result := []map[string]string{} blockIDsVal, found := typeIndex.Get(blockType) if !found { return result } blockIDs := blockIDsVal.(map[uint32]bool) for blockID := range blockIDs { blockIDKey := blockIDToKey(blockID) blockVal, found := blocks.Get(blockIDKey) if !found { continue } block := blockVal.(map[string]string) result = append(result, block) } return result } // addToTypeIndex adds a block to the type index func addToTypeIndex(blockID uint32, blockType string) { blockIDsVal, found := typeIndex.Get(blockType) var blockIDs map[uint32]bool if found { blockIDs = blockIDsVal.(map[uint32]bool) } else { blockIDs = make(map[uint32]bool) typeIndex.Set(blockType, blockIDs) } blockIDs[blockID] = true } // removeFromTypeIndex removes a block from the type index func removeFromTypeIndex(blockID uint32, blockType string) { blockIDsVal, found := typeIndex.Get(blockType) if !found { return } blockIDs := blockIDsVal.(map[uint32]bool) delete(blockIDs, blockID) if len(blockIDs) == 0 { typeIndex.Remove(blockType) } } func isSystemBlockTid(blockID uint32) bool { return blockID < 100000 } func getNextBlockID() uint32 { id := nextBlockID nextBlockID++ return id }