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_test.gno

21.10 Kb · 578 lines
  1package craft
  2
  3import (
  4	"chain"
  5	"testing"
  6
  7	"gno.land/p/nt/uassert/v0"
  8	"gno.land/r/akkadia/beta/equipment"
  9	"gno.land/r/akkadia/beta/item"
 10)
 11
 12func TestSetRecipeCreatesAndUpdatesRecipe(cur realm, t *testing.T) {
 13	setupCraftTest(cross(cur), t)
 14	testing.SetOriginCaller(craftTest.Admin)
 15	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
 16
 17	SetRecipe(cross(cur), "0001|minecraft:iron_ingot:01", 100)
 18	uassert.True(t, HasRecipe("0001|minecraft:iron_ingot:1"))
 19	uassert.True(t, HasRecipe("0001|minecraft:iron_ingot:01"))
 20	uassert.True(t, HasRecipe("0001|0:minecraft:iron_ingot:1"))
 21	uassert.Equal(t, uint32(100), GetRecipe("0001|minecraft:iron_ingot:01"))
 22	uassert.Equal(t, 1, GetRecipeCount())
 23
 24	SetRecipe(cross(cur), "0001|0:minecraft:iron_ingot:1", 101)
 25	uassert.Equal(t, uint32(101), GetRecipe("0001|minecraft:iron_ingot:1"))
 26	uassert.Equal(t, 1, GetRecipeCount())
 27
 28	SetRecipe(cross(cur), "000A|craft-hex-a:1|craft-hex-b:2", 102)
 29	uassert.True(t, HasRecipe("000a|craft-hex-a:1|craft-hex-b:2"))
 30	uassert.Equal(t, uint32(102), GetRecipe("000A|craft-hex-a:1|craft-hex-b:2"))
 31	uassert.Equal(t, 2, GetRecipeCount())
 32}
 33
 34func TestSetRecipeRequiresDirectAdminWithoutCoins(cur realm, t *testing.T) {
 35	setupCraftTest(cross(cur), t)
 36	recipe := "0001|craft-security-item:1"
 37
 38	testing.SetOriginCaller(craftTest.Owner)
 39	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
 40	uassert.AbortsWithMessage(t, cur, "admin access required", func() {
 41		SetRecipe(cross(cur), recipe, 200)
 42	})
 43	uassert.False(t, HasRecipe(recipe))
 44
 45	testing.SetOriginCaller(craftTest.Admin)
 46	testing.SetRealm(testing.NewCodeRealm("gno.land/r/example/craft_admin_relay"))
 47	uassert.AbortsWithMessage(t, cur, "admin access required", func() {
 48		SetRecipe(cross(cur), recipe, 200)
 49	})
 50	uassert.False(t, HasRecipe(recipe))
 51
 52	testing.SetOriginCaller(craftTest.Admin)
 53	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
 54	testing.SetOriginSend(chain.Coins{{"ugnot", 1}})
 55	uassert.AbortsWithMessage(t, cur, "attached coins not allowed", func() {
 56		SetRecipe(cross(cur), recipe, 200)
 57	})
 58	testing.SetOriginSend(chain.Coins{})
 59	uassert.False(t, HasRecipe(recipe))
 60}
 61
 62func TestSetRecipesStoresMultipleRecipes(cur realm, t *testing.T) {
 63	setupCraftTest(cross(cur), t)
 64	testing.SetOriginCaller(craftTest.Admin)
 65	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
 66
 67	SetRecipes(
 68		cross(cur),
 69		"0001|craft-batch-iron:1,0002|craft-batch-gold:2",
 70		"300,301",
 71	)
 72
 73	uassert.Equal(t, uint32(300), GetRecipe("0001|craft-batch-iron:1"))
 74	uassert.Equal(t, uint32(301), GetRecipe("0002|craft-batch-gold:2"))
 75}
 76
 77func TestSetRecipesStoresOccupancyBatch(cur realm, t *testing.T) {
 78	setupCraftTest(cross(cur), t)
 79	testing.SetOriginCaller(craftTest.Admin)
 80	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
 81
 82	SetRecipes(
 83		cross(cur),
 84		"0013|test-iron:1|test-iron:2|test-stick:2,"+
 85			"0003|test-wood:2|test-stone:1,"+
 86			"0001|test-herb:3,"+
 87			"0007|test-copper:1|test-leather:2|test-cloth:1",
 88		"1101000,1101001,1101002,1101003",
 89	)
 90
 91	uassert.Equal(t, 4, GetRecipeCount())
 92	uassert.Equal(t, uint32(1101000), GetRecipe("0013|test-iron:1|test-iron:2|test-stick:2"))
 93	uassert.Equal(t, uint32(1101001), GetRecipe("0003|test-wood:2|test-stone:1"))
 94	uassert.Equal(t, uint32(1101002), GetRecipe("0001|test-herb:3"))
 95	uassert.Equal(t, uint32(1101003), GetRecipe("0007|test-copper:1|test-leather:2|test-cloth:1"))
 96	uassert.True(t, HasRecipe("0013|0:test-iron:1|1:test-iron:2|4:test-stick:2"))
 97	uassert.Equal(t, 4, len(ListRecipes(1, 20)))
 98}
 99
100func TestSetRecipesAllowsLastDuplicateToWin(cur realm, t *testing.T) {
101	setupCraftTest(cross(cur), t)
102	testing.SetOriginCaller(craftTest.Admin)
103	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
104	recipe := "0001|craft-batch-duplicate:1"
105
106	SetRecipes(cross(cur), recipe+","+recipe, "310,311")
107
108	uassert.Equal(t, uint32(311), GetRecipe(recipe))
109}
110
111func TestSetRecipesRequiresDirectAdmin(cur realm, t *testing.T) {
112	setupCraftTest(cross(cur), t)
113	recipe := "0001|craft-batch-security:1"
114
115	testing.SetOriginCaller(craftTest.Owner)
116	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
117	uassert.AbortsWithMessage(t, cur, "admin access required", func() {
118		SetRecipes(cross(cur), recipe, "320")
119	})
120	uassert.False(t, HasRecipe(recipe))
121
122	testing.SetOriginCaller(craftTest.Admin)
123	testing.SetRealm(testing.NewCodeRealm("gno.land/r/example/craft_batch_relay"))
124	uassert.AbortsWithMessage(t, cur, "admin access required", func() {
125		SetRecipes(cross(cur), recipe, "320")
126	})
127	uassert.False(t, HasRecipe(recipe))
128}
129
130func TestSetRecipesRejectsCountMismatchAndRollsBack(cur realm, t *testing.T) {
131	setupCraftTest(cross(cur), t)
132	testing.SetOriginCaller(craftTest.Admin)
133	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
134	firstRecipe := "0001|craft-mismatch-a:1"
135	secondRecipe := "0001|craft-mismatch-b:1"
136
137	uassert.AbortsWithMessage(t, cur, "recipes and item ids count mismatch", func() {
138		SetRecipes(cross(cur), firstRecipe+","+secondRecipe, "400")
139	})
140
141	uassert.False(t, HasRecipe(firstRecipe))
142	uassert.False(t, HasRecipe(secondRecipe))
143}
144
145func TestSetRecipesRejectsInvalidItemID(cur realm, t *testing.T) {
146	setupCraftTest(cross(cur), t)
147	testing.SetOriginCaller(craftTest.Admin)
148	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
149	recipe := "0001|craft-invalid-id:1"
150
151	uassert.AbortsWithMessage(t, cur, "invalid item id: invalid", func() {
152		SetRecipes(cross(cur), recipe, "invalid")
153	})
154	uassert.False(t, HasRecipe(recipe))
155}
156
157func TestSetRecipesRejectsEmptyTokensAndTrailingComma(cur realm, t *testing.T) {
158	setupCraftTest(cross(cur), t)
159	testing.SetOriginCaller(craftTest.Admin)
160	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
161	recipe := "0001|craft-empty-token:1"
162
163	uassert.AbortsWithMessage(t, cur, "empty recipe not allowed", func() {
164		SetRecipes(cross(cur), recipe+",", "410,411")
165	})
166	uassert.False(t, HasRecipe(recipe))
167
168	uassert.AbortsWithMessage(t, cur, "empty item id not allowed", func() {
169		SetRecipes(cross(cur), recipe+",0001|craft-empty-id:1", "412,")
170	})
171	uassert.False(t, HasRecipe(recipe))
172}
173
174func TestSetRecipesRejectsBatchAboveLimit(cur realm, t *testing.T) {
175	setupCraftTest(cross(cur), t)
176	testing.SetOriginCaller(craftTest.Admin)
177	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
178	recipe := "0001|craft-batch-limit:1"
179	recipesCSV := recipe
180	itemIDsCSV := "500"
181	for i := 1; i < 101; i++ {
182		recipesCSV += "," + recipe
183		itemIDsCSV += ",500"
184	}
185
186	uassert.AbortsWithMessage(t, cur, "recipes exceeds 100", func() {
187		SetRecipes(cross(cur), recipesCSV, itemIDsCSV)
188	})
189	uassert.False(t, HasRecipe(recipe))
190}
191
192func TestDeleteRecipeRemovesRecipe(cur realm, t *testing.T) {
193	setupCraftTest(cross(cur), t)
194	testing.SetOriginCaller(craftTest.Admin)
195	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
196	recipe := "0001|craft-delete-item:1"
197	SetRecipe(cross(cur), recipe, 600)
198
199	DeleteRecipe(cross(cur), "0001|craft-delete-item:01")
200	uassert.False(t, HasRecipe(recipe))
201	uassert.Equal(t, 0, GetRecipeCount())
202}
203
204func TestDeleteRecipesRemovesBatchAndRequiresAdmin(cur realm, t *testing.T) {
205	setupCraftTest(cross(cur), t)
206	testing.SetOriginCaller(craftTest.Admin)
207	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
208	firstRecipe := "0001|craft-delete-a:1"
209	secondRecipe := "0002|craft-delete-b:1"
210	SetRecipe(cross(cur), firstRecipe, 601)
211	SetRecipe(cross(cur), secondRecipe, 602)
212
213	testing.SetOriginCaller(craftTest.Owner)
214	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
215	uassert.AbortsWithMessage(t, cur, "admin access required", func() {
216		DeleteRecipes(cross(cur), firstRecipe)
217	})
218	uassert.True(t, HasRecipe(firstRecipe))
219
220	testing.SetOriginCaller(craftTest.Admin)
221	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
222	DeleteRecipes(cross(cur), firstRecipe+","+secondRecipe)
223	uassert.False(t, HasRecipe(firstRecipe))
224	uassert.False(t, HasRecipe(secondRecipe))
225}
226
227func TestDeleteRecipesRejectsMissingAndRollsBack(cur realm, t *testing.T) {
228	setupCraftTest(cross(cur), t)
229	testing.SetOriginCaller(craftTest.Admin)
230	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
231	firstRecipe := "0001|craft-delete-missing-a:1"
232	secondRecipe := "0002|craft-delete-missing-b:1"
233	SetRecipe(cross(cur), firstRecipe, 603)
234	SetRecipe(cross(cur), secondRecipe, 604)
235
236	uassert.AbortsWithMessage(t, cur, "recipe not found: 0001|0:craft-delete-missing-c:1", func() {
237		DeleteRecipes(cross(cur), firstRecipe+",0001|craft-delete-missing-c:1")
238	})
239	uassert.True(t, HasRecipe(firstRecipe))
240	uassert.True(t, HasRecipe(secondRecipe))
241}
242
243func TestDeleteRecipeRejectsMissingRecipe(cur realm, t *testing.T) {
244	setupCraftTest(cross(cur), t)
245	testing.SetOriginCaller(craftTest.Admin)
246	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
247	uassert.AbortsWithMessage(t, cur, "recipe not found: 0001|0:craft-missing-delete:1", func() {
248		DeleteRecipe(cross(cur), "0001|craft-missing-delete:1")
249	})
250}
251
252func TestListRecipesPagesAndCopiesRows(cur realm, t *testing.T) {
253	setupCraftTest(cross(cur), t)
254	testing.SetOriginCaller(craftTest.Admin)
255	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
256	SetRecipe(cross(cur), "0001|craft-list-a:1", 701)
257	SetRecipe(cross(cur), "0002|craft-list-b:1", 702)
258	SetRecipe(cross(cur), "0004|craft-list-c:1", 703)
259
260	uassert.Equal(t, 3, GetRecipeCount())
261	page1 := ListRecipes(1, 2)
262	uassert.Equal(t, 2, len(page1))
263	page1[0]["itemId"] = "hacked"
264	uassert.Equal(t, "701", ListRecipes(1, 2)[0]["itemId"])
265	page2 := ListRecipes(2, 2)
266	uassert.Equal(t, 1, len(page2))
267	uassert.Equal(t, 0, len(ListRecipes(3, 2)))
268
269	uassert.PanicsWithMessage(t, cur, "page must be at least 1", func() {
270		ListRecipes(0, 1)
271	})
272	uassert.PanicsWithMessage(t, cur, "count must be at least 1", func() {
273		ListRecipes(1, 0)
274	})
275	uassert.PanicsWithMessage(t, cur, "count exceeds listLimit (max: 100)", func() {
276		ListRecipes(1, 101)
277	})
278}
279
280func TestSetListLimit(cur realm, t *testing.T) {
281	setupCraftTest(cross(cur), t)
282	testing.SetOriginCaller(craftTest.Admin)
283	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
284	SetListLimit(cross(cur), 1)
285	uassert.Equal(t, 1, GetListLimit())
286	uassert.PanicsWithMessage(t, cur, "count exceeds listLimit (max: 1)", func() {
287		ListRecipes(1, 2)
288	})
289
290	uassert.AbortsWithMessage(t, cur, "limit must be at least 1", func() {
291		SetListLimit(cross(cur), 0)
292	})
293
294	testing.SetOriginCaller(craftTest.Owner)
295	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
296	uassert.AbortsWithMessage(t, cur, "admin access required", func() {
297		SetListLimit(cross(cur), 50)
298	})
299}
300
301func TestMustParseRecipeCanonicalizesSlotsAndCountsIngredients(cur realm, t *testing.T) {
302	canonical, ingredients := mustParseRecipe("0013|minecraft:iron_ingot:01|minecraft:iron_ingot:2|minecraft:stick:3")
303
304	uassert.Equal(t, "0013|0:minecraft:iron_ingot:1|1:minecraft:iron_ingot:2|4:minecraft:stick:3", canonical)
305	uassert.Equal(t, 3, len(ingredients))
306	uassert.Equal(t, uint(0), ingredients[0].Slot)
307	uassert.Equal(t, "minecraft:iron_ingot", ingredients[0].TokenID)
308	uassert.Equal(t, int64(1), ingredients[0].Amount)
309	uassert.Equal(t, uint(4), ingredients[2].Slot)
310	uassert.Equal(t, "minecraft:stick", ingredients[2].TokenID)
311	uassert.Equal(t, int64(3), ingredients[2].Amount)
312
313	roundTrip, _ := mustParseRecipe(canonical)
314	uassert.Equal(t, canonical, roundTrip)
315
316	hexCanonical, hexIngredients := mustParseRecipe("000A|craft-hex-a:1|craft-hex-b:2")
317	uassert.Equal(t, "000a|1:craft-hex-a:1|3:craft-hex-b:2", hexCanonical)
318	uassert.Equal(t, uint(1), hexIngredients[0].Slot)
319	uassert.Equal(t, uint(3), hexIngredients[1].Slot)
320}
321
322func TestMustParseRecipeRejectsInvalidRecipes(cur realm, t *testing.T) {
323	tests := []struct {
324		recipe, panicMsg string
325	}{
326		{"", "invalid recipe"},
327		{"0000|item:1", "invalid recipe occupancy"},
328		{"0001|item", "invalid recipe ingredient"},
329		{"0001|item:0", "ingredient amount must be positive"},
330		{"0003|item:1", "occupied slots and ingredients count mismatch"},
331		{"0001|1:item:1", "ingredient slot mismatch"},
332		{"0013|4:stick:1|0:iron:1|1:iron:1", "ingredient slot mismatch"},
333		{"0001|%zz:1", "invalid item token id"},
334	}
335
336	for _, tc := range tests {
337		recipe, panicMsg := tc.recipe, tc.panicMsg
338		uassert.PanicsWithMessage(t, cur, panicMsg, func() {
339			mustParseRecipe(recipe)
340		})
341	}
342}
343
344func TestHasRecipeReturnsFalseForInvalidAndMissingRecipes(cur realm, t *testing.T) {
345	setupCraftTest(cross(cur), t)
346	uassert.False(t, HasRecipe(""))
347	uassert.False(t, HasRecipe("not-a-recipe"))
348	uassert.False(t, HasRecipe("0001|craft-missing-query:1"))
349	uassert.PanicsWithMessage(t, cur, "recipe not found: 0001|0:craft-missing-query:1", func() {
350		GetRecipe("0001|craft-missing-query:1")
351	})
352}
353
354func TestCraftBurnsItemsAndMintsEquipment(cur realm, t *testing.T) {
355	setupCraftTest(cross(cur), t)
356	recipe := "0013|craft-success-iron:1|craft-success-iron:2|craft-success-stick:2"
357
358	testing.SetOriginCaller(craftTest.Admin)
359	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
360	SetRecipe(cross(cur), recipe, 1101000)
361	mintCraftItem(cross(cur), "craft-success-iron", 3)
362	mintCraftItem(cross(cur), "craft-success-stick", 2)
363
364	testing.SetOriginCaller(craftTest.Owner)
365	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
366	before := equipment.GetLatestTokenID()
367	collectorBefore := ugnotBalanceForTest(craftTest.FeeCollector)
368	realmBefore := ugnotBalanceForTest(craftTest.PkgAddr)
369	payCraftFee(defaultCraftFeeCoins())
370	tokenID := Craft(cross(cur), "0013|craft-success-iron:01|craft-success-iron:2|craft-success-stick:2")
371	uassert.Equal(t, before+1, tokenID)
372	uassert.Equal(t, int64(0), item.BalanceOf(craftTest.Owner, "craft-success-iron"))
373	uassert.Equal(t, int64(0), item.BalanceOf(craftTest.Owner, "craft-success-stick"))
374	uassert.Equal(t, craftTest.Owner, equipment.OwnerOf(tokenID))
375	uassert.Equal(t, "1101000", equipment.GetMetadata(tokenID)["definitionId"])
376	uassert.Equal(t, defaultCraftFee, ugnotBalanceForTest(craftTest.FeeCollector)-collectorBefore)
377	uassert.Equal(t, realmBefore, ugnotBalanceForTest(craftTest.PkgAddr))
378}
379
380func TestCraftAllocatesSequentialEquipmentTokenIDs(cur realm, t *testing.T) {
381	setupCraftTest(cross(cur), t)
382	recipe := "0001|craft-skip-id:1"
383
384	testing.SetOriginCaller(craftTest.Admin)
385	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
386	SetRecipe(cross(cur), recipe, 1101003)
387	mintCraftItem(cross(cur), "craft-skip-id", 2)
388	before := equipment.GetLatestTokenID()
389	equipment.Mint(cross(cur), craftTest.Other, 1101998, "", "")
390
391	testing.SetOriginCaller(craftTest.Owner)
392	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
393	payCraftFee(defaultCraftFeeCoins())
394	tokenID := Craft(cross(cur), recipe)
395	uassert.Equal(t, before+2, tokenID)
396	uassert.Equal(t, craftTest.Other, equipment.OwnerOf(before+1))
397	uassert.Equal(t, craftTest.Owner, equipment.OwnerOf(tokenID))
398}
399
400func TestCraftRequiresEquipmentMinterRealm(cur realm, t *testing.T) {
401	setupCraftTest(cross(cur), t)
402	recipe := "0001|craft-mint-rollback:2"
403
404	testing.SetOriginCaller(craftTest.Admin)
405	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
406	SetRecipe(cross(cur), recipe, 1101001)
407	mintCraftItem(cross(cur), "craft-mint-rollback", 2)
408	equipment.UnsetMinterRealm(cross(cur), "gno.land/r/akkadia/beta/craft")
409
410	testing.SetOriginCaller(craftTest.Owner)
411	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
412	uassert.AbortsWithMessage(t, cur, "minter realm required", func() {
413		payCraftFee(defaultCraftFeeCoins())
414		Craft(cross(cur), recipe)
415	})
416}
417
418func TestCraftRequiresItemMinterRealm(cur realm, t *testing.T) {
419	setupCraftTest(cross(cur), t)
420	recipe := "0001|craft-item-minter:1"
421
422	testing.SetOriginCaller(craftTest.Admin)
423	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
424	SetRecipe(cross(cur), recipe, 1101005)
425	mintCraftItem(cross(cur), "craft-item-minter", 1)
426	item.UnsetMinterRealm(cross(cur), "gno.land/r/akkadia/beta/craft")
427
428	testing.SetOriginCaller(craftTest.Owner)
429	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
430	uassert.AbortsWithMessage(t, cur, "minter realm required", func() {
431		payCraftFee(defaultCraftFeeCoins())
432		Craft(cross(cur), recipe)
433	})
434
435	uassert.Equal(t, int64(1), item.BalanceOf(craftTest.Owner, "craft-item-minter"))
436}
437
438func TestCraftRejectsUnknownRecipeAndInsufficientItems(cur realm, t *testing.T) {
439	setupCraftTest(cross(cur), t)
440	recipe := "0001|craft-fail-item:2"
441
442	testing.SetOriginCaller(craftTest.Owner)
443	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
444	uassert.AbortsWithMessage(t, cur, "recipe not found: 0001|0:craft-fail-item:2", func() {
445		Craft(cross(cur), recipe)
446	})
447	uassert.AbortsWithMessage(t, cur, "invalid recipe", func() {
448		Craft(cross(cur), "bad")
449	})
450
451	testing.SetOriginCaller(craftTest.Admin)
452	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
453	SetRecipe(cross(cur), recipe, 1101004)
454	mintCraftItem(cross(cur), "craft-fail-item", 1)
455
456	testing.SetOriginCaller(craftTest.Owner)
457	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
458	uassert.AbortsWithMessage(t, cur, "item burn amount exceeds supply", func() {
459		payCraftFee(defaultCraftFeeCoins())
460		Craft(cross(cur), recipe)
461	})
462	uassert.Equal(t, int64(1), item.BalanceOf(craftTest.Owner, "craft-fail-item"))
463}
464
465func TestCraftRequiresDirectUserAndExactFee(cur realm, t *testing.T) {
466	setupCraftTest(cross(cur), t)
467	recipe := "0001|craft-craft-security:1"
468	testing.SetOriginCaller(craftTest.Admin)
469	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
470	SetRecipe(cross(cur), recipe, 1101002)
471	before := equipment.GetLatestTokenID()
472
473	testing.SetOriginCaller(craftTest.Owner)
474	testing.SetRealm(testing.NewCodeRealm("gno.land/r/example/craft_craft_relay"))
475	uassert.AbortsWithMessage(t, cur, "contract calls not allowed: function must be called directly by user", func() {
476		Craft(cross(cur), recipe)
477	})
478
479	testing.SetOriginCaller(craftTest.Owner)
480	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
481	uassert.AbortsWithMessage(t, cur, "exact payment required: expected 1000000ugnot, got ", func() {
482		Craft(cross(cur), recipe)
483	})
484	uassert.AbortsWithMessage(t, cur, "exact payment required: expected 1000000ugnot, got 1ugnot", func() {
485		payCraftFee(chain.Coins{{"ugnot", 1}})
486		Craft(cross(cur), recipe)
487	})
488	testing.SetOriginSend(chain.Coins{})
489	uassert.Equal(t, before, equipment.GetLatestTokenID())
490}
491
492func TestCraftRejectsInexactPayment(cur realm, t *testing.T) {
493	setupCraftTest(cross(cur), t)
494	recipe := "0001|craft-inexact-fee:1"
495	testing.SetOriginCaller(craftTest.Admin)
496	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
497	SetRecipe(cross(cur), recipe, 1101006)
498	mintCraftItem(cross(cur), "craft-inexact-fee", 3)
499	before := equipment.GetLatestTokenID()
500
501	testing.SetOriginCaller(craftTest.Owner)
502	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
503	uassert.AbortsWithMessage(t, cur, "exact payment required: expected 1000000ugnot, got 999999ugnot", func() {
504		payCraftFee(chain.Coins{{"ugnot", 999_999}})
505		Craft(cross(cur), recipe)
506	})
507	uassert.AbortsWithMessage(t, cur, "exact payment required: expected 1000000ugnot, got 1000001ugnot", func() {
508		payCraftFee(chain.Coins{{"ugnot", 1_000_001}})
509		Craft(cross(cur), recipe)
510	})
511	uassert.AbortsWithMessage(t, cur, "exact payment required: expected 1000000ugnot, got 1000000ugnot,1foo", func() {
512		payCraftFee(chain.Coins{{"ugnot", defaultCraftFee}, {"foo", 1}})
513		Craft(cross(cur), recipe)
514	})
515	uassert.Equal(t, before, equipment.GetLatestTokenID())
516	uassert.Equal(t, int64(3), item.BalanceOf(craftTest.Owner, "craft-inexact-fee"))
517}
518
519func TestSetCraftFeeUpdatesFeeAndAllowsFreeCraft(cur realm, t *testing.T) {
520	setupCraftTest(cross(cur), t)
521	uassert.Equal(t, defaultCraftFee, GetCraftFee())
522
523	testing.SetOriginCaller(craftTest.Admin)
524	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
525	SetCraftFee(cross(cur), 2_000_000)
526	uassert.Equal(t, int64(2_000_000), GetCraftFee())
527	SetCraftFee(cross(cur), 0)
528	uassert.Equal(t, int64(0), GetCraftFee())
529
530	recipe := "0001|craft-free-fee:1"
531	SetRecipe(cross(cur), recipe, 1101007)
532	mintCraftItem(cross(cur), "craft-free-fee", 1)
533
534	testing.SetOriginCaller(craftTest.Owner)
535	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
536	uassert.AbortsWithMessage(t, cur, "exact payment required: expected no coins, got 1ugnot", func() {
537		payCraftFee(chain.Coins{{"ugnot", 1}})
538		Craft(cross(cur), recipe)
539	})
540	collectorBefore := ugnotBalanceForTest(craftTest.FeeCollector)
541	payCraftFee(chain.Coins{})
542	tokenID := Craft(cross(cur), recipe)
543	uassert.Equal(t, craftTest.Owner, equipment.OwnerOf(tokenID))
544	uassert.Equal(t, collectorBefore, ugnotBalanceForTest(craftTest.FeeCollector))
545}
546
547func TestSetCraftFeeRequiresDirectAdminWithoutCoins(cur realm, t *testing.T) {
548	setupCraftTest(cross(cur), t)
549	uassert.Equal(t, defaultCraftFee, GetCraftFee())
550
551	testing.SetOriginCaller(craftTest.Owner)
552	testing.SetRealm(testing.NewUserRealm(craftTest.Owner))
553	uassert.AbortsWithMessage(t, cur, "admin access required", func() {
554		SetCraftFee(cross(cur), 50)
555	})
556	uassert.Equal(t, defaultCraftFee, GetCraftFee())
557
558	testing.SetOriginCaller(craftTest.Admin)
559	testing.SetRealm(testing.NewCodeRealm("gno.land/r/example/craft_fee_relay"))
560	uassert.AbortsWithMessage(t, cur, "admin access required", func() {
561		SetCraftFee(cross(cur), 50)
562	})
563	uassert.Equal(t, defaultCraftFee, GetCraftFee())
564
565	testing.SetOriginCaller(craftTest.Admin)
566	testing.SetRealm(testing.NewUserRealm(craftTest.Admin))
567	testing.SetOriginSend(chain.Coins{{"ugnot", 1}})
568	uassert.AbortsWithMessage(t, cur, "attached coins not allowed", func() {
569		SetCraftFee(cross(cur), 50)
570	})
571	testing.SetOriginSend(chain.Coins{})
572	uassert.Equal(t, defaultCraftFee, GetCraftFee())
573
574	uassert.AbortsWithMessage(t, cur, "fee must be non-negative", func() {
575		SetCraftFee(cross(cur), -1)
576	})
577	uassert.Equal(t, defaultCraftFee, GetCraftFee())
578}