Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: r/system/names public functions and checks with AddPackage #384

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/gno.land/r/system/names/enable.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package names

// if enabled is true, `HasPerm` will check the permissions of names.
// todo: this is a temporary solution, we should use a more robust
var enabled bool = false
1 change: 1 addition & 0 deletions examples/gno.land/r/system/names/gno.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ module gno.land/r/system/names

require (
"gno.land/p/demo/avl" v0.0.0-latest
"gno.land/p/demo/ufmt" v0.0.0-latest
)
78 changes: 51 additions & 27 deletions examples/gno.land/r/system/names/names.gno
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
package names

import (
"errors"
"regexp"
"std"

"gno.land/p/demo/avl"
Expand All @@ -12,49 +14,71 @@ import (
// determine if an address can publish a package or not.
var namespaces avl.Tree // name(string) -> Space

// TODO: more accurate.
var reNamespace = regexp.MustCompile(`^[a-z][a-z0-9_]{2,30}$`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove the /r/system from the name space here to prevent adding admin to the /r/system contracts


type Space struct {
Admins []std.Address
Editors []std.Address
InPause bool
}

func Register(namespace string) {
// TODO: input sanitization:
// - already exists / reserved.
// - min/max length, format.
// - fees (dynamic, based on length).
panic("not implemented")
func (s *Space) isAdmin(addr std.Address) bool {
return containsAddress(s.Admins, addr)
}

func AddAdmin(namespace string, newAdmin std.Address) {
// TODO: assertIsAdmin()
panic("not implemented")
func (s *Space) addAdmin(newAdmin std.Address) {
if !containsAddress(s.Admins, newAdmin) {
s.Admins = append(s.Admins, newAdmin)
}
}

func RemoveAdmin(namespace string, newAdmin std.Address) {
// TODO: assertIsAdmin()
// TODO: check if self.
panic("not implemented")
func (s *Space) removeAdmin(admin std.Address) error {
if len(s.Admins) == 1 {
return errors.New("namespace at least needs one admin")
}
if isCallerAddress(admin) {
return errors.New("cannot remove self")
}
admins := removeAddress(s.Admins, admin)
s.Admins = admins
return nil
}

func AddEditor(namespace string, newAdmin std.Address) {
// TODO: assertIsAdmin()
panic("not implemented")
func (s *Space) addEditor(newEditor std.Address) {
if !containsAddress(s.Editors, newEditor) {
s.Editors = append(s.Editors, newEditor)
}
}

func RemoveEditor(namespace string, newAdmin std.Address) {
// TODO: assertIsAdmin()
// TODO: check if self.
panic("not implemented")
func (s *Space) removeEditor(editor std.Address) error {
if isCallerAddress(editor) {
return errors.New("cannot remove self")
}
editors := removeAddress(s.Editors, editor)
s.Editors = editors
return nil
}

func SetInPause(namespace string, state bool) {
// TODO: assertIsAdmin()
panic("not implemented")
func (s *Space) hasPerm(caller std.Address) bool {
if s.InPause {
return false
}

if containsAddress(s.Admins, caller) {
return true
}

if containsAddress(s.Editors, caller) {
return true
}

return false
}

func Render(path string) string {
// TODO: by namespace.
// TODO: by address.
return "not implemented"
func validateNamespace(namespace string) error {
if !reNamespace.MatchString(namespace) {
return errors.New("invalid namespace")
}
return nil
}
124 changes: 124 additions & 0 deletions examples/gno.land/r/system/names/public.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package names

import (
"std"

"gno.land/p/demo/ufmt"
)

func Register(namespace string) {
// TODO: input sanitization:
// - already exists / reserved.
// - min/max length, format.
// - fees (dynamic, based on length).
if existsNamespace(namespace) {
panic("namespace already exists")
}

err := validateNamespace(namespace)
checkErr(err)

caller := std.GetOrigCaller()
namespaces.Set(namespace, &Space{
Admins: []std.Address{caller},
})
}

func AddAdmin(namespace string, newAdmin std.Address) {
space := getSpace(namespace)
assertIsAdmin(space)
space.addAdmin(newAdmin)
}

func RemoveAdmin(namespace string, admin std.Address) {
space := getSpace(namespace)
assertIsAdmin(space)
err := space.removeAdmin(admin)
checkErr(err)
}

func AddEditor(namespace string, newEditor std.Address) {
space := getSpace(namespace)
assertIsAdmin(space)
space.addEditor(newEditor)
}

func RemoveEditor(namespace string, editor std.Address) {
space := getSpace(namespace)
assertIsAdmin(space)
err := space.removeEditor(editor)
checkErr(err)
}

func SetInPause(namespace string, state bool) {
space := getSpace(namespace)
assertIsAdmin(space)
space.InPause = state
}

// HasPerm returns true if the caller has permission of the namespace.
// If the namespace does not exist, it will return panic.
// If the namespace exists but the caller is not an admin or editor,
// it will return false.
// The vm keeper will use this function to check to add package
func HasPerm(namespace string) bool {
// if enabled is false, it always returns true for dev and testing.
if !enabled {
return true
}
caller := std.GetOrigCaller()
space := getSpace(namespace)
return space.hasPerm(caller)
}

func Render(path string) string {
// TODO: by address.

if path == "" {
return renderIndex()
} else if path[:2] == "n/" {
return renderNamespace(path[2:])
}
return ""
}

func renderNamespace(namespace string) string {
space := getSpace(namespace)
output := ufmt.Sprintf(`
# %s

## Admins

%s

## Editors

%s

## InPause

%s

`, namespace, renderAddresses(space.Admins), renderAddresses(space.Editors), formatBool(space.InPause))
return output
}

func renderIndex() string {
output := "## Namespaces \n"
namespaces.Iterate("", "", func(namespace string, value interface{}) bool {
space := value.(*Space)
output += ufmt.Sprintf("* [%s](/r/system/names:n/%s) - admins: %d editors: %d inPause: %s \n",
namespace, namespace, len(space.Admins), len(space.Editors), formatBool(space.InPause))
return false
})

return output
}

func renderAddresses(addresses []std.Address) string {
output := ""
for _, address := range addresses {
output += ufmt.Sprintf("* %s \n", string(address))
}
return output
}
74 changes: 74 additions & 0 deletions examples/gno.land/r/system/names/public_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package names

import (
"std"
"testing"
)

func TestRegisterNamespace(t *testing.T) {
std.TestSetOrigCaller(std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq"))
creator := std.GetOrigCaller()

n := "test2"
Register(n)
s := getSpace(n)
assertTrue(t, containsAddress(s.Admins, creator))
}

func TestAdminFuncs(t *testing.T) {
std.TestSetOrigCaller(std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq"))
creator := std.GetOrigCaller()

test1 := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
test2 := std.Address("g1r6casl322p5adkmmjjkjea404e7f6w29y6gzwg")

ns := "test3"

Register(ns)
s := getSpace(ns)

AddAdmin(ns, test1)
AddAdmin(ns, test2)
assertTrue(t, containsAddress(s.Admins, test1))
assertTrue(t, containsAddress(s.Admins, test2))

RemoveAdmin(ns, test1)
assertFalse(t, containsAddress(s.Admins, test1))
assertTrue(t, containsAddress(s.Admins, test2))

AddEditor(ns, test1)
assertTrue(t, containsAddress(s.Editors, test1))

RemoveEditor(ns, test1)
assertTrue(t, !containsAddress(s.Editors, test1))
}

func TestSpaceMethods(t *testing.T) {
std.TestSetOrigCaller(std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq"))
creator := std.GetOrigCaller()

test1 := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
test2 := std.Address("g1r6casl322p5adkmmjjkjea404e7f6w29y6gzwg")

ns := "test4"
Register(ns)
s := getSpace(ns)

assertTrue(t, s.isAdmin(creator))
assertTrue(t, s.removeAdmin(creator) != nil)

s.addAdmin(test1)
assertTrue(t, s.removeAdmin(test1) == nil)
}

func assertTrue(t *testing.T, b bool) {
if !b {
t.Fail()
}
}

func assertFalse(t *testing.T, b bool) {
if b {
t.Fail()
}
}
64 changes: 64 additions & 0 deletions examples/gno.land/r/system/names/utils.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package names

import (
"std"
)

func getSpace(namespace string) *Space {
s, ok := namespaces.Get(namespace)
if !ok {
panic("namespace does not exist")
}
return s.(*Space)
}

func existsNamespace(name string) bool {
_, ok := namespaces.Get(name)
return ok
}

func assertIsAdmin(space *Space) {
caller := std.GetOrigCaller()
if !space.isAdmin(caller) {
panic("Only admins can call this function")
}
}

func removeAddress(addrs []std.Address, addr std.Address) []std.Address {
var newAddrs []std.Address
for _, a := range addrs {
if a != addr {
newAddrs = append(newAddrs, a)
}
}
return newAddrs
}

func containsAddress(addrs []std.Address, addr std.Address) bool {
for _, a := range addrs {
if a == addr {
return true
}
}
return false
}

func isCallerAddress(addr std.Address) bool {
if addr == std.GetOrigCaller() {
return true
}
return false
}

func checkErr(err error) {
if err != nil {
panic(err)
}
}

func formatBool(b bool) string {
if b {
return "true"
}
return "false"
}
Loading