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}