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}