Search Apps Documentation Source Content File Folder Download Copy Actions Download 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}