Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

facade.gno

4.28 Kb · 127 lines
  1// Package facade is the permanent entry point of the "self-registering
  2// implementation" upgrade pattern (pattern E of the exploration; see
  3// ../../README.md).
  4//
  5// The path callers import never changes and holds no business logic: it holds
  6// an interface value. A new implementation realm takes over simply by being
  7// deployed, because it registers itself from its own init. Nobody has to send
  8// a transaction to switch, which is the pattern's whole appeal and also its
  9// whole risk: whoever can deploy under the guarded prefix can take the realm.
 10package facade
 11
 12import (
 13	"strings"
 14
 15	"gno.land/p/nt/ufmt/v0"
 16)
 17
 18// Impl is the contract an implementation realm must satisfy. This interface is
 19// the one thing here that can never change: it is compiled into every caller.
 20type Impl interface {
 21	Greet(name string) string
 22	Version() string
 23}
 24
 25// prefix is the only gate. An implementation must live under it.
 26//
 27// gno ships p/nt/nestedpkg/v0 for exactly this check, but its AssertCallerIsSubPath
 28// wants the implementation nested UNDER the facade's own path, and with the
 29// version segment last (facade/v0, impl/v0) no sibling is a sub-path of another.
 30// IsSameNamespace is the other shipped option and is far too loose: it would let
 31// any realm in the whole namespace seize this one. Hence an explicit prefix.
 32const prefix = "gno.land/r/moul/x/upgrade/selfreg/impl/"
 33
 34// The stage ladder. Sui gates package upgrades with an UpgradeCap whose policy
 35// runs compatible, additive, dependency-only, immutable, and the rule that makes
 36// it worth copying is that a policy can only ever become MORE restrictive.
 37// CosmWasm and Solana land on the same primitive from different directions: a
 38// contract with no admin, a program whose upgrade authority is None. All three
 39// say the same thing, that the way out of "you are trusting the owner rather
 40// than the code" is an authority you can drop, permanently.
 41//
 42// This pattern has no owner, so its ladder has two rungs rather than four: the
 43// only actor the facade already trusts is whichever implementation is live.
 44const (
 45	StageOpen   = 0 // any realm under the prefix takes over by deploying
 46	StageSealed = 1 // nothing may register again, the live implementation is final
 47)
 48
 49var (
 50	live     Impl
 51	livePath string
 52	stage    = StageOpen
 53)
 54
 55// Register makes the calling realm the live implementation. Called from the
 56// implementation's own init, so deploying IS the upgrade.
 57func Register(cur realm, impl Impl) {
 58	if stage != StageOpen {
 59		panic("selfreg/facade/v0 is sealed, " + livePath + " is final")
 60	}
 61	caller := cur.Previous().PkgPath()
 62	if !strings.HasPrefix(caller, prefix) {
 63		panic("unauthorized: " + caller + " is not under " + prefix)
 64	}
 65	if impl == nil {
 66		panic("implementation must not be nil")
 67	}
 68	live, livePath = impl, caller
 69}
 70
 71// Seal ends this realm's upgradeability, forever. Callable only by the
 72// implementation currently serving, because with no owner that is the only
 73// actor the facade already trusts. It grants nothing new: whoever could deploy
 74// under the prefix could already take the realm over, and this only lets them
 75// make that the last word.
 76//
 77// One-way, and there is no rung above it.
 78func Seal(cur realm) {
 79	caller := cur.Previous().PkgPath()
 80	if livePath == "" || caller != livePath {
 81		panic("unauthorized: only the live implementation may seal, and that is " + livePath)
 82	}
 83	stage = StageSealed
 84}
 85
 86// Stage is the rung this realm is on. It only ever goes up.
 87func Stage() int {
 88	return stage
 89}
 90
 91// StageName is Stage as the word a caller reads in Render.
 92func StageName() string {
 93	if stage == StageSealed {
 94		return "sealed"
 95	}
 96	return "open"
 97}
 98
 99// Live is the package path currently serving, or "" before the first deploy.
100func Live() string {
101	return livePath
102}
103
104// Greet forwards to the live implementation.
105func Greet(name string) string {
106	assertLive()
107	return live.Greet(name)
108}
109
110// Version reports the live implementation's own version string.
111func Version() string {
112	assertLive()
113	return live.Version()
114}
115
116func assertLive() {
117	if live == nil {
118		panic("no implementation registered")
119	}
120}
121
122func Render(_ string) string {
123	if live == nil {
124		return ufmt.Sprintf("selfreg/facade/v0 [%s]: no implementation registered\n", StageName())
125	}
126	return ufmt.Sprintf("selfreg/facade/v0 [%s]: %s (%s)\n%s\n", StageName(), live.Version(), livePath, live.Greet("world"))
127}