Transaction

21BE2134F354C5…2455DC5EDE1D

Block 747,748 · index 0 · indexed

Summary

Hash
21BE2134F354C5D3B18AD20A251C7CC2DF38D788D81E0A64AC5D2455DC5EDE1D
Block
747,748
Size
29543 bytes
Gas used
43,130,486 / 86,260,912
Fee
86261 ugnot
Status
success

Messages

#1AddPackagegno.land/r/akkadia/beta/game_world21 arguments

Arguments · 21

  1. #1game_world
  2. #2config.gno
  3. #3package game_world const gameWorldListLimit = 100
  4. #4game_world.gno
  5. #5package game_world import ( "chain" "chain/banker" "chain/runtime" "chain/runtime/unsafe" "strconv" "gno.land/p/akkadia/beta/accesscontrol" "gno.land/r/akkadia/beta/admin" ) const ( CreateGameWorldEvent = "CreateGameWorld" RestrictGameWorldEvent = "RestrictGameWorld" ) type CreationMode int const ( CreationModeInvalid CreationMode = iota CreationModeOperatorOnly CreationModeApprovedCreator CreationModePublicCreator ) type GameWorldStatus int const ( GameWorldStatusInvalid GameWorldStatus = iota GameWorldStatusExists GameWorldStatusRestricted ) type GameWorld struct { ID uint64 Owner address GameType string Status GameWorldStatus CreatedHeight int64 RestrictedHeight int64 } type GameTypePolicy struct { CreationCost int64 CreationMode CreationMode } func CreateGameWorld(cur realm, gameType string) uint64 { owner := accesscontrol.MustGetUserCaller(0, cur) policy := mustGetGameTypePolicy(gameType) assertCreationAllowed(gameType, owner, policy.CreationMode) assertExactUgnotPayment(policy.CreationCost) id := createGameWorld(owner, gameType) if policy.CreationCost > 0 { bnk := banker.NewBanker(banker.BankerTypeRealmSend, cur) bnk.SendCoins(cur.Address(), admin.GetFeeCollector(), chain.Coins{{"ugnot", policy.CreationCost}}) } emitCreateGameWorld(owner, id, gameType, policy.CreationCost) return id } func RestrictGameWorld(cur realm, gameWorldID uint64) { executor := accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator) world := GetGameWorld(gameWorldID) if world.Status == GameWorldStatusRestricted { return } world.Status = GameWorldStatusRestricted world.RestrictedHeight = runtime.ChainHeight() gameWorlds.Set(uint64Key(gameWorldID), world) removeOwnerGameWorldID(world.Owner, gameWorldID) restrictedGameWorldIDs.Set(uint64Key(gameWorldID), true) chain.Emit( RestrictGameWorldEvent, "executor", executor.String(), "id", strconv.FormatUint(gameWorldID, 10), "oldStatus", strconv.Itoa(int(GameWorldStatusExists)), "newStatus", strconv.Itoa(int(GameWorldStatusRestricted)), "restrictedHeight", strconv.FormatInt(world.RestrictedHeight, 10), ) } func HasGameWorld(gameWorldID uint64) bool { return gameWorlds.Has(uint64Key(gameWorldID)) } func GetGameWorld(gameWorldID uint64) GameWorld { value := gameWorlds.Get(uint64Key(gameWorldID)) if value == nil { panic("game world not found: " + strconv.FormatUint(gameWorldID, 10)) } return value.(GameWorld) } func GetLatestGameWorldID() uint64 { return latestGameWorldID } func GetListLimit() int { return gameWorldListLimit } func GetRestrictedGameWorldCount() int { return restrictedGameWorldIDs.Size() } func ListRestrictedGameWorldIDs(page, count int) []uint64 { if page < 1 { panic("page must be at least 1") } if count < 1 || count > gameWorldListLimit { panic("count must be between 1 and 100") } result := []uint64{} restrictedGameWorldIDs.IterateByOffset((page-1)*count, count, func(key string, _ any) bool { result = append(result, parseUint64Key(key)) return false }) return result } func ListLatestGameWorldsByOwner(owner address) []GameWorld { ids, found := getOwnerGameWorldIDs(owner) if !found { return []GameWorld{} } offset := ids.Size() - gameWorldListLimit if offset < 0 { offset = 0 } result := []GameWorld{} ids.IterateByOffset(offset, gameWorldListLimit, func(key string, _ any) bool { result = append(result, GetGameWorld(parseUint64Key(key))) return false }) return result } func createGameWorld(owner address, gameType string) uint64 { if latestGameWorldID == uint64(18446744073709551615) { panic("game world id exhausted") } latestGameWorldID++ id := latestGameWorldID gameWorlds.Set(uint64Key(id), GameWorld{ ID: id, Owner: owner, GameType: gameType, Status: GameWorldStatusExists, CreatedHeight: runtime.ChainHeight(), }) addOwnerGameWorldID(owner, id) return id } func assertCreationAllowed(gameType string, creator address, mode CreationMode) { switch mode { case CreationModeOperatorOnly: if !admin.IsAdmin(creator) && !admin.IsOperator(creator) { panic("game type creation is operator only") } case CreationModeApprovedCreator: if !isApprovedCreator(gameType, creator) { panic("creator is not approved for game type") } case CreationModePublicCreator: return default: panic("invalid creation mode") } } func assertExactUgnotPayment(expected int64) { coins := unsafe.OriginSend() if expected == 0 { if len(coins) != 0 { panic("exact payment required: expected no coins, got " + coins.String()) } return } if len(coins) != 1 || coins[0].Denom != "ugnot" || coins[0].Amount != expected { panic("exact payment required: expected " + strconv.FormatInt(expected, 10) + "ugnot, got " + coins.String()) } } func emitCreateGameWorld(owner address, id uint64, gameType string, creationCost int64) { chain.Emit( CreateGameWorldEvent, "executor", owner.String(), "id", strconv.FormatUint(id, 10), "owner", owner.String(), "gameType", gameType, "creationCost", strconv.FormatInt(creationCost, 10), "createdHeight", strconv.FormatInt(runtime.ChainHeight(), 10), ) }
  6. #6game_world_test.gno
  7. #7package game_world import ( "chain" "testing" "gno.land/p/nt/uassert/v0" ) func TestCreateGameWorldUsesCallerAsOwnerAndChargesExactCost(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 25, CreationModePublicCreator) collectorBefore := ugnotBalanceForTest(testFeeCollector) id := createGameWorldForTest(cross(cur), testCreator, testGameTypeJump, chain.Coins{{"ugnot", 25}}) uassert.Equal(t, uint64(1), id) uassert.Equal(t, uint64(1), GetLatestGameWorldID()) world := GetGameWorld(id) uassert.Equal(t, testCreator.String(), world.Owner.String()) uassert.Equal(t, testGameTypeJump, world.GameType) uassert.Equal(t, int64(25), ugnotBalanceForTest(testFeeCollector)-collectorBefore) uassert.Equal(t, int64(0), ugnotBalanceForTest(testPkgAddr)) } func TestCreateGameWorldDoesNotRequirePersonalWorld(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModePublicCreator) uassert.True(t, HasGameWorld(createGameWorldForTest(cross(cur), testNoPersonal, testGameTypeJump, chain.Coins{}))) } func TestOperatorOnlyCreationAllowsOperator(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModeOperatorOnly) uassert.True(t, HasGameWorld(createGameWorldForTest(cross(cur), testOperator, testGameTypeJump, chain.Coins{}))) } func TestApprovedCreatorFlowAndRevocation(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeRace, 0, CreationModeApprovedCreator) uassert.AbortsWithMessage(t, cur, "creator is not approved for game type", func() { createGameWorldForTest(cross(cur), testCreator, testGameTypeRace, chain.Coins{}) }) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) AddApprovedCreator(cross(cur), testGameTypeRace, testCreator) id := createGameWorldForTest(cross(cur), testCreator, testGameTypeRace, chain.Coins{}) uassert.True(t, HasGameWorld(id)) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) RemoveApprovedCreator(cross(cur), testGameTypeRace, testCreator) uassert.True(t, HasGameWorld(id)) uassert.AbortsWithMessage(t, cur, "creator is not approved for game type", func() { createGameWorldForTest(cross(cur), testCreator, testGameTypeRace, chain.Coins{}) }) } func TestCreateGameWorldRejectsInexactPayment(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeDrop, 25, CreationModePublicCreator) uassert.AbortsWithMessage(t, cur, "exact payment required: expected 25ugnot, got 24ugnot", func() { createGameWorldForTest(cross(cur), testCreator, testGameTypeDrop, chain.Coins{{"ugnot", 24}}) }) uassert.Equal(t, uint64(0), GetLatestGameWorldID()) uassert.AbortsWithMessage(t, cur, "exact payment required: expected 25ugnot, got 25ugnot,1foo", func() { createGameWorldForTest(cross(cur), testCreator, testGameTypeDrop, chain.Coins{{"ugnot", 25}, {"foo", 1}}) }) uassert.Equal(t, uint64(0), GetLatestGameWorldID()) } func TestApprovedCreatorModeDoesNotImplicitlyApproveOperator(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeRace, 0, CreationModeApprovedCreator) uassert.AbortsWithMessage(t, cur, "creator is not approved for game type", func() { createGameWorldForTest(cross(cur), testOperator, testGameTypeRace, chain.Coins{}) }) } func TestCreateGameWorldRejectsIDOverflow(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModePublicCreator) latestGameWorldID = uint64(18446744073709551615) uassert.AbortsWithMessage(t, cur, "game world id exhausted", func() { createGameWorldForTest(cross(cur), testCreator, testGameTypeJump, chain.Coins{}) }) } func TestListLatestGameWorldsByOwner(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModePublicCreator) firstID := createGameWorldForTest(cross(cur), testCreator, testGameTypeJump, chain.Coins{}) createGameWorldForTest(cross(cur), testOtherCreator, testGameTypeJump, chain.Coins{}) for i := 1; i <= gameWorldListLimit; i++ { createGameWorldForTest(cross(cur), testCreator, testGameTypeJump, chain.Coins{}) } worlds := ListLatestGameWorldsByOwner(testCreator) uassert.Equal(t, gameWorldListLimit, len(worlds)) uassert.Equal(t, firstID+2, worlds[0].ID) uassert.Equal(t, firstID+uint64(gameWorldListLimit+1), worlds[len(worlds)-1].ID) uassert.Equal(t, testGameTypeJump, worlds[0].GameType) uassert.Equal(t, 0, len(ListLatestGameWorldsByOwner(address("g1missing")))) }
  8. #8gnomod.toml
  9. #9module = "gno.land/r/akkadia/beta/game_world" gno = "0.9"
  10. #10policy.gno
  11. #11package game_world import ( "chain" "strconv" "gno.land/p/akkadia/beta/accesscontrol" "gno.land/p/nt/bptree/v0" "gno.land/r/akkadia/beta/admin" ) const ( SetGameTypePolicyEvent = "SetGameTypePolicy" AddApprovedCreatorEvent = "AddApprovedCreator" RemoveApprovedCreatorEvent = "RemoveApprovedCreator" SetApprovedCreatorsEvent = "SetApprovedCreators" approvedCreatorListMax = 100 approvedCreatorBatchMax = 100 gameTypeMaxLen = 64 ) var ( gameTypePolicies *bptree.BPTree approvedCreators *bptree.BPTree ) func init() { initializePolicyState() } func SetGameTypePolicy(cur realm, gameType string, creationCost int64, mode CreationMode) { executor := accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator) assertValidGameType(gameType) if creationCost < 0 { panic("creation cost must not be negative") } assertValidCreationMode(mode) oldValue := gameTypePolicies.Get(gameType) old := GameTypePolicy{} found := oldValue != nil if found { old = oldValue.(GameTypePolicy) } if found && old.CreationCost == creationCost && old.CreationMode == mode { return } gameTypePolicies.Set(gameType, GameTypePolicy{CreationCost: creationCost, CreationMode: mode}) chain.Emit( SetGameTypePolicyEvent, "executor", executor.String(), "gameType", gameType, "oldCreationCost", strconv.FormatInt(old.CreationCost, 10), "newCreationCost", strconv.FormatInt(creationCost, 10), "oldCreationMode", strconv.Itoa(int(old.CreationMode)), "newCreationMode", strconv.Itoa(int(mode)), ) } func AddApprovedCreator(cur realm, gameType string, creator address) { executor := accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator) mustGetGameTypePolicy(gameType) assertValidCreator(creator) if !setApprovedCreator(gameType, creator, true) { return } chain.Emit( AddApprovedCreatorEvent, "executor", executor.String(), "gameType", gameType, "creator", creator.String(), ) } func RemoveApprovedCreator(cur realm, gameType string, creator address) { executor := accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator) mustGetGameTypePolicy(gameType) assertValidCreator(creator) if !setApprovedCreator(gameType, creator, false) { return } chain.Emit( RemoveApprovedCreatorEvent, "executor", executor.String(), "gameType", gameType, "creator", creator.String(), ) } func SetApprovedCreators(cur realm, gameType, creatorsCSV string, approved bool) { executor := accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator) mustGetGameTypePolicy(gameType) if creatorsCSV == "" { panic("creator list must not be empty") } requestedCount := 0 changedCount := 0 start := 0 for { end := start for end < len(creatorsCSV) && creatorsCSV[end] != ',' { end++ } requestedCount++ if requestedCount > approvedCreatorBatchMax { panic("creator list exceeds limit") } creator := address(creatorsCSV[start:end]) assertValidCreator(creator) if setApprovedCreator(gameType, creator, approved) { changedCount++ } if end == len(creatorsCSV) { break } start = end + 1 } if changedCount == 0 { return } chain.Emit( SetApprovedCreatorsEvent, "executor", executor.String(), "gameType", gameType, "creatorsCSV", creatorsCSV, "approved", strconv.FormatBool(approved), "requestedCount", strconv.Itoa(requestedCount), "changedCount", strconv.Itoa(changedCount), ) } func setApprovedCreator(gameType string, creator address, approved bool) bool { set, found := getApprovedCreators(gameType) old := found && set.Has(creator.String()) if old == approved { return false } if approved { if !found { set = createApprovedCreators(gameType) } set.Set(creator.String(), true) } else { set.Remove(creator.String()) } return true } func IsApprovedCreator(gameType string, creator address) bool { mustGetGameTypePolicy(gameType) assertValidCreator(creator) return isApprovedCreator(gameType, creator) } func GetApprovedCreatorCount(gameType string) int { mustGetGameTypePolicy(gameType) set, found := getApprovedCreators(gameType) if !found { return 0 } return set.Size() } func ListApprovedCreators(gameType string, offset, count int) []address { mustGetGameTypePolicy(gameType) if offset < 0 { panic("offset must not be negative") } if count < 1 || count > approvedCreatorListMax { panic("count must be between 1 and 100") } result := []address{} set, found := getApprovedCreators(gameType) if !found { return result } set.IterateByOffset(offset, count, func(value string, _ any) bool { result = append(result, address(value)) return false }) return result } func HasGameTypePolicy(gameType string) bool { assertValidGameType(gameType) return gameTypePolicies.Has(gameType) } func GetGameTypePolicy(gameType string) GameTypePolicy { return mustGetGameTypePolicy(gameType) } func initializePolicyState() { gameTypePolicies = bptree.NewBPTree32() approvedCreators = bptree.NewBPTree32() } func assertValidGameType(gameType string) { if len(gameType) < 1 || len(gameType) > gameTypeMaxLen { panic("game type key length must be between 1 and 64") } for _, ch := range gameType { if (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_' && ch != '-' { panic("invalid game type key") } } } func mustGetGameTypePolicy(gameType string) GameTypePolicy { assertValidGameType(gameType) value := gameTypePolicies.Get(gameType) if value == nil { panic("game type policy not found: " + gameType) } return value.(GameTypePolicy) } func getApprovedCreators(gameType string) (*bptree.BPTree, bool) { value := approvedCreators.Get(gameType) if value == nil { return nil, false } return value.(*bptree.BPTree), true } func createApprovedCreators(gameType string) *bptree.BPTree { set := bptree.NewBPTree32() approvedCreators.Set(gameType, set) return set } func isApprovedCreator(gameType string, creator address) bool { set, found := getApprovedCreators(gameType) return found && set.Has(creator.String()) } func assertValidCreationMode(mode CreationMode) { if mode < CreationModeOperatorOnly || mode > CreationModePublicCreator { panic("invalid creation mode") } } func assertValidCreator(creator address) { if !creator.IsValid() { panic("invalid creator address") } } func resetGameWorldState() { initializePolicyState() resetGameWorldStore() }
  12. #12policy_test.gno
  13. #13package game_world import ( "testing" "gno.land/p/nt/uassert/v0" ) func TestGameTypePoliciesStartEmptyAndAdminRegistersOpaqueKey(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) uassert.False(t, HasGameTypePolicy(testGameTypeDungeon)) uassert.False(t, HasGameTypePolicy(testGameTypeJump)) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) SetGameTypePolicy(cross(cur), "custom_type_alpha", 17, CreationModeApprovedCreator) uassert.True(t, HasGameTypePolicy("custom_type_alpha")) policy := GetGameTypePolicy("custom_type_alpha") uassert.Equal(t, int64(17), policy.CreationCost) uassert.Equal(t, int(CreationModeApprovedCreator), int(policy.CreationMode)) } func TestApprovedCreatorIsManagedPerOpaqueKey(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModeApprovedCreator) setPolicyForTest(cross(cur), testGameTypeRace, 0, CreationModeApprovedCreator) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) AddApprovedCreator(cross(cur), testGameTypeJump, testCreator) uassert.True(t, IsApprovedCreator(testGameTypeJump, testCreator)) uassert.False(t, IsApprovedCreator(testGameTypeRace, testCreator)) RemoveApprovedCreator(cross(cur), testGameTypeJump, testCreator) uassert.False(t, IsApprovedCreator(testGameTypeJump, testCreator)) } func TestAddAndRemoveApprovedCreator(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModeApprovedCreator) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) AddApprovedCreator(cross(cur), testGameTypeJump, testCreator) uassert.True(t, IsApprovedCreator(testGameTypeJump, testCreator)) RemoveApprovedCreator(cross(cur), testGameTypeJump, testCreator) uassert.False(t, IsApprovedCreator(testGameTypeJump, testCreator)) } func TestSetApprovedCreatorsBulk(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModeApprovedCreator) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) creatorsCSV := testCreator.String() + "," + testOtherCreator.String() SetApprovedCreators(cross(cur), testGameTypeJump, creatorsCSV, true) uassert.True(t, IsApprovedCreator(testGameTypeJump, testCreator)) uassert.True(t, IsApprovedCreator(testGameTypeJump, testOtherCreator)) SetApprovedCreators(cross(cur), testGameTypeJump, creatorsCSV, false) uassert.False(t, IsApprovedCreator(testGameTypeJump, testCreator)) uassert.False(t, IsApprovedCreator(testGameTypeJump, testOtherCreator)) } func TestSetApprovedCreatorsRejectsInvalidAndEmptyCSV(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModeApprovedCreator) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) uassert.AbortsWithMessage(t, cur, "invalid creator address", func() { SetApprovedCreators(cross(cur), testGameTypeJump, testCreator.String()+",invalid", true) }) setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModeApprovedCreator) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) uassert.AbortsWithMessage(t, cur, "creator list must not be empty", func() { SetApprovedCreators(cross(cur), testGameTypeJump, "", true) }) } func TestSetApprovedCreatorsAllowsDuplicateAddresses(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModeApprovedCreator) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) creatorsCSV := testCreator.String() + "," + testCreator.String() SetApprovedCreators(cross(cur), testGameTypeJump, creatorsCSV, true) uassert.True(t, IsApprovedCreator(testGameTypeJump, testCreator)) uassert.Equal(t, 1, GetApprovedCreatorCount(testGameTypeJump)) SetApprovedCreators(cross(cur), testGameTypeJump, creatorsCSV, false) uassert.False(t, IsApprovedCreator(testGameTypeJump, testCreator)) } func TestSetApprovedCreatorsRejectsEmptyEntryAndOversizedBatch(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModeApprovedCreator) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) uassert.AbortsWithMessage(t, cur, "invalid creator address", func() { SetApprovedCreators(cross(cur), testGameTypeJump, testCreator.String()+",", true) }) setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModeApprovedCreator) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) creatorsCSV := testCreator.String() for i := 1; i < 101; i++ { creatorsCSV += "," + testCreator.String() } uassert.AbortsWithMessage(t, cur, "creator list exceeds limit", func() { SetApprovedCreators(cross(cur), testGameTypeJump, creatorsCSV, true) }) } func TestListApprovedCreatorsSupportsManagementProjection(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeDrop, 0, CreationModeApprovedCreator) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) AddApprovedCreator(cross(cur), testGameTypeDrop, testCreator) AddApprovedCreator(cross(cur), testGameTypeDrop, testOtherCreator) uassert.Equal(t, 2, GetApprovedCreatorCount(testGameTypeDrop)) listed := ListApprovedCreators(testGameTypeDrop, 0, 100) uassert.Equal(t, 2, len(listed)) uassert.True(t, containsApprovedCreator(listed, testCreator)) uassert.True(t, containsApprovedCreator(listed, testOtherCreator)) } func containsApprovedCreator(creators []address, target address) bool { for _, creator := range creators { if creator == target { return true } } return false } func TestSetGameTypePolicyRejectsUnauthorizedCaller(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) testing.SetOriginCaller(testCreator) testing.SetRealm(testing.NewUserRealm(testCreator)) uassert.AbortsWithMessage(t, cur, "admin or operator access required", func() { SetGameTypePolicy(cross(cur), testGameTypeJump, 10, CreationModeApprovedCreator) }) } func TestSetGameTypePolicyValidatesOpaqueKeyAndPolicy(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) uassert.AbortsWithMessage(t, cur, "game type key length must be between 1 and 64", func() { SetGameTypePolicy(cross(cur), "", 0, CreationModeOperatorOnly) }) uassert.AbortsWithMessage(t, cur, "game type key length must be between 1 and 64", func() { SetGameTypePolicy(cross(cur), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0, CreationModeOperatorOnly) }) uassert.AbortsWithMessage(t, cur, "invalid game type key", func() { SetGameTypePolicy(cross(cur), "Jump Map", 0, CreationModeOperatorOnly) }) uassert.AbortsWithMessage(t, cur, "creation cost must not be negative", func() { SetGameTypePolicy(cross(cur), testGameTypeJump, -1, CreationModeOperatorOnly) }) uassert.AbortsWithMessage(t, cur, "invalid creation mode", func() { SetGameTypePolicy(cross(cur), testGameTypeJump, 0, CreationModeInvalid) }) } func TestUnregisteredGameTypeIsRejected(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) testing.SetOriginCaller(testCreator) testing.SetRealm(testing.NewUserRealm(testCreator)) uassert.AbortsWithMessage(t, cur, "game type policy not found: unknown_type", func() { CreateGameWorld(cross(cur), "unknown_type") }) testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) uassert.AbortsWithMessage(t, cur, "game type policy not found: unknown_type", func() { AddApprovedCreator(cross(cur), "unknown_type", testCreator) }) }
  14. #14security_test.gno
  15. #15package game_world import ( "chain" "testing" "gno.land/p/nt/uassert/v0" ) func TestGameWorldInterrealmSecurity(cur realm, t *testing.T) { setupGameWorld(cross(cur), t) setPolicyForTest(cross(cur), testGameTypeJump, 0, CreationModePublicCreator) testing.SetOriginCaller(testCreator) testing.SetOriginSend(chain.Coins{}) testing.SetRealm(testing.NewCodeRealm("gno.land/r/example/game_world_relay")) uassert.AbortsWithMessage(t, cur, "contract calls not allowed: function must be called directly by user", func() { CreateGameWorld(cross(cur), testGameTypeJump) }) testing.SetOriginCaller(testAdmin) testing.SetRealm(testing.NewCodeRealm("gno.land/r/example/game_world_admin_relay")) uassert.AbortsWithMessage(t, cur, "admin or operator access required", func() { AddApprovedCreator(cross(cur), testGameTypeJump, testCreator) }) }
  16. #16setup_test.gno
  17. #17package game_world import ( "chain" "chain/banker" "testing" "gno.land/p/nt/testutils/v0" "gno.land/r/akkadia/beta/admin" ) var ( testAdmin = testutils.TestAddress("gwAdmin") testOperator = testutils.TestAddress("gwOperator") testCreator = testutils.TestAddress("gwCreator") testOtherCreator = testutils.TestAddress("gwOtherCreator") testRestricted = testutils.TestAddress("gwRestricted") testNoPersonal = testutils.TestAddress("gwNoPersonal") testFeeCollector = testutils.TestAddress("gwFeeCollector") testPkgAddr = chain.PackageAddress("gno.land/r/akkadia/beta/game_world") setupDone bool ) const ( testGameTypeDungeon = "instance_dungeon" testGameTypeJump = "jump_map" testGameTypeRace = "race_map" testGameTypeDrop = "drop_map" ) func setupGameWorld(cur realm, t *testing.T) { t.Helper() resetGameWorldState() testing.SetOriginSend(chain.Coins{}) if setupDone { return } originalAdmin := admin.GetAdmin() testing.SetOriginCaller(originalAdmin) testing.SetRealm(testing.NewUserRealm(originalAdmin)) admin.TransferAdmin(cross(cur), testAdmin) testing.SetOriginCaller(testAdmin) testing.SetRealm(testing.NewUserRealm(testAdmin)) admin.AcceptAdmin(cross(cur)) admin.SetOperator(cross(cur), testOperator) admin.SetFeeCollector(cross(cur), testFeeCollector) setupDone = true } func setPolicyForTest(cur realm, gameType string, cost int64, mode CreationMode) { testing.SetOriginCaller(testOperator) testing.SetRealm(testing.NewUserRealm(testOperator)) SetGameTypePolicy(cross(cur), gameType, cost, mode) } func createGameWorldForTest(cur realm, creator address, gameType string, coins chain.Coins) uint64 { testing.SetOriginCaller(creator) testing.SetRealm(testing.NewUserRealm(creator)) if len(coins) > 0 { testing.IssueCoins(testPkgAddr, coins) } testing.SetOriginSend(coins) return CreateGameWorld(cross(cur), gameType) } func ugnotBalanceForTest(addr address) int64 { return banker.NewReadonlyBanker().GetCoins(addr).AmountOf("ugnot") }
  18. #18store.gno
  19. #19package game_world import ( "strconv" "gno.land/p/nt/bptree/v0" ) var ( gameWorlds *bptree.BPTree = bptree.NewBPTree32() ownerGameWorldIDs *bptree.BPTree = bptree.NewBPTree32() restrictedGameWorldIDs *bptree.BPTree = bptree.NewBPTree32() latestGameWorldID uint64 ) func addOwnerGameWorldID(owner address, gameWorldID uint64) { value := ownerGameWorldIDs.Get(owner.String()) var ids *bptree.BPTree if value != nil { ids = value.(*bptree.BPTree) } else { ids = bptree.NewBPTree32() ownerGameWorldIDs.Set(owner.String(), ids) } ids.Set(uint64Key(gameWorldID), true) } func removeOwnerGameWorldID(owner address, gameWorldID uint64) { ids, found := getOwnerGameWorldIDs(owner) if !found { return } ids.Remove(uint64Key(gameWorldID)) } func getOwnerGameWorldIDs(owner address) (*bptree.BPTree, bool) { value := ownerGameWorldIDs.Get(owner.String()) if value == nil { return nil, false } return value.(*bptree.BPTree), true } func resetGameWorldStore() { gameWorlds = bptree.NewBPTree32() ownerGameWorldIDs = bptree.NewBPTree32() restrictedGameWorldIDs = bptree.NewBPTree32() latestGameWorldID = 0 } func uint64Key(id uint64) string { // Zero-pad so BPTree key order matches numeric ID order. s := strconv.FormatUint(id, 10) if len(s) >= 20 { return s } return "00000000000000000000"[len(s):] + s } func parseUint64Key(key string) uint64 { id, err := strconv.ParseUint(key, 10, 64) if err != nil { panic("invalid uint64 key: " + key) } return id }
  20. #20/gno.MemPackageType
  21. #21 MPUserAll

Result log

msg:0,success:true,log:,events:[]