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

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}