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

authority.gno

4.27 Kb · 159 lines
  1package upgradeable
  2
  3import "strings"
  4
  5// Authority decides who may change what a [Proxy] points at.
  6//
  7// It is handed the identity of the caller -- the realm that crossed into the
  8// realm holding the Proxy. When a transaction is sent straight to that realm
  9// the caller is a user: pkgPath is empty and addr is the signer. When the
 10// call arrives through another realm, say a DAO executing a proposal, pkgPath
 11// names that realm and addr is the realm's own address.
 12//
 13// Both halves are passed because the two authorization styles on gno.land
 14// read different ones: an owner check compares addresses, a governance check
 15// compares realm paths. An implementation must read the half it means and
 16// ignore the other. In particular an empty pkgPath is not a realm -- it is
 17// every user call -- so a path check must reject it explicitly.
 18type Authority interface {
 19	Authorized(addr address, pkgPath string) bool
 20	String() string
 21}
 22
 23// AddrAuthority authorizes exactly one address: the signer of a direct call,
 24// or a realm acting at that address.
 25type AddrAuthority struct {
 26	addr address
 27}
 28
 29// NewAddrAuthority returns an Authority holding addr.
 30func NewAddrAuthority(addr address) *AddrAuthority {
 31	if !addr.IsValid() {
 32		panic(ErrBadAddress)
 33	}
 34	return &AddrAuthority{addr: addr}
 35}
 36
 37func (a *AddrAuthority) Authorized(addr address, _ string) bool {
 38	return a != nil && addr.IsValid() && addr == a.addr
 39}
 40
 41// Address returns the authorized address.
 42func (a *AddrAuthority) Address() address {
 43	if a == nil {
 44		return ""
 45	}
 46	return a.addr
 47}
 48
 49func (a *AddrAuthority) String() string {
 50	if a == nil {
 51		return "addr:<nil>"
 52	}
 53	return "addr:" + a.addr.String()
 54}
 55
 56// RealmAuthority authorizes a fixed set of realm paths, the shape r/gov/dao
 57// uses: a governance realm executes the upgrade, and the proxy recognizes it
 58// by the path it was deployed at rather than by an address.
 59//
 60// More than one path is allowed so that authority can be moved without a gap:
 61// list both the old and the new governance realm, let the new one take over,
 62// then narrow the list.
 63type RealmAuthority struct {
 64	paths []string
 65}
 66
 67// NewRealmAuthority returns an Authority holding paths. It panics on an empty
 68// list rather than authorizing nobody, and on a blank or padded entry rather
 69// than storing one that can never match a caller -- or, for a blank one,
 70// matches every user call.
 71func NewRealmAuthority(paths ...string) *RealmAuthority {
 72	if len(paths) == 0 {
 73		panic(ErrNoAuthority)
 74	}
 75	out := make([]string, 0, len(paths))
 76	for _, p := range paths {
 77		if p == "" || p != strings.TrimSpace(p) {
 78			panic(ErrBadRealmPath)
 79		}
 80		out = append(out, p)
 81	}
 82	return &RealmAuthority{paths: out}
 83}
 84
 85func (a *RealmAuthority) Authorized(_ address, pkgPath string) bool {
 86	if a == nil || pkgPath == "" {
 87		return false
 88	}
 89	for _, p := range a.paths {
 90		if p == pkgPath {
 91			return true
 92		}
 93	}
 94	return false
 95}
 96
 97// Paths returns a copy of the authorized realm paths.
 98func (a *RealmAuthority) Paths() []string {
 99	if a == nil {
100		return nil
101	}
102	dup := make([]string, len(a.paths))
103	copy(dup, a.paths)
104	return dup
105}
106
107func (a *RealmAuthority) String() string {
108	if a == nil {
109		return "realms:<nil>"
110	}
111	return "realms:" + strings.Join(a.paths, ",")
112}
113
114// AnyOf authorizes a caller that any of its members authorizes. It is the
115// handover shape for the common case: hold an address authority while you are
116// still deploying, add the governance realm, drop the address later.
117type AnyOf struct {
118	auths []Authority
119}
120
121// NewAnyOf returns an Authority satisfied by any of auths. It panics on an
122// empty list or a nil member, both of which would silently weaken or void
123// the check.
124func NewAnyOf(auths ...Authority) *AnyOf {
125	if len(auths) == 0 {
126		panic(ErrNoAuthority)
127	}
128	out := make([]Authority, 0, len(auths))
129	for _, a := range auths {
130		if a == nil {
131			panic(ErrNoAuthority)
132		}
133		out = append(out, a)
134	}
135	return &AnyOf{auths: out}
136}
137
138func (a *AnyOf) Authorized(addr address, pkgPath string) bool {
139	if a == nil {
140		return false
141	}
142	for _, sub := range a.auths {
143		if sub.Authorized(addr, pkgPath) {
144			return true
145		}
146	}
147	return false
148}
149
150func (a *AnyOf) String() string {
151	if a == nil {
152		return "anyOf:<nil>"
153	}
154	parts := make([]string, 0, len(a.auths))
155	for _, sub := range a.auths {
156		parts = append(parts, sub.String())
157	}
158	return "anyOf(" + strings.Join(parts, " ") + ")"
159}