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.

craft.gno

5.54 Kb · 221 lines
  1package craft
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"chain/runtime/unsafe"
  7	"strconv"
  8
  9	"gno.land/p/akkadia/beta/accesscontrol"
 10	"gno.land/p/nt/bptree/v0"
 11	"gno.land/r/akkadia/beta/admin"
 12	"gno.land/r/akkadia/beta/equipment"
 13	"gno.land/r/akkadia/beta/item"
 14)
 15
 16const (
 17	SetRecipeEvent     = "SetRecipe"
 18	DeleteRecipeEvent  = "DeleteRecipe"
 19	DeleteRecipesEvent = "DeleteRecipes"
 20	SetListLimitEvent  = "SetListLimit"
 21	SetCraftFeeEvent   = "SetCraftFee"
 22	CraftEvent         = "Craft"
 23)
 24
 25var (
 26	recipes   = bptree.NewBPTree32()
 27	listLimit = defaultRecipeListLimit
 28	craftFee  = defaultCraftFee
 29)
 30
 31func init() { resetCraftState() }
 32
 33func resetCraftState() {
 34	recipes = bptree.NewBPTree32()
 35	listLimit = defaultRecipeListLimit
 36	craftFee = defaultCraftFee
 37}
 38
 39// SetRecipe stores the item definition ID produced by a canonical recipe.
 40func SetRecipe(cur realm, recipe string, itemID uint32) {
 41	accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
 42	assertCraftNoCoins()
 43	canonicalRecipe, _ := mustParseRecipe(recipe)
 44	setRecipe(canonicalRecipe, itemID)
 45}
 46
 47// SetRecipes stores up to 100 canonical recipe and item ID pairs in one transaction.
 48func SetRecipes(cur realm, recipesCSV, itemIDsCSV string) {
 49	accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
 50	assertCraftNoCoins()
 51	canonicalRecipes, itemIDs := mustParseRecipeCSV(recipesCSV, itemIDsCSV)
 52	for i, recipe := range canonicalRecipes {
 53		setRecipe(recipe, itemIDs[i])
 54	}
 55}
 56
 57func setRecipe(canonicalRecipe string, itemID uint32) {
 58	recipes.Set(canonicalRecipe, itemID)
 59	chain.Emit(
 60		SetRecipeEvent,
 61		"recipe", canonicalRecipe,
 62		"itemId", strconv.FormatUint(uint64(itemID), 10),
 63	)
 64}
 65
 66func DeleteRecipe(cur realm, recipe string) {
 67	accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
 68	assertCraftNoCoins()
 69	canonicalRecipe := mustCanonicalRecipe(recipe)
 70	deleteRecipe(canonicalRecipe)
 71	chain.Emit(DeleteRecipeEvent, "recipe", canonicalRecipe)
 72}
 73
 74func DeleteRecipes(cur realm, recipesCSV string) {
 75	accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
 76	assertCraftNoCoins()
 77	canonicalRecipes := mustParseRecipeKeyCSV(recipesCSV)
 78	for _, recipe := range canonicalRecipes {
 79		if !recipes.Has(recipe) {
 80			panic("recipe not found: " + recipe)
 81		}
 82	}
 83	for _, recipe := range canonicalRecipes {
 84		deleteRecipe(recipe)
 85	}
 86	chain.Emit(DeleteRecipesEvent, "count", strconv.Itoa(len(canonicalRecipes)))
 87}
 88
 89func deleteRecipe(canonicalRecipe string) {
 90	if _, removed := recipes.Remove(canonicalRecipe); !removed {
 91		panic("recipe not found: " + canonicalRecipe)
 92	}
 93}
 94
 95func HasRecipe(recipe string) bool {
 96	canonical, _, errMsg := parseRecipe(recipe)
 97	if errMsg != "" {
 98		return false
 99	}
100	return recipes.Has(canonical)
101}
102
103func GetRecipe(recipe string) uint32 {
104	return mustGetRecipeItemID(mustCanonicalRecipe(recipe))
105}
106
107func GetRecipeCount() int {
108	return recipes.Size()
109}
110
111func ListRecipes(page, count int) []map[string]string {
112	assertRecipeListPageCount(page, count)
113	result := []map[string]string{}
114	recipes.IterateByOffset((page-1)*count, count, func(recipe string, value any) bool {
115		result = append(result, map[string]string{
116			"recipe": recipe,
117			"itemId": strconv.FormatUint(uint64(value.(uint32)), 10),
118		})
119		return false
120	})
121	return result
122}
123
124func SetListLimit(cur realm, limit int) {
125	accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
126	assertCraftNoCoins()
127	if limit < 1 {
128		panic("limit must be at least 1")
129	}
130
131	oldLimit := listLimit
132	listLimit = limit
133	chain.Emit(
134		SetListLimitEvent,
135		"oldLimit", strconv.Itoa(oldLimit),
136		"newLimit", strconv.Itoa(limit),
137	)
138}
139
140func GetListLimit() int {
141	return listLimit
142}
143
144func SetCraftFee(cur realm, fee int64) {
145	accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
146	assertCraftNoCoins()
147	if fee < 0 {
148		panic("fee must be non-negative")
149	}
150
151	oldFee := craftFee
152	craftFee = fee
153	chain.Emit(
154		SetCraftFeeEvent,
155		"oldFee", strconv.FormatInt(oldFee, 10),
156		"newFee", strconv.FormatInt(fee, 10),
157	)
158}
159
160func GetCraftFee() int64 {
161	return craftFee
162}
163
164// Craft burns a canonical recipe and mints the next GRC721 equipment token.
165func Craft(cur realm, recipe string) uint64 {
166	caller := accesscontrol.MustGetUserCaller(0, cur)
167	canonicalRecipe, ingredients := mustParseRecipe(recipe)
168	itemID := mustGetRecipeItemID(canonicalRecipe)
169	assertExactUgnotPayment(craftFee)
170
171	for _, ingredient := range ingredients {
172		item.BurnFromRealm(cross(cur), caller, ingredient.TokenID, ingredient.Amount)
173	}
174	tokenID := equipment.MintFromRealm(cross(cur), caller, itemID, "", "")
175	if craftFee > 0 {
176		bnk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
177		bnk.SendCoins(cur.Address(), admin.GetFeeCollector(), chain.Coins{{"ugnot", craftFee}})
178	}
179
180	chain.Emit(
181		CraftEvent,
182		"owner", caller.String(),
183		"recipe", canonicalRecipe,
184		"equipmentTokenId", strconv.FormatUint(tokenID, 10),
185		"itemId", strconv.FormatUint(uint64(itemID), 10),
186		"fee", strconv.FormatInt(craftFee, 10),
187	)
188	return tokenID
189}
190
191func mustCanonicalRecipe(recipe string) string {
192	canonical, _ := mustParseRecipe(recipe)
193	return canonical
194}
195
196func mustGetRecipeItemID(recipe string) uint32 {
197	value := recipes.Get(recipe)
198	if value == nil {
199		panic("recipe not found: " + recipe)
200	}
201	return value.(uint32)
202}
203
204func assertCraftNoCoins() {
205	if len(unsafe.OriginSend()) != 0 {
206		panic("attached coins not allowed")
207	}
208}
209
210func assertExactUgnotPayment(expected int64) {
211	coins := unsafe.OriginSend()
212	if expected == 0 {
213		if len(coins) != 0 {
214			panic("exact payment required: expected no coins, got " + coins.String())
215		}
216		return
217	}
218	if len(coins) != 1 || coins[0].Denom != "ugnot" || coins[0].Amount != expected {
219		panic("exact payment required: expected " + strconv.FormatInt(expected, 10) + "ugnot, got " + coins.String())
220	}
221}