Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

hub.gno

4.88 Kb · 221 lines
  1// Package hubv2 is the multi-admin module router for gnomemepad (Phase 3F).
  2//
  3// Diff vs hub v1:
  4//   - Multiple admins (AddAdmin / RemoveAdmin / ListAdmins)
  5//   - Any admin may SetModule / TransferAdmin rights via AddAdmin
  6//   - Ops helpers: AdminCount, IsAdmin
  7//
  8// Migrate: deploy hubv2 → re-SetModule all paths → point UI HUB=hubv2
  9package hubv2
 10
 11import (
 12	"chain"
 13	"strconv"
 14	"strings"
 15
 16	"gno.land/p/nt/avl/v0"
 17)
 18
 19var (
 20	// admins: address string -> true
 21	admins avl.Tree
 22	inited bool
 23	// modules: name -> package path string
 24	modules avl.Tree
 25)
 26
 27// Init sets the first admin (deployer EOA). Call once.
 28func Init(cur realm) {
 29	if inited {
 30		panic("hubv2: already initialized")
 31	}
 32	if !cur.Previous().IsUserCall() {
 33		panic("hubv2: EOA only")
 34	}
 35	a := cur.Previous().Address()
 36	admins = avl.Tree{}
 37	modules = avl.Tree{}
 38	admins.Set(a.String(), true)
 39	inited = true
 40	chain.Emit("Init", "admin", a.String())
 41}
 42
 43func requireInit() {
 44	if !inited {
 45		panic("hubv2: call Init first")
 46	}
 47}
 48
 49func isAdminAddr(addr address) bool {
 50	return admins.Has(addr.String())
 51}
 52
 53func requireAdmin(cur realm) {
 54	requireInit()
 55	if !cur.Previous().IsUserCall() {
 56		panic("hubv2: EOA only")
 57	}
 58	if !isAdminAddr(cur.Previous().Address()) {
 59		panic("hubv2: not admin")
 60	}
 61}
 62
 63// IsAdmin reports whether addr is an admin.
 64func IsAdmin(addr string) bool {
 65	requireInit()
 66	return admins.Has(strings.TrimSpace(addr))
 67}
 68
 69// AdminCount returns number of admins.
 70func AdminCount() int {
 71	requireInit()
 72	return admins.Size()
 73}
 74
 75// ListAdmins returns newline-separated admin addresses.
 76func ListAdmins() string {
 77	requireInit()
 78	out := ""
 79	admins.Iterate("", "", func(key string, _ any) bool {
 80		if out != "" {
 81			out += "\n"
 82		}
 83		out += key
 84		return false
 85	})
 86	return out
 87}
 88
 89// AddAdmin grants admin rights (existing admin only).
 90func AddAdmin(cur realm, newAdmin address) {
 91	requireAdmin(cur)
 92	if !newAdmin.IsValid() {
 93		panic("hubv2: invalid address")
 94	}
 95	if isAdminAddr(newAdmin) {
 96		panic("hubv2: already admin")
 97	}
 98	admins.Set(newAdmin.String(), true)
 99	chain.Emit("AddAdmin", "addr", newAdmin.String())
100}
101
102// RemoveAdmin revokes admin. Cannot remove last admin.
103func RemoveAdmin(cur realm, who address) {
104	requireAdmin(cur)
105	if !who.IsValid() {
106		panic("hubv2: invalid address")
107	}
108	if !isAdminAddr(who) {
109		panic("hubv2: not an admin")
110	}
111	if admins.Size() <= 1 {
112		panic("hubv2: cannot remove last admin")
113	}
114	// Prevent lock-out of caller if they're removing themselves as sole remaining — already size>1
115	admins.Remove(who.String())
116	chain.Emit("RemoveAdmin", "addr", who.String())
117}
118
119// SetModule registers or updates a module path (any admin).
120func SetModule(cur realm, name, path string) {
121	requireAdmin(cur)
122	name = strings.TrimSpace(name)
123	path = strings.TrimSpace(path)
124	if name == "" || len(name) > 32 {
125		panic("hubv2: invalid name")
126	}
127	if path == "" || len(path) > 200 {
128		panic("hubv2: invalid path")
129	}
130	if !strings.HasPrefix(path, "gno.land/") {
131		panic("hubv2: path must start with gno.land/")
132	}
133	for _, c := range name {
134		if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-') {
135			panic("hubv2: name charset")
136		}
137	}
138	modules.Set(name, path)
139	chain.Emit("SetModule", "name", name, "path", path)
140}
141
142// GetModule returns the package path for name, or "".
143func GetModule(name string) string {
144	requireInit()
145	v := modules.Get(name)
146	if v == nil {
147		return ""
148	}
149	s, ok := v.(string)
150	if !ok {
151		return ""
152	}
153	return s
154}
155
156// ListModules returns newline-separated "name|path".
157func ListModules() string {
158	requireInit()
159	out := ""
160	modules.Iterate("", "", func(key string, value any) bool {
161		path, _ := value.(string)
162		if out != "" {
163			out += "\n"
164		}
165		out += key + "|" + path
166		return false
167	})
168	return out
169}
170
171// ModuleCount for ops.
172func ModuleCount() int {
173	requireInit()
174	return modules.Size()
175}
176
177// Inited reports Init.
178func Inited() bool {
179	return inited
180}
181
182// Admin returns first admin (compat) — first key in tree iteration order.
183func Admin() string {
184	requireInit()
185	out := ""
186	admins.Iterate("", "", func(key string, _ any) bool {
187		out = key
188		return true
189	})
190	return out
191}
192
193func Render(path string) string {
194	path = strings.Trim(path, "/")
195	if !inited {
196		return "# gnomemepad hubv2\n\n> Not initialized.\n"
197	}
198	out := "# gnomemepad hubv2 (multi-admin)\n\n"
199	out += "- Admins: **" + strconv.Itoa(admins.Size()) + "**\n"
200	out += "- Modules: **" + strconv.Itoa(modules.Size()) + "**\n\n"
201	out += "## Admins\n\n"
202	admins.Iterate("", "", func(key string, _ any) bool {
203		out += "- `" + key + "`\n"
204		return false
205	})
206	out += "\n## Modules\n\n"
207	modules.Iterate("", "", func(key string, value any) bool {
208		p, _ := value.(string)
209		out += "- **" + key + "**: `" + p + "`\n"
210		return false
211	})
212	out += "\n## API\n\n- SetModule / GetModule / ListModules\n- AddAdmin / RemoveAdmin / ListAdmins / IsAdmin\n"
213	_ = path
214	return out
215}
216
217func resetForTest() {
218	admins = avl.Tree{}
219	modules = avl.Tree{}
220	inited = false
221}