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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

hub.gno

4.85 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 "# gnomi hubv2\n\n> Not initialized.\n"
197	}
198	out := "# gnomi 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}