diff --git a/examples/gno.land/p/demo/boards2/admindao/admindao.gno b/examples/gno.land/p/demo/boards2/admindao/admindao.gno index 4e1d49e435e..404bfe78db7 100644 --- a/examples/gno.land/p/demo/boards2/admindao/admindao.gno +++ b/examples/gno.land/p/demo/boards2/admindao/admindao.gno @@ -1,6 +1,7 @@ package admindao import ( + "errors" "std" "gno.land/p/demo/avl" @@ -9,6 +10,9 @@ import ( // TODO: Add support for proposals // TODO: Add support for events +// ErrMemberExists indicates that a member is already part of the DAO. +var ErrMemberExists = errors.New("member already exist") + // AdminDAO defines a Boards administration DAO. type AdminDAO struct { parent *AdminDAO @@ -40,6 +44,21 @@ func (dao AdminDAO) Members() []std.Address { return members } +// AddMember adds a new member to the DAO. +func (dao *AdminDAO) AddMember(user std.Address) error { + if dao.IsMember(user) { + return ErrMemberExists + } + dao.members.Set(user.String(), struct{}{}) + return nil +} + +// RemoveMember removes a member from the DAO. +func (dao *AdminDAO) RemoveMember(user std.Address) (removed bool) { + _, removed = dao.members.Remove(user.String()) + return removed +} + // IsMember checks if a user is a member of the DAO. func (dao AdminDAO) IsMember(user std.Address) bool { return dao.members.Has(user.String()) diff --git a/examples/gno.land/p/demo/boards2/admindao/admindao_test.gno b/examples/gno.land/p/demo/boards2/admindao/admindao_test.gno index 171f5618892..999ecdfcd0a 100644 --- a/examples/gno.land/p/demo/boards2/admindao/admindao_test.gno +++ b/examples/gno.land/p/demo/boards2/admindao/admindao_test.gno @@ -59,6 +59,30 @@ func TestNew(t *testing.T) { } } +func TestAdminDAOAddMember(t *testing.T) { + member := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + dao := New(WithMember("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")) + + err := dao.AddMember(member) + urequire.NoError(t, err) + uassert.Equal(t, 2, len(dao.Members())) + uassert.True(t, dao.IsMember(member)) + + err = dao.AddMember(member) + uassert.ErrorIs(t, err, ErrMemberExists) +} + +func TestAdminDAORemoveMember(t *testing.T) { + member := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + dao := New(WithMember(member)) + + removed := dao.RemoveMember(member) + urequire.True(t, removed) + + removed = dao.RemoveMember(member) + urequire.False(t, removed) +} + func TestAdminDAOIsMember(t *testing.T) { cases := []struct { name string diff --git a/examples/gno.land/r/demo/boards2/acl.gno b/examples/gno.land/r/demo/boards2/acl.gno deleted file mode 100644 index 631591e1214..00000000000 --- a/examples/gno.land/r/demo/boards2/acl.gno +++ /dev/null @@ -1,100 +0,0 @@ -package boards2 - -import ( - "std" - - "gno.land/p/demo/avl" - "gno.land/p/demo/boards2/admindao" -) - -// TODO: Support to deal with permissions for anonymous users? - -const ( - RoleOwner Role = "owner" - RoleAdmin = "admin" - RoleModerator = "moderator" -) - -type ( - // Role defines the type for user roles. - Role string - - // ACL or access control list manages user roles and permissions. - ACL struct { - superRole Role - dao *admindao.AdminDAO - users *avl.Tree // string(std.Address) -> []Role - roles *avl.Tree // string(role) -> []Permission - } -) - -// NewACL create a new access control list. -func NewACL(dao *admindao.AdminDAO, options ...ACLOption) *ACL { - acl := &ACL{ - dao: dao, - roles: avl.NewTree(), - users: avl.NewTree(), - } - for _, apply := range options { - apply(acl) - } - return acl -} - -// Roles returns the list of roles. -func (acl ACL) Roles() []Role { - var roles []Role - acl.roles.Iterate("", "", func(name string, _ interface{}) bool { - roles = append(roles, Role(name)) - return false - }) - return roles -} - -// GetUserRoles returns the list of roles assigned to a user. -func (acl ACL) GetUserRoles(user std.Address) []Role { - v, found := acl.users.Get(user.String()) - if !found { - return nil - } - return v.([]Role) -} - -// HasRole checks if a user has a specific role assigned. -func (acl ACL) HasRole(user std.Address, r Role) bool { - for _, role := range acl.GetUserRoles(user) { - if role == r { - return true - } - } - return false -} - -// HasPermission checks if a user has a specific permission. -func (acl ACL) HasPermission(user std.Address, perm Permission) bool { - // TODO: Should we check that the user belongs to the DAO? - for _, r := range acl.GetUserRoles(user) { - v, found := acl.roles.Get(string(r)) - if !found { - continue - } - - for _, p := range v.([]Permission) { - if p == perm { - return true - } - } - } - return false -} - -// WithPermission calls a callback when a user has a specific permission. -// It panics on error. -func (acl ACL) WithPermission(user std.Address, perm Permission, a Args, cb func(Args)) { - if !acl.HasPermission(user, perm) || !acl.dao.IsMember(user) { - panic("unauthorized") - } - - // TODO: Support DAO proposals that run the callback on proposal execution - cb(a) -} diff --git a/examples/gno.land/r/demo/boards2/acl_options.gno b/examples/gno.land/r/demo/boards2/acl_options.gno deleted file mode 100644 index 6a481470766..00000000000 --- a/examples/gno.land/r/demo/boards2/acl_options.gno +++ /dev/null @@ -1,30 +0,0 @@ -package boards2 - -import "std" - -// ACLOption configures an ACL. -type ACLOption func(*ACL) - -// WithSuperRole assigns a super role. -// A super role is one that have all ACL permissions. -// These type of role doesn't need to be mapped to any permission. -func WithSuperRole(r Role) ACLOption { - return func(acl *ACL) { - acl.superRole = r - } -} - -// WithUser adds a user to the ACL with optional assigned roles. -func WithUser(user std.Address, roles ...Role) ACLOption { - return func(acl *ACL) { - // TODO: Should we enforce that users are members of the DAO? [acl.dao.IsMember(user)] - acl.users.Set(user.String(), append([]Role(nil), roles...)) - } -} - -// WithRole add a role to the ACL with one or more assigned permissions. -func WithRole(r Role, p Permission, extra ...Permission) ACLOption { - return func(acl *ACL) { - acl.roles.Set(string(r), append([]Permission{p}, extra...)) - } -} diff --git a/examples/gno.land/r/demo/boards2/acl_test.gno b/examples/gno.land/r/demo/boards2/acl_test.gno deleted file mode 100644 index 2b765920a45..00000000000 --- a/examples/gno.land/r/demo/boards2/acl_test.gno +++ /dev/null @@ -1,271 +0,0 @@ -package boards2 - -import ( - "std" - "testing" - - "gno.land/p/demo/boards2/admindao" - "gno.land/p/demo/uassert" - "gno.land/p/demo/urequire" -) - -func TestNewACL(t *testing.T) { - roles := []string{"a", "b"} - dao := admindao.New() - - acl := NewACL(dao, WithRole("a", "permission1"), WithRole("b", "permission2")) - - urequire.Equal(t, len(roles), len(acl.Roles()), "roles") - for i, r := range acl.Roles() { - uassert.Equal(t, roles[i], string(r)) - } -} - -func TestACLWithPermission(t *testing.T) { - cases := []struct { - name string - user std.Address - permission Permission - args Args - acl *ACL - err string - called bool - }{ - { - name: "ok", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - permission: "bar", - acl: NewACL( - admindao.New(admindao.WithMember("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), - WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), - WithRole("foo", "bar"), - ), - called: true, - }, - { - name: "ok with arguments", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - permission: "bar", - args: Args{"a", "b"}, - acl: NewACL( - admindao.New(admindao.WithMember("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), - WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), - WithRole("foo", "bar"), - ), - called: true, - }, - { - name: "no permission", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - permission: "bar", - acl: NewACL( - admindao.New(admindao.WithMember("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), - WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), - WithRole("foo", "bar"), - ), - err: "unauthorized", - }, - { - name: "is not a DAO member", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - permission: "bar", - acl: NewACL( - admindao.New(), - WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), - WithRole("foo", "bar"), - ), - err: "unauthorized", - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - var ( - called bool - args Args - ) - - callback := func(a Args) { - args = a - called = true - } - - testCaseFn := func() { - tc.acl.WithPermission(tc.user, tc.permission, tc.args, callback) - } - - if tc.err != "" { - urequire.PanicsWithMessage(t, tc.err, testCaseFn, "panic") - return - } else { - urequire.NotPanics(t, testCaseFn, "no panic") - } - - urequire.Equal(t, tc.called, called, "callback called") - urequire.Equal(t, len(tc.args), len(args), "args count") - for i, a := range args { - uassert.Equal(t, tc.args[i].(string), a.(string)) - } - }) - } -} - -func TestACLGetUserRoles(t *testing.T) { - cases := []struct { - name string - user std.Address - roles []string - acl *ACL - }{ - { - name: "single role", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - roles: []string{"admin"}, - acl: NewACL(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin")), - }, - { - name: "multiple roles", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - roles: []string{"admin", "foo", "bar"}, - acl: NewACL(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin", "foo", "bar")), - }, - { - name: "without roles", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - acl: NewACL(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), - }, - { - name: "not a user", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - acl: NewACL(admindao.New()), - }, - { - name: "multiple users", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - roles: []string{"admin"}, - acl: NewACL( - admindao.New(), - WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin"), - WithUser("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "admin"), - WithUser("g1w4ek2u3jta047h6lta047h6lta047h6l9huexc", "admin", "bar"), - ), - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - roles := tc.acl.GetUserRoles(tc.user) - - urequire.Equal(t, len(tc.roles), len(roles), "user role count") - for i, r := range roles { - uassert.Equal(t, tc.roles[i], string(r)) - } - }) - } -} - -func TestACLHasRole(t *testing.T) { - cases := []struct { - name string - user std.Address - role Role - acl *ACL - want bool - }{ - { - name: "ok", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - role: "admin", - acl: NewACL(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin")), - want: true, - }, - { - name: "ok with multiple roles", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - role: "foo", - acl: NewACL(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin", "foo")), - want: true, - }, - { - name: "user without roles", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - acl: NewACL(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), - }, - { - name: "has no role", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - role: "bar", - acl: NewACL(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo")), - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - got := tc.acl.HasRole(tc.user, tc.role) - uassert.Equal(t, got, tc.want) - }) - } -} - -func TestACLHasPermission(t *testing.T) { - cases := []struct { - name string - user std.Address - permission Permission - acl *ACL - want bool - }{ - { - name: "ok", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - permission: "bar", - acl: NewACL( - admindao.New(), - WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), - WithRole("foo", "bar"), - ), - want: true, - }, - { - name: "ok with multiple users", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - permission: "bar", - acl: NewACL( - admindao.New(), - WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), - WithUser("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "foo"), - WithRole("foo", "bar"), - ), - want: true, - }, - { - name: "ok with multiple roles", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - permission: "other", - acl: NewACL( - admindao.New(), - WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo", "baz"), - WithRole("foo", "bar"), - WithRole("baz", "other"), - ), - want: true, - }, - { - name: "no permission", - user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - permission: "other", - acl: NewACL( - admindao.New(), - WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), - WithRole("foo", "bar"), - ), - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - got := tc.acl.HasPermission(tc.user, tc.permission) - uassert.Equal(t, got, tc.want) - }) - } -} diff --git a/examples/gno.land/r/demo/boards2/board.gno b/examples/gno.land/r/demo/boards2/board.gno index 64cc8730d6b..ee1b548aa0f 100644 --- a/examples/gno.land/r/demo/boards2/board.gno +++ b/examples/gno.land/r/demo/boards2/board.gno @@ -31,10 +31,6 @@ type Board struct { } func newBoard(id BoardID, name string, creator std.Address) *Board { - if gBoardsByName.Has(name) { - panic("board already exists") - } - return &Board{ id: id, name: name, diff --git a/examples/gno.land/r/demo/boards2/boards.gno b/examples/gno.land/r/demo/boards2/boards.gno index 752f63bc438..53072e37542 100644 --- a/examples/gno.land/r/demo/boards2/boards.gno +++ b/examples/gno.land/r/demo/boards2/boards.gno @@ -1,35 +1,17 @@ package boards2 -import ( - "std" - - "gno.land/p/demo/avl" - "gno.land/p/demo/boards2/admindao" -) - -// Default minimum fee in ugnot required for anonymous users -const defaultAnonymousFee = 100_000_000 +import "gno.land/p/demo/avl" var ( - gAuth Permissioner + gPerm Permissioner // TODO: Support changing the permissioner gLastBoardID BoardID gBoardsByID avl.Tree // string(id) -> *Board gBoardsByName avl.Tree // string(name) -> *Board ) func init() { - // TODO: Decide how to initialize realm owner (DAO owner member) - admin := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 - // TODO: Implement support for assigning a new realm DAO - dao := admindao.New(admindao.WithMember(admin)) - gAuth = NewACL( - dao, - WithSuperRole(RoleOwner), - // TODO: Assign roles and permissions - // WithRole(RoleAdmin, permissions...), - // WithRole(RoleModerator, permissions...), - WithUser(admin, RoleOwner), - ) + // Initialize the default realm permissions + gPerm = createDefaultPermissions() } // incGetBoardID returns a new board ID. diff --git a/examples/gno.land/r/demo/boards2/permission.gno b/examples/gno.land/r/demo/boards2/permission.gno index 70ce929c38d..0fc5dc9515d 100644 --- a/examples/gno.land/r/demo/boards2/permission.gno +++ b/examples/gno.land/r/demo/boards2/permission.gno @@ -1,9 +1,6 @@ package boards2 -import ( - "errors" - "std" -) +import "std" const ( PermissionBoardCreate Permission = "board:create" @@ -12,25 +9,42 @@ const ( PermissionThreadDelete = "thread:delete" PermissionThreadRepost = "thread:repost" PermissionReplyDelete = "reply:delete" + PermissionMemberInvite = "member:invite" + PermissionMemberRemove = "member:remove" ) -// ErrUnauzorized indicates that user doesn't have a required permission. -var ErrUnauzorized = errors.New("unauthorized") +const ( + RoleOwner Role = "owner" + RoleAdmin = "admin" + RoleModerator = "moderator" +) type ( // Permission defines the type for permissions. Permission string + // Role defines the type for user roles. + Role string + // Args is a list of generic arguments. Args []interface{} // Permissioner define an interface to for permissioned execution. Permissioner interface { + // HasRole checks if a user has a specific role assigned. + HasRole(std.Address, Role) bool + // HasPermission checks if a user has a specific permission. HasPermission(std.Address, Permission) bool // WithPermission calls a callback when a user has a specific permission. // It panics on error. WithPermission(std.Address, Permission, Args, func(Args)) + + // AddUser adds a new user to the permissioner. + AddUser(std.Address, ...Role) error + + // RemoveUser removes a user from the permissioner. + RemoveUser(std.Address) (removed bool) } ) diff --git a/examples/gno.land/r/demo/boards2/permission_default.gno b/examples/gno.land/r/demo/boards2/permission_default.gno new file mode 100644 index 00000000000..fc8efee14d2 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/permission_default.gno @@ -0,0 +1,169 @@ +package boards2 + +import ( + "errors" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/boards2/admindao" +) + +type ( + // PermissionsHandlerFunc defines a function to handle permission callbacks. + // Handlers are called by the `WithPermission()` method to execute callbacks + // when users have the permission assigned. + PermissionsHandlerFunc func(Permissioner, Args, func(Args)) + + // DefaultPermissions manages users, roles and permissions. + DefaultPermissions struct { + superRole Role + dao *admindao.AdminDAO + handlers *avl.Tree // string(permission) -> PermissionsHandlerFunc + users *avl.Tree // string(std.Address) -> []Role + roles *avl.Tree // string(role) -> []Permission + } +) + +// NewDefaultPermissions creates a new permissions type. +// This type is a default implementation to handle users, roles and permissions. +func NewDefaultPermissions(dao *admindao.AdminDAO, options ...DefaultPermissionsOption) *DefaultPermissions { + dp := &DefaultPermissions{ + dao: dao, + handlers: avl.NewTree(), + roles: avl.NewTree(), + users: avl.NewTree(), + } + for _, apply := range options { + apply(dp) + } + return dp +} + +// Roles returns the list of roles. +func (dp DefaultPermissions) Roles() []Role { + var roles []Role + dp.roles.Iterate("", "", func(name string, _ interface{}) bool { + roles = append(roles, Role(name)) + return false + }) + return roles +} + +// RoleExists checks if a role exists. +func (dp DefaultPermissions) RoleExists(r Role) bool { + return dp.roles.Iterate("", "", func(name string, _ interface{}) bool { + return Role(name) == r + }) +} + +// GetUserRoles returns the list of roles assigned to a user. +func (dp DefaultPermissions) GetUserRoles(user std.Address) []Role { + v, found := dp.users.Get(user.String()) + if !found { + return nil + } + return v.([]Role) +} + +// HasRole checks if a user has a specific role assigned. +func (dp DefaultPermissions) HasRole(user std.Address, r Role) bool { + for _, role := range dp.GetUserRoles(user) { + if role == r { + return true + } + } + return false +} + +// HasPermission checks if a user has a specific permission. +func (dp DefaultPermissions) HasPermission(user std.Address, perm Permission) bool { + // TODO: Should we check that the user belongs to the DAO? + for _, r := range dp.GetUserRoles(user) { + if dp.superRole == r { + return true + } + + v, found := dp.roles.Get(string(r)) + if !found { + continue + } + + for _, p := range v.([]Permission) { + if p == perm { + return true + } + } + } + return false +} + +// AddUser adds a new user to permissions. +func (dp *DefaultPermissions) AddUser(user std.Address, roles ...Role) error { + if dp.users.Has(user.String()) { + return errors.New("user already exists") + } + + for _, r := range roles { + if !dp.RoleExists(r) { + return errors.New("invalid role: " + string(r)) + } + } + + if err := dp.dao.AddMember(user); err != nil { + return err + } + + dp.users.Set(user.String(), append([]Role(nil), roles...)) + return nil +} + +// RemoveUser removes a user from permissions. +func (dp *DefaultPermissions) RemoveUser(user std.Address) bool { + _, removed := dp.users.Remove(user.String()) + dp.dao.RemoveMember(user) + return removed +} + +// HandleFunc registers a handler function for a permission. +func (dp *DefaultPermissions) HandleFunc(p Permission, fn PermissionsHandlerFunc) { + dp.handlers.Set(string(p), fn) +} + +// WithPermission calls a callback when a user has a specific permission. +// It panics on error or when a handler panics. +// Callbacks are by default called when there is no handle registered for the permission. +func (dp *DefaultPermissions) WithPermission(user std.Address, perm Permission, args Args, cb func(Args)) { + if !dp.HasPermission(user, perm) || !dp.dao.IsMember(user) { + panic("unauthorized") + } + + h, found := dp.handlers.Get(string(perm)) + if !found { + cb(args) // TODO: Should we fail instead? + return + } + + fn := h.(PermissionsHandlerFunc) + fn(dp, args, cb) +} + +func createDefaultPermissions() *DefaultPermissions { + // TODO: Define and change the default realm owner (or owners) + owner := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + // TODO: DAO should be a different realm or proposal and voting functions should be part of boards realm? + // Permissions and DAO mechanics should be discussed and improved. Add `GetDAO()` to `Permissioner`?? + dao := admindao.New(admindao.WithMember(owner)) + perms := NewDefaultPermissions( + dao, + WithSuperRole(RoleOwner), + WithRole(RoleAdmin, PermissionBoardCreate, PermissionMemberInvite), + // TODO: Finish assigning all roles and permissions + // WithRole(RoleModerator, permissions...), + WithUser(owner, RoleOwner), + ) + + perms.HandleFunc(PermissionBoardCreate, handleBoardCreate) + perms.HandleFunc(PermissionMemberInvite, handleMemberInvite) + + return perms +} diff --git a/examples/gno.land/r/demo/boards2/permission_default_test.gno b/examples/gno.land/r/demo/boards2/permission_default_test.gno new file mode 100644 index 00000000000..bf64018c11a --- /dev/null +++ b/examples/gno.land/r/demo/boards2/permission_default_test.gno @@ -0,0 +1,401 @@ +package boards2 + +import ( + "std" + "testing" + + "gno.land/p/demo/boards2/admindao" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +var _ Permissioner = (*DefaultPermissions)(nil) + +func TestNewDefaultPermissions(t *testing.T) { + roles := []Role{"a", "b"} + dao := admindao.New() + + perms := NewDefaultPermissions(dao, WithRole("a", "permission1"), WithRole("b", "permission2")) + + urequire.Equal(t, len(roles), len(perms.Roles()), "roles") + for i, r := range perms.Roles() { + uassert.Equal(t, string(roles[i]), string(r)) + } + + for _, r := range roles { + uassert.True(t, perms.RoleExists(r)) + } +} + +func TestDefaultPermissionsWithPermission(t *testing.T) { + cases := []struct { + name string + user std.Address + permission Permission + args Args + perms *DefaultPermissions + err string + called bool + }{ + { + name: "ok", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + permission: "bar", + perms: NewDefaultPermissions( + admindao.New(admindao.WithMember("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), + WithRole("foo", "bar"), + ), + called: true, + }, + { + name: "ok with arguments", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + permission: "bar", + args: Args{"a", "b"}, + perms: NewDefaultPermissions( + admindao.New(admindao.WithMember("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), + WithRole("foo", "bar"), + ), + called: true, + }, + { + name: "no permission", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + permission: "bar", + perms: NewDefaultPermissions( + admindao.New(admindao.WithMember("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + WithRole("foo", "bar"), + ), + err: "unauthorized", + }, + { + name: "is not a DAO member", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + permission: "bar", + perms: NewDefaultPermissions( + admindao.New(), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), + WithRole("foo", "bar"), + ), + err: "unauthorized", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var ( + called bool + args Args + ) + + callback := func(a Args) { + args = a + called = true + } + + testCaseFn := func() { + tc.perms.WithPermission(tc.user, tc.permission, tc.args, callback) + } + + if tc.err != "" { + urequire.PanicsWithMessage(t, tc.err, testCaseFn, "panic") + return + } else { + urequire.NotPanics(t, testCaseFn, "no panic") + } + + urequire.Equal(t, tc.called, called, "callback called") + urequire.Equal(t, len(tc.args), len(args), "args count") + for i, a := range args { + uassert.Equal(t, tc.args[i].(string), a.(string)) + } + }) + } +} + +func TestDefaultPermissionsGetUserRoles(t *testing.T) { + cases := []struct { + name string + user std.Address + roles []string + perms *DefaultPermissions + }{ + { + name: "single role", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + roles: []string{"admin"}, + perms: NewDefaultPermissions(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin")), + }, + { + name: "multiple roles", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + roles: []string{"admin", "foo", "bar"}, + perms: NewDefaultPermissions(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin", "foo", "bar")), + }, + { + name: "without roles", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + perms: NewDefaultPermissions(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), + }, + { + name: "not a user", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + perms: NewDefaultPermissions(admindao.New()), + }, + { + name: "multiple users", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + roles: []string{"admin"}, + perms: NewDefaultPermissions( + admindao.New(), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin"), + WithUser("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "admin"), + WithUser("g1w4ek2u3jta047h6lta047h6lta047h6l9huexc", "admin", "bar"), + ), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + roles := tc.perms.GetUserRoles(tc.user) + + urequire.Equal(t, len(tc.roles), len(roles), "user role count") + for i, r := range roles { + uassert.Equal(t, tc.roles[i], string(r)) + } + }) + } +} + +func TestDefaultPermissionsHasRole(t *testing.T) { + cases := []struct { + name string + user std.Address + role Role + perms *DefaultPermissions + want bool + }{ + { + name: "ok", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + role: "admin", + perms: NewDefaultPermissions(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin")), + want: true, + }, + { + name: "ok with multiple roles", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + role: "foo", + perms: NewDefaultPermissions(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin", "foo")), + want: true, + }, + { + name: "user without roles", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + perms: NewDefaultPermissions(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), + }, + { + name: "has no role", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + role: "bar", + perms: NewDefaultPermissions(admindao.New(), WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo")), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := tc.perms.HasRole(tc.user, tc.role) + uassert.Equal(t, got, tc.want) + }) + } +} + +func TestDefaultPermissionsHasPermission(t *testing.T) { + cases := []struct { + name string + user std.Address + permission Permission + perms *DefaultPermissions + want bool + }{ + { + name: "ok", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + permission: "bar", + perms: NewDefaultPermissions( + admindao.New(), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), + WithRole("foo", "bar"), + ), + want: true, + }, + { + name: "ok with multiple users", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + permission: "bar", + perms: NewDefaultPermissions( + admindao.New(), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), + WithUser("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "foo"), + WithRole("foo", "bar"), + ), + want: true, + }, + { + name: "ok with multiple roles", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + permission: "other", + perms: NewDefaultPermissions( + admindao.New(), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo", "baz"), + WithRole("foo", "bar"), + WithRole("baz", "other"), + ), + want: true, + }, + { + name: "no permission", + user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + permission: "other", + perms: NewDefaultPermissions( + admindao.New(), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo"), + WithRole("foo", "bar"), + ), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := tc.perms.HasPermission(tc.user, tc.permission) + uassert.Equal(t, got, tc.want) + }) + } +} + +func TestDefaultPermissionsAddUser(t *testing.T) { + cases := []struct { + name string + user std.Address + roles []Role + setup func() *DefaultPermissions + err string + }{ + { + name: "single user", + user: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + roles: []Role{"a", "b"}, + setup: func() *DefaultPermissions { + return NewDefaultPermissions( + admindao.New(), + WithRole("a", "permission1"), + WithRole("b", "permission2"), + ) + }, + }, + { + name: "multiple users", + user: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + roles: []Role{"a"}, + setup: func() *DefaultPermissions { + return NewDefaultPermissions( + admindao.New(), + WithRole("a", "permission1"), + WithUser("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "a"), + WithUser("g1w4ek2u3jta047h6lta047h6lta047h6l9huexc"), + ) + }, + }, + { + name: "duplicated user", + user: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + setup: func() *DefaultPermissions { + return NewDefaultPermissions( + admindao.New(admindao.WithMember("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + ) + }, + err: "user already exists", + }, + { + name: "duplicated user", + user: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + roles: []Role{"a", "foo"}, + setup: func() *DefaultPermissions { + return NewDefaultPermissions(admindao.New(), WithRole("a", "permission1")) + }, + err: "invalid role: foo", + }, + { + name: "already a DAO member", + user: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + setup: func() *DefaultPermissions { + return NewDefaultPermissions( + admindao.New(admindao.WithMember("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), + ) + }, + err: "member already exist", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + perm := tc.setup() + + err := perm.AddUser(tc.user, tc.roles...) + + if tc.err != "" { + urequire.True(t, err != nil, "expected an error") + uassert.Equal(t, tc.err, err.Error()) + return + } else { + urequire.NoError(t, err) + } + + roles := perm.GetUserRoles(tc.user) + uassert.Equal(t, len(tc.roles), len(roles)) + for i, r := range roles { + urequire.Equal(t, string(tc.roles[i]), string(r)) + } + }) + } +} + +func TestDefaultPermissionsRemoveUser(t *testing.T) { + cases := []struct { + name string + user std.Address + setup func() *DefaultPermissions + want bool + }{ + { + name: "ok", + user: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + setup: func() *DefaultPermissions { + return NewDefaultPermissions( + admindao.New(admindao.WithMember("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")), + WithUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + ) + }, + want: true, + }, + { + name: "user not found", + user: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + setup: func() *DefaultPermissions { + return NewDefaultPermissions(admindao.New()) + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + perm := tc.setup() + got := perm.RemoveUser(tc.user) + uassert.Equal(t, tc.want, got) + }) + } +} diff --git a/examples/gno.land/r/demo/boards2/permission_handlers.gno b/examples/gno.land/r/demo/boards2/permission_handlers.gno new file mode 100644 index 00000000000..9c6a24b90f0 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/permission_handlers.gno @@ -0,0 +1,38 @@ +package boards2 + +import ( + "std" + + "gno.land/r/demo/users" +) + +func handleBoardCreate(_ Permissioner, args Args, cb func(Args)) { + // TODO: This way of dealing with arguments is delicate, ideally types should be used + name := args[0].(string) + if std.Address(name).IsValid() { + panic("addresses are not allowed as board name") + } + + // When the board name is the name of a registered user + // check that caller is the owner of the name. + caller := std.GetOrigCaller() + user := users.GetUserByName(name) + if user != nil && user.Address != caller { + panic("board name is a user name registered to a different user") + } + + cb(args) +} + +func handleMemberInvite(p Permissioner, args Args, cb func(Args)) { + // Make sure that only owners invite other owners + role := args[1].(Role) + if role == RoleOwner { + caller := std.GetOrigCaller() + if !p.HasRole(caller, RoleOwner) { + panic("only owners are allowed to invite other owners") + } + } + + cb(args) +} diff --git a/examples/gno.land/r/demo/boards2/permission_options.gno b/examples/gno.land/r/demo/boards2/permission_options.gno new file mode 100644 index 00000000000..fdaef050206 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/permission_options.gno @@ -0,0 +1,30 @@ +package boards2 + +import "std" + +// DefaultPermissionsOption configures an DefaultPermissions. +type DefaultPermissionsOption func(*DefaultPermissions) + +// WithSuperRole assigns a super role. +// A super role is one that have all permissions. +// These type of role doesn't need to be mapped to any permission. +func WithSuperRole(r Role) DefaultPermissionsOption { + return func(dp *DefaultPermissions) { + dp.superRole = r + } +} + +// WithUser adds a user to default permissions with optional assigned roles. +func WithUser(user std.Address, roles ...Role) DefaultPermissionsOption { + return func(dp *DefaultPermissions) { + // TODO: Should we enforce that users are members of the DAO? [dp.dao.IsMember(user)] + dp.users.Set(user.String(), append([]Role(nil), roles...)) + } +} + +// WithRole add a role to default permissions with one or more assigned permissions. +func WithRole(r Role, p Permission, extra ...Permission) DefaultPermissionsOption { + return func(dp *DefaultPermissions) { + dp.roles.Set(string(r), append([]Permission{p}, extra...)) + } +} diff --git a/examples/gno.land/r/demo/boards2/public.gno b/examples/gno.land/r/demo/boards2/public.gno index e3cb3066f3d..9820c45499f 100644 --- a/examples/gno.land/r/demo/boards2/public.gno +++ b/examples/gno.land/r/demo/boards2/public.gno @@ -3,8 +3,6 @@ package boards2 import ( "std" "strings" - - "gno.land/r/demo/users" ) func GetBoardIDFromName(name string) (BoardID, bool) { @@ -23,16 +21,16 @@ func CreateBoard(name string) BoardID { panic("board name is empty") } - // TODO: Now that registered user requirement is removed must define a way to avoid - // increasing the IDs. Require a fee? - // Or we have to change the way boards are created, which could be async. - caller := std.GetOrigCaller() id := incGetBoardID() args := Args{name, id} - gAuth.WithPermission(caller, PermissionBoardCreate, args, func(a Args) { + gPerm.WithPermission(caller, PermissionBoardCreate, args, func(a Args) { // TODO: Do the callback really need the args or we could have the same result directly referencing? name := a[0].(string) + if gBoardsByName.Has(name) { + panic("board already exists") + } + id := a[1].(BoardID) board := newBoard(id, name, caller) gBoardsByID.Set(id.Key(), board) @@ -44,13 +42,11 @@ func CreateBoard(name string) BoardID { func CreateThread(bid BoardID, title, body string) PostID { assertIsUserCall() + // TODO: Assert that caller is a board member (when board type is invite only) caller := std.GetOrigCaller() assertHasPermission(caller, PermissionThreadCreate) // TODO: Who can create threads? - assertAnonymousCallerFeeReceived(caller) // TODO: Do we require a fee to anonymous users? assertBoardExists(bid) - // TODO: Assert that caller is a board member (when board type is invite only) - board := mustGetBoard(bid) thread := board.AddThread(caller, title, body) return thread.id @@ -59,9 +55,8 @@ func CreateThread(bid BoardID, title, body string) PostID { func CreateReply(bid BoardID, threadID, replyID PostID, body string) PostID { assertIsUserCall() + // TODO: Assert that caller is a board member (when board type is invite only) caller := std.GetOrigCaller() - assertAnonymousCallerFeeReceived(caller) // TODO: Do we require a fee to anonymous users? - board := mustGetBoard(bid) thread := mustGetThread(board, threadID) @@ -83,8 +78,8 @@ func CreateReply(bid BoardID, threadID, replyID PostID, body string) PostID { func CreateRepost(bid BoardID, threadID PostID, title, body string, dstBoardID BoardID) PostID { assertIsUserCall() + // TODO: Assert that caller is a board member (when board type is invite only) caller := std.GetOrigCaller() - assertAnonymousCallerFeeReceived(caller) assertBoardExists(dstBoardID) board := mustGetBoard(bid) @@ -111,7 +106,7 @@ func DeleteThread(bid BoardID, threadID PostID) { caller := std.GetOrigCaller() args := Args{bid, threadID} - gAuth.WithPermission(caller, PermissionThreadDelete, args, func(a Args) { + gPerm.WithPermission(caller, PermissionThreadDelete, args, func(a Args) { bid := a[0].(BoardID) board := mustGetBoard(bid) @@ -132,7 +127,7 @@ func DeleteReply(bid BoardID, threadID, replyID PostID) { caller := std.GetOrigCaller() args := Args{bid, threadID, replyID} - gAuth.WithPermission(caller, PermissionReplyDelete, args, func(a Args) { + gPerm.WithPermission(caller, PermissionReplyDelete, args, func(a Args) { bid := a[0].(BoardID) board := mustGetBoard(bid) @@ -152,7 +147,7 @@ func EditThread(bid BoardID, threadID PostID, title, body string) { caller := std.GetOrigCaller() args := Args{bid, threadID, title, body} - gAuth.WithPermission(caller, PermissionThreadEdit, args, func(a Args) { + gPerm.WithPermission(caller, PermissionThreadEdit, args, func(a Args) { bid := a[0].(BoardID) board := mustGetBoard(bid) @@ -180,28 +175,40 @@ func EditReply(bid BoardID, threadID, replyID PostID, title, body string) { reply.Update(title, body) } -func assertIsUserCall() { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { - panic("invalid non-user call") - } +func InviteMember(user std.Address, role Role) { + assertIsUserCall() + + caller := std.GetOrigCaller() + args := Args{user, role} + gPerm.WithPermission(caller, PermissionMemberInvite, args, func(a Args) { + user := a[0].(std.Address) + role := a[1].(Role) + if err := gPerm.AddUser(user, role); err != nil { + panic(err) + } + }) } -func assertAnonymousFeeReceived() { - sent := std.GetOrigSend() - fee := std.NewCoin("ugnot", int64(defaultAnonymousFee)) - if len(sent) == 0 || sent[0].IsLT(fee) { - panic("please register a user, otherwise a minimum fee of " + fee.String() + " is required") - } +func RemoveMember(user std.Address) { + assertIsUserCall() + + caller := std.GetOrigCaller() + gPerm.WithPermission(caller, PermissionMemberRemove, Args{user}, func(a Args) { + user := a[0].(std.Address) + if !gPerm.RemoveUser(user) { + panic("member not found") + } + }) } -func assertAnonymousCallerFeeReceived(caller std.Address) { - if users.GetUserByAddress(caller) == nil { - assertAnonymousFeeReceived() +func assertIsUserCall() { + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") } } func assertHasPermission(user std.Address, p Permission) { - if !gAuth.HasPermission(user, p) { + if !gPerm.HasPermission(user, p) { panic("unauthorized") } } diff --git a/examples/gno.land/r/demo/boards2/z_0_a_filetest.gno b/examples/gno.land/r/demo/boards2/z_0_a_filetest.gno new file mode 100644 index 00000000000..fc3781a78a8 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/z_0_a_filetest.gno @@ -0,0 +1,21 @@ +package main + +import ( + "std" + + "gno.land/r/demo/boards2" +) + +const owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + +func init() { + std.TestSetOrigCaller(owner) +} + +func main() { + bid := boards2.CreateBoard("test1") + println("ID =", bid) +} + +// Output: +// ID = 1 diff --git a/examples/gno.land/r/demo/boards2/z_0_b_filetest.gno b/examples/gno.land/r/demo/boards2/z_0_b_filetest.gno new file mode 100644 index 00000000000..08cbcf8ff43 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/z_0_b_filetest.gno @@ -0,0 +1,20 @@ +package main + +import ( + "std" + + "gno.land/r/demo/boards2" +) + +const owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + +func init() { + std.TestSetOrigCaller(owner) +} + +func main() { + boards2.CreateBoard("") +} + +// Error: +// board name is empty diff --git a/examples/gno.land/r/demo/boards2/z_0_c_filetest.gno b/examples/gno.land/r/demo/boards2/z_0_c_filetest.gno new file mode 100644 index 00000000000..e9a06b53e55 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/z_0_c_filetest.gno @@ -0,0 +1,24 @@ +package main + +import ( + "std" + + "gno.land/r/demo/boards2" +) + +const ( + owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + boardName = "test1" +) + +func init() { + std.TestSetOrigCaller(owner) + boards2.CreateBoard(boardName) +} + +func main() { + boards2.CreateBoard(boardName) +} + +// Error: +// board already exists diff --git a/examples/gno.land/r/demo/boards2/z_0_d_filetest.gno b/examples/gno.land/r/demo/boards2/z_0_d_filetest.gno new file mode 100644 index 00000000000..8992a5cb133 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/z_0_d_filetest.gno @@ -0,0 +1,11 @@ +// PKGPATH: gno.land/r/demo/boards2_test +package boards2_test + +import "gno.land/r/demo/boards2" + +func main() { + boards2.CreateBoard("foo") +} + +// Error: +// invalid non-user call diff --git a/examples/gno.land/r/demo/boards2/z_0_e_filetest.gno b/examples/gno.land/r/demo/boards2/z_0_e_filetest.gno new file mode 100644 index 00000000000..33f45878b65 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/z_0_e_filetest.gno @@ -0,0 +1,20 @@ +package main + +import ( + "std" + + "gno.land/r/demo/boards2" +) + +const owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + +func init() { + std.TestSetOrigCaller(owner) +} + +func main() { + boards2.CreateBoard("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") +} + +// Error: +// addresses are not allowed as board name diff --git a/examples/gno.land/r/demo/boards2/z_0_f_filetest.gno b/examples/gno.land/r/demo/boards2/z_0_f_filetest.gno new file mode 100644 index 00000000000..f17f73bac4c --- /dev/null +++ b/examples/gno.land/r/demo/boards2/z_0_f_filetest.gno @@ -0,0 +1,20 @@ +package main + +import ( + "std" + + "gno.land/r/demo/boards2" +) + +const owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + +func init() { + std.TestSetOrigCaller(owner) +} + +func main() { + boards2.CreateBoard("gnoland") +} + +// Error: +// board name is a user name registered to a different user diff --git a/examples/gno.land/r/demo/boards2/z_1_a_filetest.gno b/examples/gno.land/r/demo/boards2/z_1_a_filetest.gno new file mode 100644 index 00000000000..6497b4203e1 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/z_1_a_filetest.gno @@ -0,0 +1,24 @@ +package main + +import ( + "std" + + "gno.land/r/demo/boards2" +) + +const ( + owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") // @test2 +) + +func init() { + std.TestSetOrigCaller(owner) +} + +func main() { + boards2.InviteMember(admin, boards2.RoleAdmin) + println("ok") +} + +// Output: +// ok diff --git a/examples/gno.land/r/demo/boards2/z_1_b_filetest.gno b/examples/gno.land/r/demo/boards2/z_1_b_filetest.gno new file mode 100644 index 00000000000..eac31e48707 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/z_1_b_filetest.gno @@ -0,0 +1,29 @@ +package main + +import ( + "std" + + "gno.land/r/demo/boards2" +) + +const ( + owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") // @test2 + user = std.Address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn") +) + +func init() { + // Add an admin user + std.TestSetOrigCaller(owner) + boards2.InviteMember(admin, boards2.RoleAdmin) + + // Next call will be done by the admin user + std.TestSetOrigCaller(admin) +} + +func main() { + boards2.InviteMember(user, boards2.RoleOwner) +} + +// Error: +// only owners are allowed to invite other owners diff --git a/examples/gno.land/r/demo/boards2/z_1_c_filetest.gno b/examples/gno.land/r/demo/boards2/z_1_c_filetest.gno new file mode 100644 index 00000000000..01ca55cb2f2 --- /dev/null +++ b/examples/gno.land/r/demo/boards2/z_1_c_filetest.gno @@ -0,0 +1,30 @@ +package main + +import ( + "std" + + "gno.land/r/demo/boards2" +) + +const ( + owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") // @test2 + user = std.Address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn") +) + +func init() { + // Add an admin user + std.TestSetOrigCaller(owner) + boards2.InviteMember(admin, boards2.RoleAdmin) + + // Next call will be done by the admin user + std.TestSetOrigCaller(admin) +} + +func main() { + boards2.InviteMember(user, boards2.RoleAdmin) + println("ok") +} + +// Output: +// ok