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_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}