Realm
gno.land/r/moul/x/upgrade/adminreg/facade/v0
Overview
Realm Path
gno.land/r/moul/x/upgrade/adminreg/facade/v0
Exported Functions
0
State Entries
11
Source Files
4
Total Package Entries
24
Exported Functions
No exported functions found.
State
11 state entries
Source Code
FILES
facade.gno
go
1// untrusted-render: every path Render echoes was read off a crossing frame in
2// Propose (cur.Previous().PkgPath(), which a caller cannot forge or type), and
3// Accept can only promote a key that is already in that tree.
4//
5// Package facade is the permanent entry point of the "propose and accept"
6// upgrade pattern (pattern F of the exploration; see ../../README.md).
7//
8// Pattern E lets a deploy take the realm over on the spot. This one splits that
9// into two steps that different people can hold: an implementation realm
10// NOMINATES itself from its own init, and the owner ACCEPTS a package path in a
11// separate transaction. Nothing serves until both have happened.
12//
13// The split exists because of a mechanical limit, not just a governance
14// preference. An implementation is an interface value, and a wallet cannot put
15// one in a `maketx call` argument: only strings and numbers travel. The
16// original shape of this pattern (the owner hands the facade an object) is
17// therefore reachable only from `maketx run` or from another realm. Proposing
18// from init and accepting by PATH makes both halves ordinary transactions.
19package facade
20
21import (
22 "strings"
23
24 "gno.land/p/nt/avl/v0"
25 "gno.land/p/nt/ownable/v0"
26 "gno.land/p/nt/ufmt/v0"
27)
28
29const owner address = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul
30
31// prefix bounds who may even nominate itself. Accepting is still a separate,
32// owner-gated decision.
33const prefix = "gno.land/r/moul/x/upgrade/adminreg/impl/"
34
35// Impl is the contract an implementation realm must satisfy.
36type Impl interface {
37 Greet(name string) string
38 Version() string
39}
40
41// The stage ladder, copied in shape from Sui's UpgradeCap: compatible, additive,
42// dependency-only, immutable, where a policy can only ever become MORE
43// restrictive and make_immutable discards the cap. CosmWasm (a contract with no
44// admin) and Solana (an upgrade authority set to None) reach the same place with
45// one bit; the ladder is better because the interesting states are between
46// "anything may take this over" and "nothing may ever change again".
47//
48// This pattern has an owner and a candidate list, so it has a middle rung the
49// owner-less selfreg cannot express: no new code, but still free to roll back
50// among what is already deployed.
51//
52// What the top rung does NOT do on its own: freezing this realm ends changes to
53// the POINTER, not to the code behind it. A private implementation realm can be
54// re-added at its own path, which would swap behaviour under a frozen facade.
55// It holds here only because every implementation is public by construction:
56// handing the facade its own object is exactly what forbids private (see
57// ../../README.md).
58const (
59 StageOpen = 0 // anything under the prefix may propose, the owner may accept any candidate
60 StageClosed = 1 // no new candidates; the owner may still accept among those already proposed
61 StageFrozen = 2 // nothing may be accepted again, whatever is live is final
62)
63
64var (
65 Ownable = ownable.NewWithAddress(owner)
66
67 stage = StageOpen
68
69 candidates = avl.NewTree() // pkgpath -> Impl
70 live Impl
71 livePath string
72)
73
74// Propose nominates the calling realm. Called from the implementation's init,
75// so deploying makes a candidate and nothing more.
76func Propose(cur realm, impl Impl) {
77 if stage != StageOpen {
78 panic("adminreg/facade/v0 is " + StageName() + ", no new candidate may be proposed")
79 }
80 caller := cur.Previous().PkgPath()
81 if !strings.HasPrefix(caller, prefix) {
82 panic("unauthorized: " + caller + " is not under " + prefix)
83 }
84 if impl == nil {
85 panic("implementation must not be nil")
86 }
87 candidates.Set(caller, impl)
88}
89
90// Accept promotes a proposed path to live. Owner-gated, and it takes a STRING,
91// so it is callable straight from a wallet.
92func Accept(cur realm, pkgPath string) {
93 Ownable.AssertOwnedBy(cur.Previous().Address())
94 if stage == StageFrozen {
95 panic("adminreg/facade/v0 is frozen, " + livePath + " is final")
96 }
97 v := candidates.Get(pkgPath)
98 if v == nil {
99 panic("no candidate at " + pkgPath)
100 }
101 live, livePath = v.(Impl), pkgPath
102}
103
104// Close stops new candidates. The owner may still accept among those already
105// proposed, so a rollback stays possible while new code does not.
106func Close(cur realm) {
107 tighten(cur, StageClosed)
108}
109
110// Freeze ends this realm's upgradeability, forever. There is no rung above it
111// and nothing takes it back: that is the whole point, and it is the only way out
112// of every caller trusting the owner rather than the code.
113func Freeze(cur realm) {
114 tighten(cur, StageFrozen)
115}
116
117// tighten is the ratchet. Owner-gated, and it refuses to loosen: the stage is
118// the one piece of state here that a later owner cannot undo.
119func tighten(cur realm, to int) {
120 Ownable.AssertOwnedBy(cur.Previous().Address())
121 if to <= stage {
122 panic("the stage ladder only tightens, and this realm is already " + StageName())
123 }
124 stage = to
125}
126
127// Stage is the rung this realm is on. It only ever goes up.
128func Stage() int {
129 return stage
130}
131
132// StageName is Stage as the word a caller reads in Render.
133func StageName() string {
134 switch stage {
135 case StageFrozen:
136 return "frozen"
137 case StageClosed:
138 return "closed"
139 default:
140 return "open"
141 }
142}
143
144// Live is the package path currently serving, or "" before the first Accept.
145func Live() string {
146 return livePath
147}
148
149// Candidates lists every path that has nominated itself, in order.
150func Candidates() []string {
151 out := []string{}
152 candidates.Iterate("", "", func(k string, _ any) bool {
153 out = append(out, k)
154 return false
155 })
156 return out
157}
158
159// Greet forwards to the accepted implementation.
160func Greet(name string) string {
161 assertLive()
162 return live.Greet(name)
163}
164
165// Version reports the accepted implementation's own version string.
166func Version() string {
167 assertLive()
168 return live.Version()
169}
170
171func assertLive() {
172 if live == nil {
173 panic("no implementation accepted")
174 }
175}
176
177func Render(_ string) string {
178 out := ufmt.Sprintf("adminreg/facade/v0 [%s]\n", StageName())
179 if live == nil {
180 out += "live: none accepted\n"
181 } else {
182 out += ufmt.Sprintf("live: %s (%s)\n%s\n", live.Version(), livePath, live.Greet("world"))
183 }
184 out += ufmt.Sprintf("candidates: %d\n", candidates.Size())
185 for _, p := range Candidates() {
186 out += "- " + p + "\n"
187 }
188 return out
189}
190Raw Package Data
Raw JSON data