role.gno
4.34 Kb · 139 lines
1// Package role is a role held by one address and passed in two steps
2// (offer, accept), which its holder can cancel or renounce. A Role is pinned
3// to the realm that made it (its home): every method that acts takes that
4// realm's live cur and refuses any other value, so a *Role that leaked out of
5// its realm can do nothing anywhere else.
6package role
7
8import (
9 "chain"
10 "chain/runtime/unsafe"
11)
12
13// Canonical reports whether a is a valid address in its one canonical
14// spelling, lower case. The chain also takes the upper-case bech32 of an
15// account, but realms compare and key addresses as strings, so every
16// address argument that is stored, compared or paid to goes through this.
17func Canonical(a address) bool {
18 return !Upper(a) && a.IsValid()
19}
20
21// Upper reports whether a is spelled in upper case. Bech32 is all upper or
22// all lower case (mixed case is not IsValid), and an address starts with its
23// letter prefix "g", so the first byte tells: a scan of the 40 bytes costs
24// far more gas, strings.ToLower more still.
25func Upper(a address) bool {
26 s := a.String()
27 return len(s) > 0 && s[0] >= 'A' && s[0] <= 'Z'
28}
29
30// Role is kept by its home realm in an unexported variable.
31type Role struct {
32 home string // the realm that made it, the only one it answers
33 name string // for events and messages ("owner", "guardian")
34 holder address // "" once renounced
35 pending address // offered, "" if none
36}
37
38// New makes a role held by holder and pinned to the calling realm. Call it
39// from the realm's init.
40func New(name string, holder address) *Role {
41 if !Canonical(holder) {
42 panic("role: not an address")
43 }
44 return &Role{home: unsafe.CurrentRealm().PkgPath(), name: name, holder: holder}
45}
46
47// live checks that rlm is the home realm's runtime-current cur: a stale,
48// stored, Previous() or foreign realm value is refused (Class 2). The _ int
49// keeps the method non-crossing: v1.5.0 refuses crossing methods in /p/.
50func (r *Role) live(_ int, rlm realm) {
51 if !rlm.IsCurrent() || rlm.PkgPath() != r.home {
52 panic("role: not the home realm's live cur")
53 }
54}
55
56// Holder is who holds the role ("" once renounced).
57func (r *Role) Holder() address { return r.holder }
58
59// Pending is who was offered the role ("" if nobody).
60func (r *Role) Pending() address {
61 if r == nil { // no holder since a release renounced the role
62 return ""
63 }
64 return r.pending
65}
66
67// Is reports whether the immediate caller of the home realm holds the role.
68func (r *Role) Is(_ int, rlm realm) bool {
69 r.live(0, rlm)
70 return r.holder != "" && rlm.Previous().Address() == r.holder
71}
72
73// Must panics unless the immediate caller holds the role.
74func (r *Role) Must(_ int, rlm realm) {
75 if !r.Is(0, rlm) {
76 panic("role: " + r.name + " only")
77 }
78}
79
80// Offer offers the role to to. Offering it to the holder cancels an offer.
81func (r *Role) Offer(_ int, rlm realm, to address) {
82 r.Must(0, rlm)
83 if !Canonical(to) {
84 panic("role: not an address")
85 }
86 if to == r.holder {
87 r.pending = ""
88 chain.Emit("RoleOfferCancelled", "role", r.name)
89 return
90 }
91 r.pending = to
92 chain.Emit("RoleOffered", "role", r.name, "to", to.String())
93}
94
95// Accept takes the role offered to the caller.
96func (r *Role) Accept(_ int, rlm realm) {
97 r.live(0, rlm)
98 c := rlm.Previous().Address()
99 if r.holder == "" || r.pending == "" || c != r.pending {
100 panic("role: nothing offered to you")
101 }
102 from := r.holder
103 r.holder, r.pending = c, ""
104 chain.Emit("RoleChanged", "role", r.name, "from", from.String(), "to", c.String())
105}
106
107// Renounce gives the role up for good.
108func (r *Role) Renounce(_ int, rlm realm) {
109 r.Must(0, rlm)
110 r.holder, r.pending = "", ""
111 chain.Emit("RoleRenounced", "role", r.name)
112}
113
114// ---- the release handover: a realm's role and its mirror in data ----
115
116// Mirror is the holder: the mirror h when it exists (ok), as the previous
117// release may have changed it after r was made, else r's ("" once renounced).
118func Mirror(r *Role, h string, ok bool) address {
119 if ok {
120 return address(h)
121 }
122 if r == nil {
123 return ""
124 }
125 return r.holder
126}
127
128// Adopt is r once it agrees with the mirror h: unchanged without a mirror
129// (!ok) or when r already holds it, nil when the mirror says renounced,
130// else a new role named name for h, pinned to the calling realm (New).
131func Adopt(r *Role, name, h string, ok bool) *Role {
132 if !ok || r != nil && h == r.holder.String() {
133 return r
134 }
135 if h == "" {
136 return nil
137 }
138 return New(name, address(h))
139}