root.gno
2.44 Kb · 81 lines
1// untrusted-render: live is the only stored string Render echoes, and SetLive
2// validates it with assertPkgPath before storing it.
3//
4// Package root is the data realm of the "state realm + swappable logic" upgrade
5// pattern (pattern C of the exploration; see ../../README.md).
6//
7// root holds the state and nothing else worth changing. It grants write access
8// to exactly one logic realm at a time, identified by package path off the
9// crossing frame rather than passed in as an argument, so a logic realm cannot
10// claim to be a path it does not occupy. Upgrading is a single SetLive call:
11// no migration, no downtime, and the data never moves.
12package root
13
14import (
15 "strings"
16 "gno.land/p/nt/ownable/v0"
17 "gno.land/p/nt/ufmt/v0"
18)
19
20const owner address = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul
21
22var (
23 Ownable = ownable.NewWithAddress(owner)
24
25 counter int
26 live = "gno.land/r/moul/x/upgrade/store/logic/v0"
27)
28
29// Inc adds n to the stored counter. Only the live logic realm may call it.
30func Inc(cur realm, n int) int {
31 assertCallerIsLive(cur)
32 counter += n
33 return counter
34}
35
36// Get is open to everyone: the data is public, only writes are gated.
37func Get() int {
38 return counter
39}
40
41// Live is the package path currently allowed to write.
42func Live() string {
43 return live
44}
45
46// SetLive hands write access to another logic realm. This is the upgrade.
47func SetLive(cur realm, pkgPath string) {
48 Ownable.AssertOwnedBy(cur.Previous().Address())
49 assertPkgPath(pkgPath)
50 live = pkgPath
51}
52
53func assertCallerIsLive(cur realm) {
54 caller := cur.Previous().PkgPath()
55 if caller != live {
56 panic("unauthorized: " + caller + " is not the live logic realm (" + live + ")")
57 }
58}
59
60func Render(_ string) string {
61 return ufmt.Sprintf("store/root/v0: %d (live logic: %s)\n", counter, live)
62}
63
64// assertPkgPath rejects anything that is not a gno.land realm path.
65//
66// It exists for Render, not for correctness of the upgrade: the stored path is
67// echoed into markdown, so validating it at write time is what lets Render
68// print it raw. ui.Inline would escape the dots in "gno.land" and turn the one
69// string a reader needs to copy into "gno\.land".
70func assertPkgPath(p string) {
71 if !strings.HasPrefix(p, "gno.land/r/") {
72 panic("not a realm path: " + p)
73 }
74 for _, c := range p {
75 ok := c == '/' || c == '.' || c == '_' || c == '-' ||
76 (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
77 if !ok {
78 panic("illegal character in realm path")
79 }
80 }
81}