registry.gno
4.73 Kb · 162 lines
1package bolo
2
3import (
4 "chain"
5 "chain/runtime"
6 "strings"
7
8 "gno.land/p/nt/avl/v0"
9)
10
11const (
12 registryPath = "gno.land/r/g12yg9nh4ncma44emgm8msxe8aavzywt0p95tanv/bolo"
13 versionPrefix = registryPath + "/v"
14
15 // maxVersions keeps Versions() to one bounded read (INV-9).
16 maxVersions = 50
17
18 // maxPathLen is the longest package path the chain deploys: a longer one could
19 // never hold a version.
20 maxPathLen = 256
21)
22
23// The registry's state (API §3.7), all unexported (C6).
24var (
25 admin address = adminAddr // a realm once a DAO accepts the role (ADR D11)
26 pendingAdmin address // the proposed admin until it accepts; "" for none
27
28 versions []string // every version added, oldest first
29 added avl.Tree // path → true, for lookups
30 current string // "" until the first SetCurrent
31)
32
33// The registry's events (API §6).
34const (
35 evVersionAdded = "VersionAdded"
36 evCurrentSet = "CurrentSet"
37 evAdminProposed = "AdminProposed"
38 evAdminChanged = "AdminChanged"
39)
40
41// Current returns the version new campaigns should use: a pkgpath, or "" before the
42// first SetCurrent.
43func Current() string { return current }
44
45// Versions returns every version ever added, oldest first, as a copy.
46func Versions() []string { return append([]string(nil), versions...) }
47
48// GetAdmin returns the admin and the proposed admin, "" when there is none.
49func GetAdmin() (address, address) { return admin, pendingAdmin }
50
51// AddVersion registers a version. None is ever removed.
52func AddVersion(cur realm, path string) {
53 assertCurrent(cur)
54 if cur.Previous().IsUserCall() {
55 runtime.AssertOriginCall()
56 } else {
57 assertCodeCaller(cur)
58 }
59 assertSigner(cur, admin)
60 checkNewVersion(path)
61 added.Set(path, true)
62 versions = append(versions, path)
63 chain.Emit(evVersionAdded, "path", path)
64}
65
66// SetCurrent points new campaigns, and their donors' money, at a version already
67// added.
68func SetCurrent(cur realm, path string) {
69 assertCurrent(cur)
70 if cur.Previous().IsUserCall() {
71 runtime.AssertOriginCall()
72 } else {
73 assertCodeCaller(cur)
74 }
75 assertSigner(cur, admin)
76 if !added.Has(path) {
77 panic(ErrSetting)
78 }
79 current = path
80 chain.Emit(evCurrentSet, "path", path)
81}
82
83// ProposeAdmin proposes next as the registry's admin, who takes over by accepting.
84// A second proposal replaces the first.
85func ProposeAdmin(cur realm, next address) {
86 assertCurrent(cur)
87 if cur.Previous().IsUserCall() {
88 runtime.AssertOriginCall()
89 } else {
90 assertCodeCaller(cur)
91 }
92 assertSigner(cur, admin)
93 checkCandidate(next)
94 pendingAdmin = next
95 chain.Emit(evAdminProposed, "next", next.String())
96}
97
98// AcceptAdmin makes the proposed admin the admin, and ends the proposal.
99func AcceptAdmin(cur realm) {
100 assertCurrent(cur)
101 if cur.Previous().IsUserCall() {
102 runtime.AssertOriginCall()
103 } else {
104 assertCodeCaller(cur)
105 }
106 assertSigner(cur, pendingAdmin)
107 previous := admin
108 admin, pendingAdmin = pendingAdmin, ""
109 chain.Emit(evAdminChanged, "admin", admin.String(), "previous", previous.String())
110}
111
112// Render is Bolo's front page on gnoweb, the twin of the web app's Home (UI spec §3,
113// §4.13): the name, the tagline, a link to the current version, then the versions,
114// oldest first, the current one marked. It ignores path: nothing a visitor types
115// reaches the page (API §3.7).
116func Render(path string) string {
117 if len(versions) == 0 {
118 return "# Bolo\n\nNo version is registered yet.\n"
119 }
120 var b strings.Builder
121 b.WriteString("# Bolo\n\nGive together. See where it goes.\n\n")
122 if current != "" {
123 b.WriteString("**[Open Bolo](" + strings.TrimPrefix(current, "gno.land") + ")**, where new campaigns start.\n\n")
124 }
125 b.WriteString("## Versions\n\nEach campaign runs to its end on the version it started on: money never moves between versions.\n\n")
126 for _, v := range versions {
127 b.WriteString("- [" + v + "](" + strings.TrimPrefix(v, "gno.land") + ")")
128 if v == current {
129 b.WriteString(" — current")
130 }
131 b.WriteString("\n")
132 }
133 return b.String()
134}
135
136// checkNewVersion refuses, with ErrSetting, a path that isn't a version path, one
137// already added, and one past the cap (API §3.7).
138func checkNewVersion(path string) {
139 if !isVersionPath(path) || added.Has(path) || len(versions) >= maxVersions {
140 panic(ErrSetting)
141 }
142}
143
144// isVersionPath reports whether path names a Bolo version (API §3.7), parsed by
145// hand: exactly versionPrefix, then 0 or a number without a leading zero, and
146// nothing else, in at most maxPathLen bytes. v012 would be another package than
147// v12, so it is refused.
148func isVersionPath(path string) bool {
149 if len(path) > maxPathLen || !strings.HasPrefix(path, versionPrefix) {
150 return false
151 }
152 n := path[len(versionPrefix):]
153 if n == "" || (n[0] == '0' && len(n) > 1) {
154 return false
155 }
156 for i := 0; i < len(n); i++ {
157 if n[i] < '0' || n[i] > '9' {
158 return false
159 }
160 }
161 return true
162}