lock.gno
2.35 Kb · 79 lines
1// untrusted-render: successor is the only stored string Render echoes, and
2// Retire validates it with assertPkgPath before storing it.
3//
4// Package lock is version 0 of the "retire the predecessor" upgrade pattern
5// (pattern B of the exploration; see ../README.md).
6//
7// Unlike pattern A, this version knows it can be superseded. Retire() freezes
8// it and names its successor, and the freeze is one-way: once a successor has
9// absorbed this version's total, re-opening it here would double-count.
10package lock
11
12import (
13 "strings"
14 "gno.land/p/nt/ownable/v0"
15 "gno.land/p/nt/ufmt/v0"
16)
17
18const owner address = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul
19
20var (
21 Ownable = ownable.NewWithAddress(owner)
22
23 counter int
24 successor string // pkgpath that replaced this one; "" while this version is live
25)
26
27// Inc adds n. It aborts once a successor has been declared.
28func Inc(cur realm, n int) {
29 if successor != "" {
30 panic("lock/v0 is retired, use " + successor)
31 }
32 counter += n
33}
34
35// Get returns this version's final (or current) total.
36func Get() int {
37 return counter
38}
39
40// Successor is the path callers should move to, or "" while this version is live.
41func Successor() string {
42 return successor
43}
44
45// Retire freezes this version and names its replacement. Owner-gated, one-way.
46func Retire(cur realm, pkgPath string) {
47 Ownable.AssertOwnedBy(cur.Previous().Address())
48 if successor != "" {
49 panic("lock/v0 is already retired, successor is " + successor)
50 }
51 assertPkgPath(pkgPath)
52 successor = pkgPath
53}
54
55func Render(_ string) string {
56 if successor == "" {
57 return ufmt.Sprintf("lock/v0: %d (live)\n", counter)
58 }
59 return ufmt.Sprintf("lock/v0: %d (retired, use %s)\n", counter, successor)
60}
61
62// assertPkgPath rejects anything that is not a gno.land realm path.
63//
64// It exists for Render, not for correctness of the upgrade: the stored path is
65// echoed into markdown, so validating it at write time is what lets Render
66// print it raw. ui.Inline would escape the dots in "gno.land" and turn the one
67// string a reader needs to copy into "gno\.land".
68func assertPkgPath(p string) {
69 if !strings.HasPrefix(p, "gno.land/r/") {
70 panic("not a realm path: " + p)
71 }
72 for _, c := range p {
73 ok := c == '/' || c == '.' || c == '_' || c == '-' ||
74 (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
75 if !ok {
76 panic("illegal character in realm path")
77 }
78 }
79}