mgroup.gno

6.15 Kb ยท 198 lines
  1// Package mgroup is a simple managed group managing ownership and membership
  2// for authorization in gno realms. The ManagedGroup struct is used to manage
  3// the owner, backup owners, and members of a group. The owner is the primary
  4// owner of the group and can add and remove backup owners and members. Backup
  5// owners can claim ownership of the group. This is meant to provide backup
  6// accounts for the owner in case the owner account is lost or compromised.
  7// Members are used to authorize actions across realms.
  8package mgroup
  9
 10import (
 11	"errors"
 12	"std"
 13
 14	"gno.land/p/demo/avl"
 15	"gno.land/p/demo/ownable"
 16)
 17
 18var (
 19	ErrCannotRemoveOwner = errors.New("mgroup: cannot remove owner")
 20	ErrNotBackupOwner    = errors.New("mgroup: not a backup owner")
 21	ErrNotMember         = errors.New("mgroup: not a member")
 22	ErrInvalidAddress    = errors.New("mgroup: address is invalid")
 23)
 24
 25type ManagedGroup struct {
 26	owner        *ownable.Ownable
 27	backupOwners *avl.Tree
 28	members      *avl.Tree
 29}
 30
 31// New creates a new ManagedGroup with the owner set to the provided address.
 32// The owner is automatically added as a backup owner and member of the group.
 33func New(ownerAddress std.Address) *ManagedGroup {
 34	g := &ManagedGroup{
 35		owner:        ownable.NewWithAddress(ownerAddress),
 36		backupOwners: avl.NewTree(),
 37		members:      avl.NewTree(),
 38	}
 39	err := g.addBackupOwner(ownerAddress)
 40	if err != nil {
 41		panic(err)
 42	}
 43	err = g.addMember(ownerAddress)
 44	if err != nil {
 45		panic(err)
 46	}
 47	return g
 48}
 49
 50// AddBackupOwner adds a backup owner to the group by std.Address.
 51// If the caller is not the owner, an error is returned.
 52func (g *ManagedGroup) AddBackupOwner(addr std.Address) error {
 53	if !g.owner.OwnedByPrevious() {
 54		return ownable.ErrUnauthorized
 55	}
 56	return g.addBackupOwner(addr)
 57}
 58
 59func (g *ManagedGroup) addBackupOwner(addr std.Address) error {
 60	if !addr.IsValid() {
 61		return ErrInvalidAddress
 62	}
 63	g.backupOwners.Set(addr.String(), struct{}{})
 64	return nil
 65}
 66
 67// RemoveBackupOwner removes a backup owner from the group by std.Address.
 68// The owner cannot be removed. If the caller is not the owner, an error is returned.
 69func (g *ManagedGroup) RemoveBackupOwner(addr std.Address) error {
 70	if !g.owner.OwnedByPrevious() {
 71		return ownable.ErrUnauthorized
 72	}
 73	if !addr.IsValid() {
 74		return ErrInvalidAddress
 75	}
 76	if addr == g.Owner() {
 77		return ErrCannotRemoveOwner
 78	}
 79	g.backupOwners.Remove(addr.String())
 80	return nil
 81}
 82
 83// ClaimOwnership allows a backup owner to claim ownership of the group.
 84// If the caller is not a backup owner, an error is returned.
 85// The caller is automatically added as a member of the group.
 86func (g *ManagedGroup) ClaimOwnership() error {
 87	caller := std.PreviousRealm().Address()
 88	// already owner, skip
 89	if caller == g.Owner() {
 90		return nil
 91	}
 92	if !g.IsBackupOwner(caller) {
 93		return ErrNotMember
 94	}
 95	g.owner = ownable.NewWithAddress(caller)
 96	err := g.addMember(caller)
 97	return err
 98}
 99
100// AddMember adds a member to the group by std.Address.
101// If the caller is not the owner, an error is returned.
102func (g *ManagedGroup) AddMember(addr std.Address) error {
103	if !g.owner.OwnedByPrevious() {
104		return ownable.ErrUnauthorized
105	}
106	return g.addMember(addr)
107}
108
109func (g *ManagedGroup) addMember(addr std.Address) error {
110	if !addr.IsValid() {
111		return ErrInvalidAddress
112	}
113	g.members.Set(addr.String(), struct{}{})
114	return nil
115}
116
117// RemoveMember removes a member from the group by std.Address.
118// The owner cannot be removed. If the caller is not the owner,
119// an error is returned.
120func (g *ManagedGroup) RemoveMember(addr std.Address) error {
121	if !g.owner.OwnedByPrevious() {
122		return ownable.ErrUnauthorized
123	}
124	if !addr.IsValid() {
125		return ErrInvalidAddress
126	}
127	if addr == g.Owner() {
128		return ErrCannotRemoveOwner
129	}
130	g.members.Remove(addr.String())
131	return nil
132}
133
134// MemberCount returns the number of members in the group.
135func (g *ManagedGroup) MemberCount() int {
136	return g.members.Size()
137}
138
139// BackupOwnerCount returns the number of backup owners in the group.
140func (g *ManagedGroup) BackupOwnerCount() int {
141	return g.backupOwners.Size()
142}
143
144// IsMember checks if an address is a member of the group.
145func (g *ManagedGroup) IsMember(addr std.Address) bool {
146	return g.members.Has(addr.String())
147}
148
149// IsBackupOwner checks if an address is a backup owner in the group.
150func (g *ManagedGroup) IsBackupOwner(addr std.Address) bool {
151	return g.backupOwners.Has(addr.String())
152}
153
154// Owner returns the owner of the group.
155func (g *ManagedGroup) Owner() std.Address {
156	return g.owner.Owner()
157}
158
159// BackupOwners returns a slice of all backup owners in the group, using the underlying
160// avl.Tree to iterate over the backup owners. If you have a large group, you may
161// want to use BackupOwnersWithOffset to iterate over backup owners in chunks.
162func (g *ManagedGroup) BackupOwners() []string {
163	return g.BackupOwnersWithOffset(0, g.BackupOwnerCount())
164}
165
166// Members returns a slice of all members in the group, using the underlying
167// avl.Tree to iterate over the members. If you have a large group, you may
168// want to use MembersWithOffset to iterate over members in chunks.
169func (g *ManagedGroup) Members() []string {
170	return g.MembersWithOffset(0, g.MemberCount())
171}
172
173// BackupOwnersWithOffset returns a slice of backup owners in the group, using the underlying
174// avl.Tree to iterate over the backup owners. The offset and count parameters allow you
175// to iterate over backup owners in chunks to support patterns such as pagination.
176func (g *ManagedGroup) BackupOwnersWithOffset(offset, count int) []string {
177	return sliceWithOffset(g.backupOwners, offset, count)
178}
179
180// MembersWithOffset returns a slice of members in the group, using the underlying
181// avl.Tree to iterate over the members. The offset and count parameters allow you
182// to iterate over members in chunks to support patterns such as pagination.
183func (g *ManagedGroup) MembersWithOffset(offset, count int) []string {
184	return sliceWithOffset(g.members, offset, count)
185}
186
187// sliceWithOffset is a helper function to iterate over an avl.Tree with an offset and count.
188func sliceWithOffset(t *avl.Tree, offset, count int) []string {
189	var result []string
190	t.IterateByOffset(offset, count, func(k string, _ any) bool {
191		if k == "" {
192			return true
193		}
194		result = append(result, k)
195		return false
196	})
197	return result
198}