← Back to Realms

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}
190

Raw Package Data

Raw JSON data