package chunk import ( "chain" "chain/runtime" "strconv" "strings" "gno.land/p/demo/tokens/grc721" ) var ( // Chunk authorization: tid -> address -> role name -> bool chunkAuthorization = make(map[grc721.TokenID]map[address]map[string]bool) ) // ==================== Role Grant/Revoke ==================== // canGrant checks if an address can grant a specific role for a chunk // Owner can always grant any role // Users with roles that have grantable permissions can grant those roles func canGrant(tid grc721.TokenID, addr address, role string) bool { // Owner can always grant any role owner := OwnerOf(tid) if owner == addr { return true } // Get user's roles for this chunk if _, found := chunkAuthorization[tid]; !found { return false } if _, found := chunkAuthorization[tid][addr]; !found { return false } // Check if any of user's roles can grant the target role for userRole := range chunkAuthorization[tid][addr] { if grantables, found := grantableMap[userRole]; found { if grantables[role] { return true } } } return false } // GrantRole assigns a role to a user for a specific chunk // Owner, admin, or users with grantable permission can grant roles func GrantRole(cur realm, tid grc721.TokenID, roleName string, user address) { caller := runtime.PreviousRealm().Address() // Check grant permission if getAdmin() != caller && !canGrant(tid, caller, roleName) { panic("cannot grant role: caller lacks permission to assign role '" + roleName + "'") } // Check if role exists roleInfo, found := roleMap[roleName] if !found { panic("role not found: " + roleName) } // Initialize maps if needed if _, found := chunkAuthorization[tid]; !found { chunkAuthorization[tid] = make(map[address]map[string]bool) } if _, found := chunkAuthorization[tid][user]; !found { chunkAuthorization[tid][user] = make(map[string]bool) } // Check if user already has this role if chunkAuthorization[tid][user][roleName] { panic("user " + user.String() + " already has role '" + roleName + "'") } // Check MaxAssign limit (0 = unlimited) maxAssign, _ := strconv.Atoi(roleInfo["maxAssign"]) if maxAssign > 0 { currentCount := 0 for addr := range chunkAuthorization[tid] { if chunkAuthorization[tid][addr][roleName] { currentCount++ } } if currentCount >= maxAssign { panic("role assignment limit reached") } } // Grant the role chunkAuthorization[tid][user][roleName] = true chain.Emit( GrantRoleEvent, "tid", string(tid), "role", roleName, "user", user.String(), ) } // RevokeRole removes a role from a user for a specific chunk // Owner, admin, or users with grantable permission can revoke roles func RevokeRole(cur realm, tid grc721.TokenID, roleName string, user address) { caller := runtime.PreviousRealm().Address() // Check revoke permission (same as grant permission) if getAdmin() != caller && !canGrant(tid, caller, roleName) { panic("cannot revoke role: caller lacks permission to revoke role '" + roleName + "'") } // Check if role exists if _, found := roleMap[roleName]; !found { panic("role not found: " + roleName) } // Check if user has the role if _, found := chunkAuthorization[tid]; !found { panic("user " + user.String() + " does not have role '" + roleName + "'") } if _, found := chunkAuthorization[tid][user]; !found { panic("user " + user.String() + " does not have role '" + roleName + "'") } if !chunkAuthorization[tid][user][roleName] { panic("user " + user.String() + " does not have role '" + roleName + "'") } // Revoke the role delete(chunkAuthorization[tid][user], roleName) // Clean up empty maps if len(chunkAuthorization[tid][user]) == 0 { delete(chunkAuthorization[tid], user) } if len(chunkAuthorization[tid]) == 0 { delete(chunkAuthorization, tid) } chain.Emit( RevokeRoleEvent, "tid", string(tid), "role", roleName, "user", user.String(), ) } // ==================== Permission Check ==================== // HasPermission checks if a user has a specific permission for a chunk // Owner always has all permissions func HasPermission(tid grc721.TokenID, user address, permission string) bool { // Admin has all permissions if getAdmin() == user { return true } // Master has all permissions for the world worldID := getWorldIDFromTokenID(tid) if IsMaster(worldID, user) { return true } // Owner has all permissions owner := OwnerOf(tid) if owner == user { return true } // Check user's roles for the permission if _, found := chunkAuthorization[tid]; !found { return false } if _, found := chunkAuthorization[tid][user]; !found { return false } for roleName := range chunkAuthorization[tid][user] { if perms, found := rolePermissions[roleName]; found { if perms[permission] { return true } } } return false } // HasInstallPermission checks if caller has permission to install blocks in the chunk func HasInstallPermission(tid grc721.TokenID, caller address) bool { return HasPermission(tid, caller, "block:install") } // HasUninstallPermission checks if caller has permission to uninstall blocks from the chunk func HasUninstallPermission(tid grc721.TokenID, caller address) bool { return HasPermission(tid, caller, "block:uninstall") } // HasRole checks if a user has a specific role for a chunk func HasRole(tid grc721.TokenID, user address, roleName string) bool { if _, found := chunkAuthorization[tid]; !found { return false } if _, found := chunkAuthorization[tid][user]; !found { return false } return chunkAuthorization[tid][user][roleName] } // ==================== Query Functions ==================== // ListUserRoles returns all roles assigned to a user for a specific chunk func ListUserRoles(tid grc721.TokenID, user address) []map[string]string { result := []map[string]string{} if _, found := chunkAuthorization[tid]; !found { return result } if _, found := chunkAuthorization[tid][user]; !found { return result } for roleName := range chunkAuthorization[tid][user] { if role, found := roleMap[roleName]; found { permList := []string{} if perms, ok := rolePermissions[roleName]; ok { for perm := range perms { permList = append(permList, perm) } } result = append(result, map[string]string{ "name": role["name"], "maxAssign": role["maxAssign"], "permissions": strings.Join(permList, ","), }) } } return result } // ListRoleGrants returns all users assigned to a specific role for a chunk func ListRoleGrants(tid grc721.TokenID, roleName string) []address { result := []address{} if _, found := chunkAuthorization[tid]; !found { return result } for addr := range chunkAuthorization[tid] { if chunkAuthorization[tid][addr][roleName] { result = append(result, addr) } } return result }